freeswitch_log_parser/stream/stats.rs
1//! Cumulative parsing statistics ([`ParseStats`]) and the tunable detail
2//! level for lines that could not be fully classified ([`UnclassifiedTracking`]).
3
4/// Controls how much detail is recorded for lines that couldn't be fully classified.
5///
6/// Higher fidelity levels allocate more memory. The default is `CountOnly`.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum UnclassifiedTracking {
9 /// Increment the counter only — zero allocation.
10 CountOnly,
11 /// Record line number and reason for each unclassified line.
12 TrackLines,
13 /// Like `TrackLines` plus the full line content.
14 CaptureData,
15}
16
17/// Why a line was marked as unclassified.
18#[derive(Debug, Clone, PartialEq, Eq)]
19#[non_exhaustive]
20pub enum UnclassifiedReason {
21 /// Bare continuation line arrived with no pending entry to attach to.
22 OrphanContinuation,
23 /// Line was parsed but the message didn't match any known pattern.
24 UnknownMessageFormat,
25 /// EXECUTE or variable line was only partially readable.
26 TruncatedField,
27}
28
29/// Record of a single unclassified line, captured when tracking is enabled.
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct UnclassifiedLine {
32 pub line_number: u64,
33 pub reason: UnclassifiedReason,
34 /// The full line content; only populated under [`UnclassifiedTracking::CaptureData`].
35 pub data: Option<String>,
36}
37
38/// Cumulative parsing statistics, updated as lines flow through the stream.
39#[derive(Debug, Clone, Default)]
40pub struct ParseStats {
41 pub lines_processed: u64,
42 pub lines_unclassified: u64,
43 /// Lines that became part of entries (primary line + attached lines per entry).
44 pub lines_in_entries: u64,
45 /// Empty lines that arrived with no pending entry to attach to.
46 pub lines_empty_orphan: u64,
47 /// Extra chunks produced by splitting physical lines that held more than
48 /// one record: a cut write's successor glued on, or write contention
49 /// interleaving two records. One line yielding N chunks counts N - 1.
50 pub lines_split: u64,
51 /// Continuation lines an entry could not store because its attached buffer
52 /// outgrew the offsets addressing it. The entry carries a
53 /// [`ParseWarning::AttachedOverflow`](super::ParseWarning::AttachedOverflow)
54 /// naming each one.
55 pub lines_dropped: u64,
56 /// Populated only when tracking is `TrackLines` or `CaptureData`.
57 pub unclassified_lines: Vec<UnclassifiedLine>,
58}
59
60impl ParseStats {
61 /// Lines that were processed but not accounted for by any tracking category.
62 ///
63 /// Returns 0 when the parser correctly accounts for every input line.
64 /// A non-zero value indicates a parser bug — lines were silently lost.
65 /// A line the parser knowingly could not keep is counted in
66 /// [`lines_dropped`](Self::lines_dropped) rather than going missing here.
67 ///
68 /// Invariant:
69 /// `lines_processed + lines_split == lines_in_entries + lines_empty_orphan + lines_dropped`
70 pub fn unaccounted_lines(&self) -> u64 {
71 let expected = self.lines_in_entries + self.lines_empty_orphan + self.lines_dropped;
72 let actual = self.lines_processed + self.lines_split;
73 actual.saturating_sub(expected)
74 }
75}