logcrux / usage


usage

$ logcrux /var/log/syslog

Or pipe a stream straight in:

$ tail -f /var/log/nginx/error.log | logcrux
$ journalctl -u nginx | logcrux

how you invoke it

This page writes logcrux ... everywhere, which assumes the executable is on your PATH. Whether it is depends on how you installed it:

installed withinvoke as
uv tool install or pipxlogcrux ..., already on PATH
pip into a venv~/.venvs/logcrux/bin/logcrux ..., or activate the venv first
no install, one-off runuvx logcrux ...

Everything else on this page, flags, stdin, exit codes, is identical regardless of which form you use; substitute your invocation for the literal logcrux. See install for the full picture, including hosts where logcrux can't be installed at all, analyze those from outside instead: docker logs oldcontainer | logcrux, run from a host that does have it installed.

flags

--last DURATIONonly analyze events from the trailing window, e.g. --last 1h, --last 30m, --last 2d
--threshold FLOATminimum confidence to trust a classified category (default 0.35)
--format NAMEforce a parser instead of auto-detecting, e.g. --format nginx. Common aliases work: nginx, apache, k8s, dmesg, ssh, dnf, journal
--no-baselineskip comparison against historical baselines
--jsonmachine-readable output instead of the terminal report
--config PATHuse a specific config file (see config)
--verbosedebug logging
--version / -Vprint the installed version
--install-completioninstall shell completion
--show-completionprint the completion script
--helpfull flag reference from the CLI itself

examples

$ logcrux /var/log/syslog                    # analyze a file
$ logcrux /var/log/syslog --last 1h          # only the last hour
$ logcrux /var/log/syslog --threshold 0.7    # raise confidence threshold
$ logcrux /var/log/syslog --format nginx     # force a parser
$ logcrux /var/log/syslog --no-baseline      # skip baseline comparison
$ logcrux /var/log/syslog --json             # machine-readable output
$ logcrux /var/log/syslog --config my.yaml   # custom config
$ logcrux /var/log/syslog --verbose          # debug logging

reading the report

An incident:

╭───────────────────────   WARNING    Auth Brute Force ────────────────────────╮
│   ● Error burst detected  (25 events in 5s window)                           │
│   Confidence: 96%                                                            │
│                                                                              │
│   Remediation: Investigate the root service throwing errors. Check upstream  │
│ connectivity and review recent config changes.                               │
╰──────────────────────────────────────────────────────────────────────────────╯
Analyzed 117 events in 0.1s  │  1 incident(s)  │  29 line(s) unparsed

Header: severity (WARNING / CRITICAL) and the incident category. Body: what clustered and over what window, the classifier's confidence, and a suggested first action. Footer: events analyzed, elapsed time, incident count, and any unparsed lines.

A clean result:

╭──────────────────────   CLEAN    No incidents detected ──────────────────────╮
│   Confidence: 100%                                                           │
╰──────────────────────────────────────────────────────────────────────────────╯
Analyzed 13 events in 0.0s  │  0 incident(s)

json output

$ logcrux /var/log/postgres.log --json
{
  "analysis_id": "11962dbc-1857-4a75-9a2a-48b0fd5f4bf1",
  "level": "WARNING",
  "title": "Auth Brute Force",
  "findings": [
    { "headline": "Error burst detected", "detail": "25 events in 5s window" }
  ],
  "confidence": 0.957,
  "category": "auth_brute_force",
  "remediation": "Investigate the root service throwing errors. Check upstream connectivity and review recent config changes.",
  "log_path": "/var/log/postgres.log",
  "parser_format": "postgresql",
  "analyzed_at": "2024-01-15T10:02:38Z",
  "parsed_count": 117,
  "skipped_count": 29,
  "elapsed_seconds": 0.084
}

A clean run reports "level": "CLEAN", "findings": [], "confidence": 1.0, "category": "unknown", and "remediation": null. parser_format shows which parser was chosen; skipped_count mirrors the unparsed-line count in the footer, useful for spotting a wrong-format feed.

incident categories

categorytriggered byexit
auth_brute_forceSSH/SASL/DB auth-failure clusters, firewall port-22 blocks3
http_overload5xx spikes, 502/503, upstream failures3
network_issuetimeouts, replication lag, CNI failures3
oomOOM-killed processes, memory allocation failures4
service_crashbackend down, FATAL/PANIC, crash-loop restarts4
disk_full"no space left on device"4
config_errorinvalid parameters, malformed settings4

Severity follows the category, not just confidence: a high-confidence auth_brute_force is still exit 3, while oom and service_crash are always exit 4 once clustered. Reaching that CRITICAL state also requires the failure to actually be logged at error severity and in a parseable format, not just to have happened. A database's own LOG-level recovery trace, or a crash backtrace no parser recognizes, won't trip it: feed logcrux the stream where the failure is recorded at error severity (kernel/syslog, journalctl, orchestrator events).

exit codes

Script-friendly by design:

0clean, nothing found
3info or warning-level finding
4critical finding

rotated and compressed logs

Reading a file transparently decompresses it if it's gzip or bz2. Detection is by magic number, not by trusting the .gz/.bz2 extension, so a rotated syslog.1.gz just works. A truncated or partially-corrupt archive (say, a log rotated mid-write) is salvaged up to the point it can still decompress, with a warning, rather than aborting outright.

data loss is never silent

If a line can't be parsed, it's counted and reported in the footer ("N line(s) unparsed"), never dropped without a trace. If the auto-detected parser only covers a minority of the file, logcrux re-parses with a generic fallback that never drops a non-blank line, and keeps whichever result recovered more events. --format disables this safety net if you want to force a specific parser regardless.

feeding logs correctly

logcrux is only as good as the stream you hand it.

One log format per invocation. docker logs merges stdout and stderr. If a service writes an access log to stdout and an error log to stderr, the mixed stream defeats format auto-detection. Split them:

$ docker logs myapp 2>/dev/null > access.log   # stdout only
$ logcrux access.log

Strip container syslog priority prefixes. Containers logging via syslog can prepend a <PRI>-style tag (e.g. <134>) that file-oriented parsers reject:

$ docker logs myapp 2>&1 | sed -E 's/^<[0-9]+>//' | logcrux

Strip ANSI color codes. Some services colorize their own log output; the escape sequences disrupt parsing:

$ docker logs myapp 2>&1 | sed -E 's/\x1b\[[0-9;]*m//g' | logcrux

Turn on the severity you want detected. Some services only log auth failures at a raised verbosity level (MySQL's log_error_verbosity, for example). If the source doesn't log an event at error/warning severity, there's nothing to cluster.

Parsing and detection are separate: even when a dedicated parser matches only a minority of lines, logcrux falls back to a generic parser and can still catch incidents by content. --verbose shows the fallback decision.