NEXORA
← LabsResearch

Log anomaly detection: a statistical baseline

2026-09-23

Sentinel’s job is to take ten thousand log lines and hand back the ten worth reading. The obvious approach — a trained model — needs training data nobody has for a brand-new product, and an inference cost on every request. Here’s the baseline that ships instead: two techniques, both explainable, both free to run.

1. Keyword rules

Some lines are noteworthy regardless of how often they repeat. fatal, panic and out of memory are always worth a look, even if the same fatal error happens on every line. Three severity tiers — fatal, error, reliability — catch these before frequency ever gets consulted.

2. Frequency-based outliers

Most of what’s actually interesting isn’t a known keyword — it’s a line that just doesn’t look like the others. The approach:

  1. Normalize each line’s “shape” — replace timestamps, IPs, UUIDs and numbers with placeholders, so request completed in 42ms and request completed in 51ms collapse to the same bucket instead of counting as two different lines.
  2. Count each shape’s frequency across the whole sample.
  3. Flag shapes below a rarity threshold — 2% of the sample, so it scales with volume instead of using a fixed count that’s meaningless on both a 50-line and a 50,000-line sample.
function normalizeShape(line: string): string {
  return line
    .replace(UUID_PATTERN, "<uuid>")
    .replace(IP_PATTERN, "<ip>")
    .replace(TIMESTAMP_PATTERN, "<timestamp>")
    .replace(/\b\d+\b/g, "<n>")
    .trim();
}

What this catches, and what it doesn’t

It catches: known-bad keywords regardless of volume, and any line shape that’s genuinely rare in the sample — a one-off stack trace, an unusual diagnostic dump, a request pattern that only happened once. It doesn’t catch: an anomaly that happens to share a shape with common traffic (a slow request logged in exactly the same format as a fast one), or anything that needs semantic understanding of what the log actually means. That gap is exactly where a real model earns its cost later — this baseline is the floor, not the ceiling.

The real, unit-tested implementation is apps/sentinel/src/lib/analyze.ts in the NEXORA monorepo — this write-up describes what actually ships, not a simplified version of it.