Architecture
How rlg is put together, for contributors. For how to use it, start with the introduction; for the reasoning behind individual decisions, read the ADRs.
The workspace
Ten publishable crates share one version and are released together.
| Crate | Role | Depends on |
|---|---|---|
rlg | The logging engine: records, formats, sinks, config | — |
rlg-cli | rlg binary: parse, filter and render log files | rlg |
rlg-report | rlg-report binary: summaries of a log file | rlg, rlg-cli |
rlg-mcp | MCP server exposing log files as tools | rlg, rlg-cli |
rlg-otlp | OTLP/HTTP exporter to an OpenTelemetry Collector | rlg |
rlg-redact | Redaction of secrets and PII before a record is written | rlg |
rlg-tower | tower::Layer emitting per-request access logs | rlg |
rlg-test | Assertions over captured records in tests | rlg |
rlg-wasm | WebAssembly bindings | rlg |
rlg-ebpf | Enrichment of records with kernel context | rlg |
crates/xtask holds maintainer automation and is never published.
The engine (rlg)
application thread flusher thread (rlg-flusher)
────────────────── ────────────────────────────
Log::info("…").fire()
└─ ENGINE.ingest(event) loop:
├─ level filter (atomic) drain ≤ 64 events
├─ ShardedQueue::push ────▶ format each (Display)
└─ unpark flusher PlatformSink::emit
park (5 ms fallback)
The split is the design: the application thread does one atomic level
check, one queue push and one unpark, and never formats, allocates a
string or takes a lock. Everything expensive happens on the flusher.
- Records (
log.rs):Logis built through a fluent API and carries level, component, description, time, au64session id and aBTreeMapof attributes.componentandtimeareCow<'static, str>, so static strings are never copied. - Queue (
engine.rs,sharded_queue.rs): a 65,536-slot ring buffer ofcrossbeam::ArrayQueue, one shard by default, eight with thefast-queuefeature (ADR 0009). A full shard evicts its oldest record. The shutdown handshake and session-id monotonicity are checked by Loom (ADR 0001) and Kani (ADR 0004). - Formats (
log.rs,log/write.rs): fourteen output formats (JSON, NDJSON, ECS, GELF, Logstash, OTLP, MCP, logfmt, CLF, CEF, ELF, W3C, Apache access log, Log4j XML) written straight to the formatter with no intermediateserde_json::Value. Their shape is property-tested (ADR 0003). - Sinks (
sink.rs):os_logon macOS through FFI (the one placeunsafeis allowed),journaldover its datagram socket on Linux, a file, or stdout.io_uringis an opt-in file sink on Linux (ADR 0011). - Configuration (
config.rsandconfig/): TOML loaded withConfig::loadorload_async, validated, and optionally hot-reloaded by polling the file (config/hot_reload.rs,tokiofeature). Rotation policies (size:N,time:N,date,count:N) parse inconfig/log_rotation.rsand run inrotation.rs. - Bridges (
logger.rs,tracing.rs):rlg::init()installs alog::Logimplementation; thetracing-layerfeature adds atracing_subscriber::Layer. Both feed the same engine. - Dashboard (
tui.rs): an opt-in terminal view of throughput, levels and formats, started withRLG_TUI=1.
The satellites
rlg-mcpserves four tools (tail_log,filter_log,summarize_errors,tail_logs_glob), one prompt and two resources through the official MCP SDK.ops.rsholds the operations as plain functions,model.rsthe tool arguments and results,lib.rsthe server, andtransport.rswithtransport/sse.rs(shared across the suite’s MCP servers) the stdio, streamable HTTP and HTTP+SSE transports.rlg-otlpsends OTLP/HTTP JSON to a local Collector, which owns TLS (ADR 0015). Both exporters use an in-house HTTP/1.1 client (http.rs), the blocking one overstd::netand the async one over Tokio, and share retry, jitter and circuit-breaking frombackoff.rs(ADR 0010).rlg-redactscans each value once, against a single regex that fuses every built-in pattern into one alternation (ADR 0008).rlg-wasmandrlg-ebpfare scaffolds on their way to full implementations (ADR 0013, ADR 0012).
Invariants the gates hold
| Invariant | Enforced by |
|---|---|
| No undefined behaviour in the engine | Miri on every push |
| Shutdown and ordering under concurrency | Loom proofs |
| Level and counter invariants | Kani proofs |
| Parsers survive hostile input | cargo-fuzz targets (ADR 0002) |
| Dependencies are licensed, unique and reviewed | cargo-deny over all features, cargo-vet |
| Public API changes are deliberate | cargo-semver-checks |
| Functions and files stay small | scripts/complexity-gate.py against a baseline |
| Coverage stays above 95% | tarpaulin in CI |
Run all of them locally with make verify; see
DEVELOPMENT.md.