LOG-WHISPERER(1)

NAME
log-whisperer - CLI log pattern analysis and anomaly detection tool

SYNOPSIS
log-whisperer [--file PATH] [--docker CONTAINER] [--compose SERVICE] [--compose-all] [--service UNIT]
    [--since WINDOW] [--lines N] [--show-new] [--show-samples]
    [--min-severity LEVEL] [--json]
    [--state-db PATH] [--baseline-state PATH]
    [--baseline-learn DURATION] [--reset]
    [--notify-ntfy-topic TOPIC] [--notify-telegram-token TOKEN]
    [--notify-email-host HOST] ...

DESCRIPTION
log-whisperer reads log lines from a configured source (Docker container, Docker Compose service, systemd journal unit, or plain text file), normalizes variable data out of each line (IP addresses, UUIDs, timestamps, file paths, numbers), clusters the normalized lines into deduplicated patterns, and compares them against a persistent database of previously seen patterns.

Patterns that have never been seen before are tagged [NEW] and can optionally trigger alert notifications via ntfy, Telegram, or email. Previously seen patterns are tagged [seen].

The tool is designed to be run periodically (e.g. via cron(8)) to detect anomalous log activity — new error patterns, unexpected warnings, or any log message structure that hasn't appeared before.

PIPELINE
The analysis pipeline consists of four stages:

    1.  Read — Fetch raw log lines from the configured source, limited to the last --lines entries.

    2.  Normalize — Strip timestamp prefixes and replace variable tokens (IPs, UUIDs, hashes, MAC addresses, hex literals, file paths, numbers) with stable placeholders (<IP>, <UUID>, <HASH>, <MAC>, <HEX>, <PATH>, <N>).

    3.  Cluster — Group identical normalized patterns, counting occurrences and classifying severity (ERROR, WARN, INFO) by keyword matching.

    4.  Diff — Compare the current window's patterns against the persistent JSON-lines database to determine which are [NEW] and which are [seen]. Update the database with new counts and timestamps.

OPTIONS

  Source Selection
Exactly one source must be specified (unless --reset is used).

--file PATH
    Read the last --lines lines from the plain text file at PATH. No external tools are required.

--docker CONTAINER
    Fetch logs from a Docker container via docker logs. The --since value is passed directly to Docker.

--compose SERVICE
    Fetch logs from a single Docker Compose service via docker compose logs. Colour output is disabled automatically.

--compose-all
    Fetch logs from all Docker Compose services in the current project.

--service UNIT
    Fetch logs from a systemd journal unit via journalctl(1). Uses -o cat for bare message output without journal metadata.

  Display Options

--show-samples
    Print one raw (un-normalized) sample log line below each pattern in the text report.

--show-new
    Only display patterns that have never been seen before. Previously seen patterns are still recorded in the database but omitted from the report output.

--min-severity LEVEL
    Filter the report to show only patterns at or above the given severity. Valid values: INFO (default), WARN, ERROR. Patterns below the threshold are still recorded in the database.

--json
    Output the full report as a JSON object to stdout. The database is still updated as normal.

  Window Options

--since WINDOW
    Time window for log retrieval. Passed directly to docker logs or journalctl(1). For file sources, this value is recorded in the report metadata but all lines in the file are read regardless.
    Default: 1h. Examples: 10m, 1h, today.

--lines N
    Maximum number of log lines to process (taken from the tail of the output).
    Default: 5000.

  State Management

--state-db PATH
    Path to the pattern database file (JSON-lines format).
    Default: ~/.local/state/logwhisperer/patterns.db.

--baseline-state PATH
    Path to the baseline learning state file (JSON).
    Default: ~/.local/state/logwhisperer/baseline.json.

--baseline-learn DURATION
    Activate baseline learning mode for DURATION. During this period, new patterns are recorded in the database but no alert notifications are dispatched.
    Accepted formats: 30s, 10m, 2h, 1d.

--reset
    Delete the pattern database and baseline state files, then exit. A source flag must still be provided (use --file /dev/null if no real source is needed).

  Notification Options
Notifications are sent only when new patterns are detected outside of baseline learning mode. All settings can also be configured via environment variables (see ENVIRONMENT).

--notify-ntfy-topic TOPIC
    Publish alerts to the given ntfy(1) topic.

--notify-ntfy-server URL
    ntfy server URL.
    Default: https://ntfy.sh.

--notify-telegram-token TOKEN
    Telegram Bot API token.

--notify-telegram-chat-id CHAT_ID
    Telegram chat ID to send alerts to.

--notify-email-host HOST
    SMTP server hostname.

--notify-email-port PORT
    SMTP server port.
    Default: 587.

--notify-email-user USERNAME
    SMTP authentication username.

--notify-email-pass PASSWORD
    SMTP authentication password.

--notify-email-from ADDRESS
    Sender email address.

--notify-email-to ADDRESS
    Recipient email address.

--notify-email-no-tls
    Disable STARTTLS when connecting to the SMTP server. Useful for local or internal mail relays.

NORMALISATION
The normalizer applies substitutions in a specific order to prevent partial matches. UUIDs and long hex hashes are replaced before the generic number pattern.

Data Type | Example | Placeholder
ISO timestamp prefix | 2024-03-15T10:23:45 | (stripped)
Syslog prefix | Mar 15 10:23:45 host app: | (stripped)
UUID | 550e8400-e29b-41d4-... | <UUID>
Hash (32-64 hex) | d41d8cd98f00b204... | <HASH>
IPv4 address | 192.168.1.42 | <IP>
MAC address | aa:bb:cc:dd:ee:ff | <MAC>
Hex literal | 0x1A2F | <HEX>
File path | /usr/local/bin/app | <PATH>
Number | 42, 3000 | <N>

After all substitutions, consecutive whitespace is collapsed to a single space and the result is trimmed. The normalized string is then hashed with SHA-1 to produce a stable pattern identifier.

SEVERITY CLASSIFICATION
Each pattern is classified by scanning (case-insensitively) for keywords:

ERROR: error, fatal, exception, traceback, panic, segfault, failed, failure, critical.

WARN: warn, warning, timeout, timed out, retry, throttle, rate limit, deprecated, slow, unavailable.

INFO: Everything else.

ERROR takes precedence over WARN.

DATABASE FORMAT
The pattern database uses a JSON-lines format — one JSON object per line. Each entry contains:

    {
      "h": "<sha1-hash>",
      "first_seen": <epoch>,
      "last_seen": <epoch>,
      "total_seen": <count>,
      "severity": "ERROR|WARN|INFO",
      "pattern": "<normalized pattern>",
      "sample": "<raw log line>"
    }

The database file is protected by fcntl(2) file locks: shared locks (LOCK_SH) for reads and exclusive locks (LOCK_EX) for writes. This prevents corruption when concurrent instances (e.g. overlapping cron jobs) access the file simultaneously.

The file can be inspected with standard tools:

    # Pretty-print all entries
    jq . ~/.local/state/logwhisperer/patterns.db
    # Count patterns
    wc -l ~/.local/state/logwhisperer/patterns.db
    # Find ERROR patterns
    grep '"severity":"ERROR"' ~/.local/state/logwhisperer/patterns.db | jq .

ENVIRONMENT

XDG_STATE_HOME
    Base directory for state files. Default: ~/.local/state.

LOGWHISPERER_NTFY_TOPIC
    Default value for --notify-ntfy-topic.

LOGWHISPERER_NTFY_SERVER
    Default value for --notify-ntfy-server.

LOGWHISPERER_TELEGRAM_TOKEN
    Default value for --notify-telegram-token.

LOGWHISPERER_TELEGRAM_CHAT_ID
    Default value for --notify-telegram-chat-id.

LOGWHISPERER_SMTP_HOST
    Default value for --notify-email-host.

LOGWHISPERER_SMTP_PORT
    Default value for --notify-email-port.

LOGWHISPERER_SMTP_USER
    Default value for --notify-email-user.

LOGWHISPERER_SMTP_PASS
    Default value for --notify-email-pass.

LOGWHISPERER_EMAIL_FROM
    Default value for --notify-email-from.

LOGWHISPERER_EMAIL_TO
    Default value for --notify-email-to.

EXIT STATUS

0   Success.

2   Invalid arguments, missing source binary, file not found, or invalid duration format.

FILES

~/.local/state/logwhisperer/patterns.db
    Default pattern database (JSON-lines).

~/.local/state/logwhisperer/baseline.json
    Default baseline learning state (JSON).

EXAMPLES

  Basic Usage

Analyse a log file and show sample lines:

    $ log-whisperer --file /var/log/app.log --show-samples

Show only never-before-seen patterns:

    $ log-whisperer --file /var/log/app.log --show-new

Filter to errors only:

    $ log-whisperer --file /var/log/app.log --min-severity ERROR

  Docker & Compose

Analyse a Docker container's last 30 minutes of logs:

    $ log-whisperer --docker my-api --since 30m

Analyse all Compose services:

    $ log-whisperer --compose-all --since 1h --show-new

  systemd Journal

Analyse today's nginx logs:

    $ log-whisperer --service nginx --since today

  Baseline Learning

Learn patterns for 24 hours before alerting:

    $ log-whisperer --file /var/log/app.log --baseline-learn 24h

  JSON Output

Pipe JSON output to jq for further processing:

    $ log-whisperer --file app.log --json | jq '.items[] | select(.tag == "NEW")'

  Cron Integration

Check every 5 minutes and alert via ntfy:

    */5 * * * * log-whisperer --file /var/log/app.log --since 5m \
        --notify-ntfy-topic my-alerts 2>>/var/log/lw-errors.log

  Multiple Services with Separate Databases

    $ log-whisperer --docker api    --since 5m --state-db /var/lib/lw/api.db
    $ log-whisperer --docker worker --since 5m --state-db /var/lib/lw/worker.db
    $ log-whisperer --service nginx --since 5m --state-db /var/lib/lw/nginx.db

  CI Smoke Test

Fail if new error patterns are detected:

    $ log-whisperer --file test.log --json \
        | jq -e '[.items[] | select(.tag=="NEW" and .severity=="ERROR")] | length == 0' \
        || { echo "New error patterns!"; exit 1; }

  Notifications

Alert via ntfy and Telegram simultaneously:

    $ log-whisperer --file /var/log/app.log \
        --notify-ntfy-topic my-alerts \
        --notify-telegram-token "123456:ABC-DEF..." \
        --notify-telegram-chat-id "-100123456789"

Alert via email:

    $ log-whisperer --file /var/log/app.log \
        --notify-email-host smtp.company.com \
        --notify-email-from alerts@company.com \
        --notify-email-to oncall@company.com

SEE ALSO
docker-logs(1), journalctl(1), jq(1), cron(8), fcntl(2)

AUTHORS
Log Whisperer was created as a lightweight log anomaly detection tool.

BUGS
Report bugs at the project's issue tracker.