Log pattern recognition
Collapsing thousands of log lines into the handful of shapes they actually have — and the two things that will otherwise make the answer confidently wrong.
What it does
Section titled “What it does”Pattern recognition groups log messages by structure rather than content. Lines that differ only in a timestamp, an id or a number collapse into one pattern — a template with the varying parts masked — so a million lines become a few dozen shapes you can actually read.
It is the fastest way to answer “what is in this index that I have never looked at?”, and to notice a new error shape appearing.
The implementation is a Drain-style tree: messages are tokenized, walked down a
fixed-depth tree and merged when they are similar enough. The defaults that
matter are depth (7 on the agent-side config, 6 server-side), similar (0.4)
and tokenizer (simple; json, smart and a CJK tokenizer also ship).
Two constraints that change the answer
Section titled “Two constraints that change the answer”1. It runs on search2 only
Section titled “1. It runs on search2 only”logcluster exists in the search2 grammar and nowhere else — there is no
LOGCLUSTER token in the search1 parser at all. A pipeline using it must run on
search2:
search2 repo="app_logs" | logcluster summaryIf the engine is not named, the global Default Search Engine setting decides
which one runs, so a saved search or dashboard panel using logcluster can stop
working when that setting changes. Name the engine.
2. It is on for your own indexes, and off for ours
Section titled “2. It is on for your own indexes, and off for ours”Pattern recognition is on by default for every non-internal event index,
including default — the index events land in when a sender names none. A fresh
install ships one target covering all of them, and because that target is a
rule rather than a fixed list, an index you create next month is covered too
without revisiting the setting.
It is off for the product’s own internal indexes (_internal, _alert_event
and friends). Those are sondar’s own logs, not yours; clustering them costs parse
time for no benefit on day one. Switch them on in Settings if you want them.
Enabling later does not backfill, so the default matters more than it looks. Patterns are computed at ingest and written as they arrive — nothing walks history afterwards. Logs that arrive while an index is switched off can never be clustered, even after you switch it on; only what arrives from that point forward is. This is why the feature ships on rather than waiting to be discovered.
A brand-new sourcetype’s first batch is clustered too. Its parser loads when that
batch arrives, and the batch waits for the load — for at most
sondar.logcluster.parser.first_load_wait, 5 seconds by default, counted from the
start of the load and shared by every batch that arrives meanwhile. A template
store slower than that lets the batch through unclustered rather than hold
ingest; the next batch clusters.
Targets are set for named indexes or index groups in Settings, and turning the feature off removes them all. Once off, it stays off — the shipped default is applied once, at install, and never reasserted.
This used to be the sharpest edge in the feature. With it off, the query still returned a result: a single pattern covering 100% of the logs, with an empty template — a confident, plausible, wrong answer. Three corpora with obviously different message shapes all collapsed to the same thing, which is how it was caught.
It now says so instead:
Pattern recognition is not enabled for any index, so no log patterns can be detected. Enable it for an index in Settings; the result below is not a real pattern.
The check is instance-wide — if no index has pattern recognition enabled, no
logcluster query can produce a real pattern, so the warning has no false
positives. With the shipped default in place it should not normally fire; seeing
it means the feature was switched off deliberately. The converse is not covered: with the feature on for index A and a
query against index B, the query returns the empty-template result and no
warning. Enable it for the index you intend to search.
Over the API the targets live at /api/v1/repo_logcluster_target (GET to list,
PUT to set), addressed either by index (repo) or by index group.
The command
Section titled “The command”The mode keyword is required — the grammar is
logcluster (summary | search | trend | compare | alert | analyze) …, so a bare
| logcluster is a parse error, not a default.
| mode | what it returns |
|---|---|
summary | the pattern list — template, count, share |
search | the raw events behind a pattern |
trend | pattern volume over time |
compare | one window against another |
alert | the shape used by a pattern-based alert rule |
analyze | the analysis view |
Options are name=value after the mode. The ones worth knowing:
| option | default | meaning |
|---|---|---|
level | 10 | how many patterns to aim for, 1–10. Lower means fewer, broader patterns; 10 is the most granular and is the default |
limit | 100000 | maximum patterns |
max_events | 100000 | events considered |
span | auto | bucket width for trend |
sort_by | num | order the pattern list |
thin | false | reduced output |
timeshift | — | offset for compare |
by groups patterns by a field, and asc/desc set the direction:
search2 repo="app_logs" | logcluster summary level=6 by service descReading the result
Section titled “Reading the result”A pattern’s template shows the parts that varied, masked: a run of differing
text as *, a timestamp as its format, a number as [min,max]. The columns are
a rank, the log count, its share of the total, a trend sparkline,
the field the pattern was found in (usually _raw), and the template
itself. Above the table: a List / Compare toggle, a Detail slider from
Fewer to More — that is the level option below — a running count of patterns
and logs, and Group by.
Clicking a pattern opens its mask details: the events behind the template, with the most frequent values for each masked position. Clicking a bar in the sparkline narrows to that time slice.
Set a time period. With no period selected the search still runs, but the
trend buckets are spread across the whole epoch — from 1970 to now, in ten
steps — so every pattern’s sparkline collapses to a single bar at the right
edge and the tooltips read like 2004-01-02. That is arithmetically what an
unbounded window means, not a fault in the data; pick a period and the ten
buckets divide it evenly (verified 2 Sep 2026 against a 21,677-event corpus:
a 24-hour window produced ten 2.4-hour buckets).
(Verified against a running instance on 2 Sep 2026, which is what turned up the two notes above. The trend-to-dashboard and trend-to-alert paths described in earlier revisions of this section were not re-confirmed and are not claimed here.)
Tuning
Section titled “Tuning”Defaults are reasonable; reach for these only when the patterns are obviously too coarse or too fine.
similar(0.4) — the merge threshold. Higher splits more.depth— tree depth before templates form.tokenizer—simplesplits on whitespace and punctuation. Usejsonfor structured payloads;smartand the CJK tokenizer also ship.- Masks — regexes whose matches are always treated as variable. The shipped config masks the agent’s own runner/sender/transformer identifiers so they do not fragment every pattern.
- Memory — patterns are capped per node per source type (default 1,000,000). Patterns from different nodes can exceed that total.
These are commented in the rule configuration itself, under
sondar.logcluster.parser.*.
When one pattern becomes many
Section titled “When one pattern becomes many”If a sourcetype shows far more patterns than it has real message shapes — dozens of near-identical templates differing only by a hostname or a service name — this is the one tuning problem worth knowing about.
The parser builds a tree keyed on the leading tokens of each line, and it only
generalizes a token automatically when that token contains a digit. So
web01 sshd: connection closed generalizes, and alpha sshd: connection closed
does not: each distinct alphabetic host opens its own branch. Measured: eight
hosts sending one identical message produce eight patterns; the same corpus
with host0–host7 produces one.
Syslog puts the host and the process at the start of every line, so this is the common case rather than an exotic one.
The quickest fix needs no configuration at all: the Pattern Count slider.
On the search page’s Pattern tab, drag Pattern Count from Big towards
Small. That re-clusters the results at a coarser granularity, and the fragments
merge. Measured on 317 syslog lines from twelve alphabetic hosts: at Big it
reports twelve patterns, one per host; at Small it reports one —
<*> sshd: Accepted publickey for deploy from port <*>, 100% of the logs.
Try that first. It is a query-time dial, so it changes what this search shows and nothing else.
The durable fix is a mask. Where a sourcetype always has the problem, add a user-defined mask to its pattern rule so the variable is generalized at INGEST, permanently and for every future query:
[pattern](alpha|bravo|charlie|delta)[/pattern]Anything the mask matches is treated as a variable before the tree is walked, so
one pattern comes back instead of one per host. A regex covering your naming
convention ([pattern](node-[a-z]+)[/pattern]) works the same way.
Timestamps need no help — both 2026-08-18T06:00:00Z and the digit-free
Aug 18 06:00:00 already collapse.
Known gaps
Section titled “Known gaps”- Per-index scoping of the warning. As above, the “not enabled” warning is instance-wide; a query against an index that specifically lacks a target gets the empty-template result with nothing said. Narrowing it needs the query’s indexes at a layer that does not currently have them.
levelis not a pattern count. It is a granularity dial, 1–10, not a pattern count.
See also
Section titled “See also”- interactive-search.md — the query bar, the two engines and why to name one
- search-sonql-reference.md — the command reference, including where the engines disagree
- indexes-and-field-extraction.md — indexes and source types, which pattern recognition is scoped by