Core Engine
The ttlog crate — Trace orchestrator, writer/listener threads, ring buffer, and listener API.
The ttlog crate is the core engine. It handles a log event's lifecycle:
creation, listener processing, and storage in the snapshot buffer.
Core Concepts
The Trace Struct
The Trace struct is the orchestrator. One instance is created at startup and stored
in a thread_local! static so the logging macros can reach it.
Responsibilities:
- Spawns and manages the writer and listener threads.
- Holds the channels that decouple application threads from the logging backend.
- Exposes the configuration API: adding listeners, setting the log level.
Initialization: Trace::init()
Trace::init() initializes the whole system.
pub fn init(
capacity: usize,
channel_capacity: usize,
service_name: &str,
storage_path: Option<&str>,
) -> Self
capacity: number ofLogEvents in the in-memory ring buffer for snapshots. Larger means more history in a crash dump, but more memory.channel_capacity: size of the bounded channel used for control messages (like "create a snapshot now").service_name: application identifier, embedded in snapshot files.storage_path: directory for snapshot files. Defaults to./tmp/.
LogEvent
Internal representation of a log record. Uses integer IDs instead of strings to keep the struct small.
pub struct LogEvent {
pub packed_meta: u64, // Timestamp, level, and thread ID packed into a u64
pub target_id: u16,
pub message_id: Option<num::NonZeroU16>,
pub kv_id: Option<num::NonZeroU16>,
pub file_id: u16,
pub position: (u32, u32), // (line, column)
}
The _id fields are integer handles to strings managed by the StringInterner.
Listeners
Listeners process log events on a dedicated listener thread. Use one to send output to the console, a file, or a network destination.
The LogListener trait:
pub trait LogListener: Send + Sync + 'static {
fn handle(&self, event: &LogEvent, interner: &StringInterner);
// ... other optional methods
}
Two built-in listeners ship with TTLog:
StdoutListener: formats and prints logs to standard output.FileListener: writes logs to a file.
Implement the trait for any other destination — network service, database, anything.
Snapshots
TTLog keeps a lock-free, in-memory ring buffer of the last N log events (N is the
capacity passed to init). Listeners don't consume it — the buffer exists for crash
diagnostics.
A snapshot dumps the buffer plus application metadata. Snapshots fire on:
- Panics: a global panic hook writes a snapshot before the process exits.
- Signals:
SIGINT,SIGTERM,SIGQUIT, andSIGSEGVtrigger one. - Periodic trigger: the writer thread creates one every 60 seconds if new events have been logged.
- Manual request:
trace.request_snapshot("my-reason").
Snapshot files are serialized with serde_cbor and compressed with lz4.
String Interning
StringInterner avoids allocating and cloning duplicate strings (file paths, module
names, repeated log messages).
On the first info!("Starting up") call in src/main.rs, the interner stores
"Starting up", "my_app::main", and "src/main.rs" in a global thread-safe hash
map, assigning each a unique integer ID.
Later calls reuse the cached ID — an integer lookup. A thread_local! cache avoids
lock contention in the hot path.