logdrain
High-throughput, online log-template mining in Rust.
The Drain3 algorithm with path-preserving tokenization, masks, and stack-trace clustering - as an embeddable library and a CLI.
Feed logdrain a stream of noisy log lines and it learns their templates
incrementally - returning a cluster id per line, with no batch training and no model
files.
GET /api/v1/servers/409/metrics 200 12ms ┐
GET /api/v1/servers/410/metrics 200 9ms ├─► GET /api/v1/servers/<*>/metrics <*> <*>
GET /api/v1/servers/873/metrics 503 41ms ┘
request 550e8400-…-446655440000 done 88ms ┐
request 6ba7b810-…-00c04fd430c8 done 51ms ├─► request <uuid> done <*>
request 7c9e6679-…-e07fc1f90ae7 done 75ms ┘
5,000,000 noisy lines → 8 templates in ~2 s on a single core. Memory tracks the number of templates, not the volume of input.
✨ Features
Online & incremental
add(line) returns a cluster id instantly - created, generalized, or matched. No batch step.
Path-preserving tokenization
/servers/409/foo clusters to /servers/<*>/foo, not <*>. URLs and structured logs stay readable.
Configurable masks
UUIDs, IPs, emails, JWTs → named placeholders before clustering - built-in or custom.
Stack-trace clustering
Cluster multi-line traces on their first line and keep the full trace as a per-cluster suffix.
Extraction & members
extract() recovers the variable values of a line; attach de-duplicated labels per cluster.
Exhaustive & persistent
Exact per-template counts (no sampling, no top-N) with snapshot/restore to a pluggable backend.
Thread-safe & fast
add(&self) from many threads; a sharded, mostly-lock-free hot path (~1-2M lines/sec per core).
💡 Use cases
Noise reduction & triage
Collapse millions of lines into a handful of templates, ranked by volume, to see what's actually happening.
New-pattern alerting
Alert on the Created signal - the first time a never-seen log shape appears (new errors, attacks, regressions).
Incident & exception clustering
Rank distinct stack-trace failures by frequency, so you triage the top one instead of scrolling.
Pipeline cost reduction
Store (template, count) instead of raw lines, or sample by template - a Vector/Cribl-style stage.
Structured extraction
Turn unstructured logs into (template_id, params) for dashboards, ML features, or alerting.
Edge / agent summarization
Embed it in a log shipper to summarize at the source and cut egress and ingest cost.
📦 Install
🖥️ CLI
Reads files (or stdin), one record per line, and prints the templates it found:
$ logdrain --path-delimiters / --masks uuid,ipv4,email access.log
ID SIZE TEMPLATE
1 5 GET /api/v1/servers/<*>/metrics <*> <*>
2 4 POST /api/v1/users/<*>/login <*> from <ipv4>
3 4 request <uuid> completed in <*>
4 4 signup for <email> from <ipv4>
5 4 cache miss for key <*>
… (30 lines → 9 templates)
| |
| Option | Description |
|---|---|
--format text|json|csv |
output format (default text) |
--key <FIELD> |
parse each line as JSON and extract this dot-path field |
--masks <names> |
comma list of built-ins: uuid,hex32,email,ipv4,jwt |
--path-delimiters <CHARS> |
characters preserved as token boundaries, e.g. / |
--first-line-only |
cluster on the first line; keep the rest as a suffix |
--sort size|template|id|rate |
ordering (default size) |
--min-size <N> |
hide clusters smaller than N |
--sim-th <FLOAT> |
similarity threshold (default 0.4) |
--depth <N> |
prefix-tree depth (default 4) |
📚 Library
use ;
let miner = builder
.path_delimiters
.masks
.build
.unwrap;
miner.add;
miner.add;
for c in miner.clusters
// Classify a new line and recover its variable parts.
let = miner.extract.unwrap;
assert_eq!; // <ipv4> is a named placeholder, not a wildcard
use ;
let store = new;
miner.save_state.unwrap; // atomic write (tmp + fsync + rename)
let restored = builder.build.unwrap;
restored.load_state.unwrap; // rebuilds the tree from the snapshot
Persistence is a small sync trait; MemoryPersistence and FilePersistence ship in
core, and Miner::{snapshot, restore} expose the raw bytes for any other backend.
⚡ Performance
Numbers from one mid-range Linux box - they vary with hardware and load, so treat them as orders of magnitude and re-run on your own target.
| Throughput (single thread) | ~1-2M lines/sec |
| Throughput (8 threads) | multi-M lines/sec (~3.3× scaling) |
add latency (steady / cold) |
~0.3 µs / ~1.8 µs |
| Memory | bounded by template count, not input |
Reproduce: cargo bench -p logdrain --bench add, or
cargo run --release -p logdrain --example scaling.
🗺️ Status & roadmap
Available: logdrain (library) and logdrain-cli, published and tested.
Roadmap: an HTTP service (draind), Redis/S3 persistence backends, and
hierarchical/distributed aggregation.
🤝 Contributing
Issues and pull requests welcome. Please keep the standard checks green:
⚖️ License
Dual-licensed under Apache-2.0 or MIT, at your option. Minimum supported Rust version: 1.85.