Skip to main content

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 of LogEvents 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:

  1. Panics: a global panic hook writes a snapshot before the process exits.
  2. Signals: SIGINT, SIGTERM, SIGQUIT, and SIGSEGV trigger one.
  3. Periodic trigger: the writer thread creates one every 60 seconds if new events have been logged.
  4. 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.