freeswitch_log_parser/lib.rs
1//! Parser for FreeSWITCH log files.
2//!
3//! Handles the full complexity of `mod_logfile` output: five distinct line
4//! formats, multi-line CHANNEL_DATA and SDP dumps, truncated buffer collisions,
5//! and per-session state tracking — all with zero dependencies and no regex.
6//!
7//! # Architecture
8//!
9//! The parser is organized in three composable layers, each wrapping the previous:
10//!
11//! - **Layer 1** ([`parse_line`]) — stateless, zero-allocation single-line classifier
12//! - **Layer 2** ([`LogStream`]) — structural state machine that groups continuations,
13//! classifies messages, and detects multi-line blocks
14//! - **Layer 3** ([`SessionTracker`]) — per-UUID state machine that propagates
15//! dialplan context, channel state, and variables across entries; extensible
16//! via [`SessionTracker::with_relationship_hook`] for custom leg detection
17//!
18//! See `docs/design-rationale.md` in the repository for the full story on format
19//! discovery, parsing strategy, and why each layer exists.
20//!
21//! # Examples
22//!
23//! Read lines from stdin, process through all three layers, and print enriched entries:
24//!
25//! ```no_run
26//! use std::io;
27//! use freeswitch_log_parser::{read_log_lines, LogStream, SessionTracker};
28//!
29//! // read_log_lines tolerates mod_logfile's truncated codepoints; the strict
30//! // BufRead::lines() reader would panic on them.
31//! let lines = read_log_lines(io::stdin().lock()).map(|d| d.expect("read error").text);
32//! let stream = LogStream::new(lines);
33//! let mut tracker = SessionTracker::new(stream);
34//!
35//! for enriched in tracker.by_ref() {
36//! let e = &enriched.entry;
37//! println!("{} [{}] {}", e.timestamp, e.message_kind, e.message);
38//! }
39//!
40//! let stats = tracker.stats();
41//! eprintln!("{} lines, {} unclassified",
42//! stats.lines_processed, stats.lines_unclassified);
43//! ```
44//!
45//! # Feature flags
46//!
47//! - **`cli`** — enables the `fslog` binary with clap, xz decompression, and regex filtering
48
49mod attached;
50mod chain;
51mod decode;
52mod level;
53mod line;
54mod message;
55mod session;
56mod stream;
57
58pub use attached::{AttachedLines, AttachedLinesIter};
59pub use chain::{SegmentTracker, TrackedChain};
60pub use decode::{classify_utf8, read_log_lines, DecodedLine, Utf8Decode};
61pub use freeswitch_types::{
62 variables::SofiaVariable, CallDirection, CallState, ChannelState, ChannelVariable,
63};
64pub use level::{LogLevel, ParseLevelError};
65pub use line::{parse_line, LineKind, RawLine};
66pub use message::{classify_message, DtmfSource, MessageKind, SdpDirection, SipInviteDirection};
67pub use session::{EnrichedEntry, SessionSnapshot, SessionState, SessionTracker};
68pub use stream::{
69 Block, LogEntry, LogStream, ParseStats, UnclassifiedLine, UnclassifiedReason,
70 UnclassifiedTracking,
71};