Skip to main content

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}
24
25/// Record of a single unclassified line, captured when tracking is enabled.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct UnclassifiedLine {
28    pub line_number: u64,
29    pub reason: UnclassifiedReason,
30    /// The full line content; only populated under [`UnclassifiedTracking::CaptureData`].
31    pub data: Option<String>,
32}
33
34/// Cumulative parsing statistics, updated as lines flow through the stream.
35#[derive(Debug, Clone, Default)]
36pub struct ParseStats {
37    pub lines_processed: u64,
38    pub lines_unclassified: u64,
39    /// Lines that became part of entries (primary line + attached lines per entry).
40    pub lines_in_entries: u64,
41    /// Empty lines that arrived with no pending entry to attach to.
42    pub lines_empty_orphan: u64,
43    /// Extra chunks produced by splitting physical lines that held more than
44    /// one record: a cut write's successor glued on, or write contention
45    /// interleaving two records. One line yielding N chunks counts N - 1.
46    pub lines_split: u64,
47    /// Continuation lines an entry could not store because its attached buffer
48    /// outgrew the offsets addressing it. The entry carries a
49    /// [`ParseWarning::AttachedOverflow`](super::ParseWarning::AttachedOverflow)
50    /// naming each one.
51    pub lines_dropped: u64,
52    /// Populated only when tracking is `TrackLines` or `CaptureData`.
53    pub unclassified_lines: Vec<UnclassifiedLine>,
54}
55
56impl ParseStats {
57    /// How far the line accounting is out of balance, positive when lines went
58    /// missing and negative when the entries claim more than arrived.
59    ///
60    /// Returns 0 when the parser correctly accounts for every input line. Any
61    /// other value, either sign, indicates a parser bug. A line the parser
62    /// knowingly could not keep is counted in
63    /// [`lines_dropped`](Self::lines_dropped) rather than going missing here.
64    ///
65    /// Invariant:
66    /// `lines_processed + lines_split == lines_in_entries + lines_empty_orphan + lines_dropped`
67    pub fn unaccounted_lines(&self) -> i64 {
68        let expected = self.lines_in_entries + self.lines_empty_orphan + self.lines_dropped;
69        let actual = self.lines_processed + self.lines_split;
70        actual as i64 - expected as i64
71    }
72}