Intelligent log compression with millisecond search β built in Rust.
logcompressor is a command-line tool that compresses text logs with industry-leading ratios (typically 75β90% space savings) while remaining searchable without full decompression. It ships as a single, dependency-free binary that runs anywhere your shell does.
Point it at a folder of logs, give it a JSON config describing your log shape, and you get:
- ποΈ Compact storage β pattern-aware compression on top of a Zstd dictionary.
- β‘ Sub-second search β bloom-filtered indexes skip irrelevant blocks entirely.
- π Built-in web viewer β pivot, filter and inspect matches directly in the browser.
- π§© Universal tokenizer β works on any text log (CloudWatch, nginx, k8s, Postgres, JVM, custom) without per-format adapters.
- π Bounded memory β handles 100GB+ files with a fixed memory ceiling.
| Feature | logcompressor |
gzip / zstd plain |
grep + tail |
|---|---|---|---|
| Compression ratio on real logs | ~85% | ~60% | n/a |
| Search without decompressing | β | β | β |
| Block-level skip (time / level) | β | β | β |
| JSON output for pipelines | β | β | partial |
| Web UI for triage | β | β | β |
| Bounded memory on huge files | β | β | β |
Every release publishes signed, ready-to-run binaries on the GitHub Releases page. Pick the command that matches your machine and paste it into a terminal β it downloads the binary, drops it on your PATH, and you're ready to go.
mkdir -p ~/.local/bin && \
curl -L -o ~/.local/bin/logcompressor \
https://github.com/InigoRomero/logzstd-Log-Compressor/releases/latest/download/logcompressor-macos-arm64 && \
curl -L -o ~/.local/bin/global.dict \
https://github.com/InigoRomero/logzstd-Log-Compressor/releases/latest/download/global.dict && \
chmod +x ~/.local/bin/logcompressor && \
xattr -d com.apple.quarantine ~/.local/bin/logcompressor 2>/dev/null; \
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc && \
exec $SHELL
logcompressor --helpmkdir -p ~/.local/bin && \
curl -L -o ~/.local/bin/logcompressor \
https://github.com/InigoRomero/logzstd-Log-Compressor/releases/latest/download/logcompressor-macos-x86_64 && \
curl -L -o ~/.local/bin/global.dict \
https://github.com/InigoRomero/logzstd-Log-Compressor/releases/latest/download/global.dict && \
chmod +x ~/.local/bin/logcompressor && \
xattr -d com.apple.quarantine ~/.local/bin/logcompressor 2>/dev/null; \
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc && \
exec $SHELL
logcompressor --helpmkdir -p ~/.local/bin && \
curl -L -o ~/.local/bin/logcompressor \
https://github.com/InigoRomero/logzstd-Log-Compressor/releases/latest/download/logcompressor-linux-x86_64 && \
curl -L -o ~/.local/bin/global.dict \
https://github.com/InigoRomero/logzstd-Log-Compressor/releases/latest/download/global.dict && \
chmod +x ~/.local/bin/logcompressor && \
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && \
exec $SHELL
logcompressor --help$InstallDir = "$env:LOCALAPPDATA\Programs\logcompressor"
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
Invoke-WebRequest -Uri "https://github.com/InigoRomero/logzstd-Log-Compressor/releases/latest/download/logcompressor-windows-x86_64.exe" -OutFile "$InstallDir\logcompressor.exe"
Invoke-WebRequest -Uri "https://github.com/InigoRomero/logzstd-Log-Compressor/releases/latest/download/global.dict" -OutFile "$InstallDir\global.dict"
$user = [Environment]::GetEnvironmentVariable("Path", "User")
if ($user -notlike "*$InstallDir*") { [Environment]::SetEnvironmentVariable("Path", "$user;$InstallDir", "User") }
Write-Host "Installed to $InstallDir. Open a new terminal and run: logcompressor --help"The CLI looks for
global.dictnext to its own executable, so both files must live in the same folder.
For a step-by-step walkthrough (uninstall, troubleshooting, custom install paths) see INSTALL.md.
# 1) Point the CLI at the config that describes your log shape
logcompressor config myapp
# 2) Compress
logcompressor compress --input logs/app.log
# 3) Search
logcompressor search -F "Session expired"
logcompressor search "user_id: 1234" --from 2026-05-01 --to 2026-05-12
logcompressor search "." --level ERROR --level WARN
# 4) Open the web viewer
logcompressor serve --port 8081A real-world example with AWS Lambda CloudWatch logs (1.1 MB, 10k records):
$ logcompressor compress --input logs/aws.logs --level 5
Detected 5 log templates.
Total original size: 1.12 MB | Compressed: 0.29 MB | Space savings: 73.80%
Compression finished in 14.68ms
logcompressor needs a JSON file describing where the timestamp lives in each line and which named groups to extract:
{
"output": "logs/compressed/myapp",
"parse_rules": {
"pattern": "^(?<timestamp>\\S+) \\[(?<level>[A-Z]+)\\] (?<message>.*)$",
"timestamp_format": "YYYY-MM-DDTHH:mm:ss"
},
"columns": [
{ "key": "timestamp", "label": "Date", "type": "datetime" },
{ "key": "level", "label": "Level", "type": "string", "filter": ["DEBUG", "INFO", "WARN", "ERROR"] },
{ "key": "message", "label": "Message", "type": "text" }
],
"compression": {
"include_metadata_keys": ["timestamp", "level"],
"text_field": "message"
}
}That's it. The tokenizer handles everything else automatically β decimals, IPs, UUIDs, request IDs, currency amounts, paths β using a universal rule: if a token contains a digit, it is a variable.
Need more control? See the tutorial on writing a config from scratch.
| Topic | Where |
|---|---|
| First-time install | INSTALL.md |
| Hands-on tutorial (recommended for new users) | TUTORIAL.md |
| Release notes | CHANGELOG.md |
The CLI complains about a missing global.dict
The dictionary must sit next to the executable, not next to your config. Copy it once after installing:
cp global.dict $(dirname "$(which logcompressor)")/Web viewer shows "Found N events" but rows are empty
Your parse_rules.pattern does not match the raw lines. The web viewer caches the config on startup, so after editing config.json you must restart logcompressor serve. Validate with:
curl -s 'http://127.0.0.1:8081/api/search?q=.&page_size=1' | jq '.hits[0].metadata'If metadata is {}, the regex still doesn't match. Adjust and restart.
Compression reports many "Rotated N pattern parts"
The tokenizer is producing too many distinct templates, typically because your log has identifier-like tokens without digits (e.g. req-abcDEF). Add a custom variable rule to your config:
"parse_rules": {
"pattern": "...",
"variables": [
{ "name": "request_id", "regex": "req-[A-Za-z0-9]+" }
]
}Paths get resolved relative to the wrong directory
--input and config.output are resolved against the current working directory. Either always run from the repo root, or use absolute paths in both.
Measured on a M2 MacBook Pro (single-threaded compress, 4 threads search):
| Input | Size | Ratio | Compress time | Search latency |
|---|---|---|---|---|
| Application log (synthetic) | 5 GB | 89.6% | 35 s | 5.7 s (full scan, 2.5M hits) |
| AWS Lambda CloudWatch export | 1.1 MB | 73.8% | 15 ms | 3 ms (6 hits) |
| AWS Lambda CloudWatch export | 1.1 MB | 73.8% | 15 ms | 5 ms (2k hits) |
Memory stays bounded regardless of input size thanks to the streaming compressor and the LRU-evicted encoder pool.
logcompressor uses a streaming, single-pass design tailored to pattern-rich text logs:
- Parse the timestamp out of each line using the regex you provide.
- Tokenize the rest with configurable delimiters; any token containing at least one digit (or matching a custom regex) becomes a
{}placeholder. - Group lines by the resulting template hash β
Duration: 12.09 msandDuration: 19.01 mscollapse to the same group. - Stream-compress each group through Zstd with a shared dictionary, writing a
<hash>_<run-id>.txt.lzstdblock and a small.zidxsidecar containing min/max timestamp, level, and metadata samples. - Search consults
.zidxfiles first to skip entire blocks that cannot possibly match, then streams matches out β yielding sub-second latency on multi-gigabyte archives.
A bounded LRU pool of 512 live encoders guarantees memory stays flat even if a log produces thousands of distinct templates.
logcompressor is distributed under the EULA included with this release. See LICENSE for the full terms.
- The Zstandard team for the compression core.
- The
clap,rayon,regexandahashcrates that make Rust CLIs a joy to build.
