When something's off, ask your logs. Local-first log search for developers and AI coding agents — zero setup, no daemon, nothing leaves your machine.
Your app writes logs; somethingsoff makes them instantly searchable. Read commands transparently discover your log files, index only the new bytes, and answer in milliseconds.
cd your-project # has a ./logs/ dir? that's the whole setup
somethingsoff errors --last 1hBuilt for the debugging loop — especially the one your AI coding agent runs: ask many small questions ("is this error new?", "did my fix work?", "show me everything for request X") and get fresh, structured, token-efficient answers every time.
# Prebuilt binary (macOS arm64/x64, Linux x64/arm64, Windows x64)
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/aarmanroy/somethingsoff/releases/latest/download/somethingsoff-installer.sh | sh
# Or via cargo
cargo binstall somethingsoff # prebuilt, seconds
cargo install somethingsoff # from sourceMake Claude reach for your logs the moment a command fails. This repo is a plugin marketplace — two commands inside Claude Code:
/plugin marketplace add aarmanroy/somethingsoff
/plugin install somethingsoff@somethingsoff
The plugin ships two things:
-
The
/somethingsoffskill — teaches Claude the CLI, the output contract, and when to check logs first ("something's off with the app" is literally in its trigger vocabulary). Type/somethingsoffto invoke it explicitly, or let Claude reach for it on its own. -
Two hooks — both query your index in-process and hand Claude the actual evidence, not a suggestion to go look for it.
- On failure: when a Bash command fails, the top error group is injected into Claude's context:
"Payment gateway gw-<num> timeout after <num>ms" (6×, last seen 09:35). Evidence at the exact moment debugging starts. - Before a raw read: when Claude reaches for
cat logs/app.log(ortail,grep, …), the command is intercepted and the indexed answer comes back instead — the query already ran. Your context window never sees the raw file.
Both are silent when there's nothing useful to say, rate-limited, and neither runs a first-time ingest on its own. A denied read always runs if Claude repeats it, so the raw file is never out of reach.
- On failure: when a Bash command fails, the top error group is injected into Claude's context:
No plugin? somethingsoff claude install writes the same skill to .claude/skills/ (or --global) — offline, no marketplace. Pick one path, not both.
Option 1 — log files. Drop (or point your app's file logger at) ./logs/*.log|json|jsonl, then just query:
somethingsoff search --level error --last 24h
somethingsoff errors --last 1h # grouped error analysis
somethingsoff get --request-id req-123 # trace one request
somethingsoff stats --by-level --by-routeOption 2 — pipe anything. Works with any language, zero instrumentation. Your terminal still shows the output:
npm run dev 2>&1 | somethingsoff tap --source web
cargo run 2>&1 | somethingsoff tap --source backend
python app.py 2>&1 | somethingsoff tap- Auto-ingest-on-query: every read command checks your sources first (a stat per file — ~10ms when nothing changed), tails only new bytes into a Tantivy full-text index, then answers. No
ingest/rebuildstep, no watcher required. - Formats auto-detected per line: JSON, logfmt, Python logging, syslog (RFC3164/5424), Apache/Nginx, Go logrus, Log4j. Mixed formats in one file are fine.
- Lossless — nothing is dropped: unknown JSON fields (and camelCase aliases like
requestId,msg,durationMs) map into the schema or land in a searchableattributesobject. Any line that matches no structured format — plain dev-server, build, or test output — is still captured as a raw text entry (ANSI stripped, level sniffed), and multiline stack traces / compiler diagnostics coalesce into single events. - Error intelligence:
errorsgroups near-duplicate failures by masked template —Connection timeout to db-<num> after <num>ms: 438×instead of 438 lines. - State is disposable: entries are deduplicated by content hash, so a lost cursor file or a re-ingested file can never produce duplicates or wrong results.
- Private by default: emails, tokens, cards, and secrets are redacted at ingest (disable with
--no-redact). Everything lives in./.somethingsoff/(index, cursors, tap journals, optional config).
| Command | What it does |
|---|---|
search |
Filter + full-text query (-q, --level, --last, --status 500-599, --slow-above 1000, --context 5, ...) |
errors |
Error groups by masked template: count, affected users, first/last seen |
get |
Point lookup by LOG_ID, --request-id, or --user-id |
stats |
Volumes --by-level / --by-route / --by-user / --by-format |
schema |
Profile of what's actually in your logs: fields, types, cardinality, samples |
tap |
Pipe a process through: echo + capture + index |
watch |
Optional continuous ingest (lower latency than on-demand sync) |
ingest |
One-shot ingest of a file outside ./logs/ (stays auto-synced after) |
health |
Index, sources, disk, and lock checks |
index |
rebuild / status / clean (retention) |
learn |
Suggest a regex for an unrecognized log format |
claude |
install the Agent Skill · hook (plugin plumbing) |
Global flags: --quiet (silence stderr diagnostics) · --format json|jsonl · --no-sync (skip auto-ingest) · --no-redact.
Every command prints exactly one JSON envelope on stdout; diagnostics go to stderr. Failures carry a machine-readable code and a hint naming the next command to run. Exit codes: 0 ok · 2 ok but zero results · 3 usage/config · 4 index locked/corrupt · 5 permission/IO · 6 parse · 1 internal.
The full specification lives in docs/CONTRACT.md — it's the file agents and tests both rely on.
This CLI is designed to be driven by coding agents:
- One envelope shape everywhere; branch on exit codes; recover via
error.hint. - Output is bounded by default (
-n,--last); trim further with--fields timestamp,level,message --compact, or stream bare records with--format jsonl. schemais self-describing discovery: run it first to learn what fields exist.stats --by-formatis trust calibration: it shows how much of the index was structurally parsed vs captured raw, so an agent knows when field filters see everything and when to fall back to full-text.- Non-interactive by design: no prompts without a TTY (destructive ops require
--force), reads never block on locks (stale read +sync_deferrednotice instead).
Zero-config covers most projects. For anything else, ./.somethingsoff/config.toml:
[general]
retention_days = 30 # `index clean` deletes older entries
[log_sources] # explicit sources beyond ./logs/
backend = "/var/log/myapp/backend.log"
[sync]
auto = true # auto-ingest before reads
poll_interval_secs = 2 # `watch` polling intervalSet SOMETHINGSOFF_BASE_DIR to relocate the state directory (defaults to ./.somethingsoff).
cargo test # unit + integration suites
cargo clippy --all-targets -- -D warnings
cargo bench # criterion benchmarksMIT