freeswitch-log-parser
Rust library for parsing FreeSWITCH log files. Three-layer streaming
architecture, no regex, single runtime dependency (freeswitch-types
for typed enums).
Layers
Layer 1: parse_line() &str -> RawLine (stateless, zero-alloc)
Layer 2: LogStream Iterator -> LogEntry (structural state machine)
Layer 3: SessionTracker LogStream -> EnrichedEntry (per-UUID state)
Layer 1 classifies individual log lines into five formats (Full, System, UuidContinuation, BareContinuation, Truncated) and extracts positional fields (UUID, timestamp, log level, source, message).
Layer 2 groups continuation lines, detects block boundaries
(CHANNEL_DATA dumps, SDP bodies), reassembles multi-line variable
values, and classifies messages into semantic MessageKind variants:
Execute, Dialplan, ChannelData, ChannelField, Variable,
SdpMarker, StateChange, CodecNegotiation, Media,
ChannelLifecycle, SipInvite, EventSocket, General, plus the
synthetic FileChange/DateChange markers. SipInvite is the
canonical sip_call_id ↔ channel_uuid correlation primitive — sofia
emits it for every inbound and outbound call regardless of dialplan.
Every entry carries both a typed Block and raw attached lines.
Layer 3 maintains per-UUID session state (dialplan context, channel
state, learned variables, call direction, caller/destination numbers)
and propagates it across entries. Also links bridged a-leg ↔ b-leg
sessions via other_leg_uuid — from the explicit Peer UUID: suffix
when present, or by matching a live b-leg channel_name when
FreeSWITCH omits it. Yields EnrichedEntry with a SessionSnapshot
alongside the raw LogEntry.
Each layer wraps the previous and can be used independently.
Performance
Built to handle the worst mod_logfile produces — 2 KiB buffer
truncations, multi-line CHANNEL_DATA dumps with embedded SDP/XML,
write-contention collisions. An 11 MB fixture (185 physical lines
averaging ~60 KB each) parses in ~60 ms. LogEntry::attached uses
a compact contiguous buffer (AttachedLines) instead of
Vec<String> to amortize allocations on CHANNEL_DATA-heavy entries
— iteration via &entry.attached works unchanged.
Usage
use ;
use ;
let lines = stdin.lock.lines.map;
let stream = new;
for enriched in new
Unclassified data tracking
Lines that can't be fully classified are tracked, never silently dropped:
use ;
let mut stream = new
.unclassified_tracking;
for entry in stream.by_ref
let stats = stream.stats;
eprintln!;
Multi-file input with segment tracking
TrackedChain concatenates named input segments (typically rotated log
files) into a single iterator and records where each segment starts.
SegmentTracker then maps any line number back to its source file —
useful for emitting FileChange markers or reporting errors with the
original filename:
use ;
let segments = vec!;
let = new;
let stream = new;
// ... consume stream; query tracker.segment_for_line(n) as needed.
fslog binary
The crate also ships an fslog CLI for searching and tailing logs.
Build with cargo build --release --features cli (search/filter) or
--features tui (adds the monitor subcommand with a ratatui call
table). The library itself has no CLI dependencies unless a feature is
enabled.
License
LGPL-2.1-or-later