Skip to content

Repository files navigation

LogLynx

Log analysis and anomaly detection for application, syslog and access logs. LogLynx reads a log file and tells you what happened in it: which errors repeat, when they clustered, which time windows deviate from the file's own baseline, and what all of it adds up to as a single health score.

No dependencies. Terminal output is in Turkish.

ci PyPI python coverage tests license

LogLynx analysing a log file

Install

pip install loglynx
loglynx app.log

python -m loglynx does the same thing when the scripts directory is not on your PATH, and from a checkout python main.py app.log works with nothing installed at all.

Usage

loglynx app.log                                 analyse one file
loglynx logs/                                   a directory, .gz included
cat app.log | loglynx -                         stdin

loglynx app.log --since 18:00 --until 20:00     narrow the window
loglynx app.log --grep timeout --level ERROR    narrow the content

loglynx app.log --watch                         follow the file live
loglynx app.log --save                          record the run
loglynx --compare                               diff the last two runs
loglynx --serve                                 dashboard on :8000

loglynx app.log --html report.html              shareable report
loglynx app.log --json                          machine-readable output
loglynx app.log --notify "$SLACK_WEBHOOK"       post the result
loglynx app.log --explain                       LLM commentary

Every mode is documented in docs/GUIDE.md. --help lists the full set.

What it looks like in practice

A database outage. You are handed yesterday's log and told "something happened around six".

loglynx app.log --quiet

The score comes back 44.8/100 (D), and the findings name the incident without you reading a single line: 229 errors in the 18:00 hour against a baseline of 4, one signature accounting for 72% of them, the db module at a 59% error rate, and — the part that is easy to miss by eye — a twelve minute stretch at 22:47 with no log output at all. Without --quiet it also prints the cluster: DatabaseConnectionError, 275 occurrences, 18:15:00 to 19:19:57, with a rule-based guess at the cause.

A deploy that made things worse. Record a run before and after:

loglynx app.log --save --label "before"
loglynx app.log --save --label "after"
Sağlık skoru     87.9 → 44.8  ▼ (-43.1)   KÖTÜLEŞME
Yeni ortaya çıkan hatalar:
  + CircuitBreakerOpenError (20)

That is not a guess: error signatures are stored per run, so "new" means literally absent from the previous set. As a CI gate: loglynx app.log --fail-on health --min-health 80.

A spike of 5xx. Access logs have no level field, so the HTTP status supplies one — 5xx becomes ERROR, 4xx WARNING:

loglynx /var/log/nginx/access.log --since 18:00 --until 20:00 --level ERROR

Anomaly detection

Anomalies are scored against the file's own baseline rather than a fixed threshold, because "too many errors" only means something relative to what that system normally produces.

The default is a modified z-score, 0.6745 * (x - median) / MAD. The obvious alternative, (x - mean) / stdev, has a flaw that matters here: the outlier you are looking for inflates the standard deviation and therefore hides itself. In the sample log there are two error spikes, at 18:00 and 19:00. The classic score only reports the first; the larger spike raises stdev to 49.6 and the second one falls inside the noise. The median and MAD are unaffected by outliers, so both are reported. tests/test_anomalies.py::test_robust_beats_classic_zscore pins this down. The classic method is still available with --method zscore.

Long silences are treated as anomalies too. A stopped service or a dead log agent produces nothing at all, which error counting never catches.

Health score

A single 0-100 number, computed by subtracting penalties from 100. Each factor has a ceiling, so one bad metric cannot flatten the score and make every bad log look identical.

Factor Ceiling Full penalty at
Error rate 35 25% of entries
Critical entries 20 2% of entries
Anomalies 20 4 windows
Bursts 10 3 bursts
Log stream gaps 10 10% of the time span
Unparsed lines 5 nothing parsed

Grades: A ≥ 90, B ≥ 75, C ≥ 60, D ≥ 40, F below. The weights are a judgement call, not a derived truth; WEIGHTS in loglynx/analyzer/health.py is the one place to retune them.

Formats

Name Example
standard 2026-08-22 10:31:02 ERROR [db] Database connection failed
bracket [2026-08-22 10:31:02] [ERROR] [db] Database down
python_logging 2026-08-22 10:31:02,123 - auth - ERROR - Bad token
json {"timestamp":"...","level":"ERROR","message":"..."}
syslog_rfc5424 <11>1 2026-08-22T18:15:00Z web-01 nginx 1234 ID - refused
syslog Aug 22 18:15:00 web-01 nginx[1234]: connection refused
access_log 1.2.3.4 - - [22/Aug/2026:18:15:00 +0300] "GET / HTTP/1.1" 500 12

The format is detected per file, and lines that do not match the dominant format fall back to the other parsers, so mixed files still work. JSON field names follow OpenTelemetry, Elastic ECS or Python conventions; unrecognised keys are kept on the entry. Timestamps may be ISO strings or epoch numbers, and offset-aware values are normalised to UTC so a file mixing both does not break comparisons.

Two formats carry no level, so one is derived — syslog decodes severity from the priority number, access logs from the HTTP status. Adding a format means adding one class with an extract(line) method.

Layout

loglynx/cli.py                  argument parsing and wiring
loglynx/parser/                 formats, timestamps, reading, filtering
loglynx/analyzer/               statistics, patterns, anomalies, health,
                                insights, optional LLM commentary
loglynx/database/               run history and comparison
loglynx/watcher.py              live following and rolling window
loglynx/notify.py               webhook notifications
loglynx/server.py               dashboard HTTP server
loglynx/reports/                console, HTML, JSON, dashboard
tools/                          sample data, log replay, README assets

Everything under loglynx/analyzer is pure: it returns data and never prints, so the same code backs the terminal, the HTML report, the JSON output and the dashboard.

Development

pip install -e ".[dev]"
ruff check .
pytest

306 tests, no network access from any of them. The sample generator (python tools/make_sample_logs.py) produces a day of traffic containing a deliberate database outage between 18:15 and 19:20, which is what the pattern and anomaly tests are written against.

License

MIT

About

Log analysis and anomaly detection for application, syslog and access logs. Zero dependencies.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages