cardio
Fast Rust CLI that trims the fat from logs — fewer tokens for LLM agents.
Logs can be GBs: cardio streams line by line with near-constant memory
(Bloom-filter dedup, capped stats/trace maps). Agents can fetch logs
themselves (--gcloud, --bq) and get context with cardio --agent-md.
Install
With cargo (installs the cardio binary):
Without cargo (macOS/Linux — downloads the binary from the latest release):
|
Windows: grab the cardio-Windows-X64.exe from the releases page and put it on your PATH.
Or from source:
Input
⚠ The default pipeline drops lines. Whole repeated lines are removed (Bloom filter — ~1% false positives: a unique line can be discarded as if repeated) and output stops at 2000 lines (
--max-linesoverrides). It's the right trade for trimming noise; when any line might be THE answer, run--rawor narrow with--query.
gcloud mode pushes --since/--until bounds into the server-side filter
(timestamp clauses), so it downloads only the window you asked for. Without
time bounds the query streams oldest-first — bound it with --since (e.g. an
ISO timestamp 7 days back) if you only need recent entries.
bq mode runs bq query --format=csv and the result rows flow through the
same pipeline (query/range/trace/summary/stats) — pull trace data with SQL
and trim it in one step. Rows are decoded as CSV (quoted fields with
embedded commas/newlines keep a single row) and the header row is skipped.
bq tips:
# TIMESTAMP columns render as "2026-10-13 12:00:00 UTC" — that leaves a
# stray ` UTC` in the output and --since/--until can't read it. Emit ISO
# so cardio understands it:
--max_rows=10000000is passed because bq silently caps selects at 1000 rows otherwise.--since/--untilare NOT pushed into bq (the SQL is yours): filter dates in the query with aWHEREclause overFORMAT_TIMESTAMP(...)text, and the same ISO bounds still apply locally.--max-linescuts the bq process early once the cap is reached.
Flags
| Flag | Effect |
|---|---|
[file] |
Log file. Omitted → stdin. |
--tail N |
Only the LAST N kept lines (after dedup/query; reads the whole input, ignores the cap). |
--raw |
Skip the default agent pipeline: no dedup, no line cap. |
--agent-md |
Print an agent-facing markdown card (pipe: cardio --agent-md > AGENTS.md). |
--query TEXT |
Keep only lines containing TEXT (substring, applied first). |
--dedup |
Drop repeated whole lines (Bloom filter — ~1% false positives, and they're unique lines thrown away). |
--max-lines N |
Cap output (events in --trace/--summary mode). |
--json |
NDJSON output: {"index","content"} per line; with --trace → {"id","events"}; with --summary → {"id","count","first","last"}. |
--since TS |
Timestamp >= this. ISO (2026-10-09T05:00:00) or YYYY-MM-DD (= day start). |
--until TS |
Timestamp < this. ISO, or YYYY-MM-DD (= NEXT-day start, so the end day is kept). |
--trace REGEX |
Group lines by captured ID, e.g. "request_id=([a-z0-9-]+)"; sessions printed at the end. |
--summary |
With --trace: compact per-ID map — event count + first/last event, IDs preserved. |
--bq SQL |
Run the bq CLI with this SQL (CSV) and process its rows. One source only. |
--stats |
Survey mode: top-20 pattern frequencies to stderr, no stream. --stats --json → NDJSON {"pattern","count"} on stdout. |
--config FILE |
TOML: timestamp_regex, boilerplate_regex, default_trace. |
Time handling
ISO timestamps compare lexicographically (string order == time order). Timezone
offsets are matched and trimmed but comparisons are tz-naive: mixing
+02:00-suffixed logs with UTC bounds can misjudge hours around day boundaries.
Config example
= '\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}'
= '(?i)\b(INFO|DEBUG|TRACE)\b'
= 'trace_id=([a-zA-Z0-9-]+)'
Examples
# agent-ready error digest
|
# what happened to one request
# same, compact: first/last event + count per request
# survey a 10GB log without streaming output