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.

```rust
pub fn init(
    capacity: usize,
    channel_capacity: usize,
    service_name: &str,
    storage_path: Option<&str>,
) -> Self
```

* `capacity`: number of `LogEvent`s 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.

```rust
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:

```rust
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.