Filter records on a route
Every route has an input filter: an OTTL boolean expression that decides which incoming records the route accepts. This page is the practical guide to writing one — targeting a specific source, scoping to a signal type, matching on content, and reasoning about which route wins when several could match.
The model in one paragraph
Section titled “The model in one paragraph”Each source on a collector fans out to every route deployed there. A route’s
input filter selects the records it accepts; matched records run through the
route’s pipeline and go to its destinations. An empty filter — or the literal
true — is a catch-all that accepts everything. You set it on the
Collector (or Group) detail page → Routing tab → the route’s
input-filter field.
Priority and is-final decide who wins
Section titled “Priority and is-final decide who wins”Routes evaluate in priority order — lower numbers first (default 100).
What happens on a match depends on is-final:
- Final (the default): the first matching route consumes the record; lower-priority routes never see it. This is classic first-match-wins.
- Not final: the record runs through this route and continues down the chain, so more than one route can act on it — the “also copy to a backup destination” pattern.
On the topology canvas, each route shows a badge per source indicating whether it statically matches that source: in (accepts), out (excluded), or ? when the match can’t be resolved without seeing live data. Only filters written against the injected source attributes (below) resolve to in/out; content and signal filters show ? — they still work at runtime, the canvas just can’t predict them.
Filter by source
Section titled “Filter by source”Every source stamps two resource attributes onto its records at the receiver, so you can target a source by name or by its stable id:
# Exactly one source, by name
resource.attributes["linkmesh.source.name"] == "nginx-access"
# Everything except one source
resource.attributes["linkmesh.source.name"] != "debug-firehose"
# Sources whose name matches a pattern (regex)
IsMatch(resource.attributes["linkmesh.source.name"], "prod-.*")
# By stable id — survives a source rename
resource.attributes["linkmesh.source.id"] == "6a2efc8073848d5115d32d5d"
Filter by signal type
Section titled “Filter by signal type”There is no signal == "logs" field — signal scoping is structural, not
something you write in the filter expression. A route processes a source’s
logs, metrics, or traces only where the referenced pipeline’s signal types
overlap the source’s signals.
To make a route logs-only (or metrics-only / traces-only), point it at a pipeline whose signal types are scoped to that signal. If a route’s pipeline can’t process a signal the source emits, the canvas shows a signal-mismatch indicator on that edge — the record simply isn’t routed for that signal.
Filter by content — a cookbook
Section titled “Filter by content — a cookbook”Each entry is a want → expression. The fields available are body,
severity_number (logs only), attributes["key"] (record/span/datapoint
attributes), and resource.attributes["key"] (resource-level attributes).
# Errors and above only (logs) — 17 is ERROR in OTel severity numbers
severity_number >= 17
# Body contains a substring (regex)
IsMatch(body, ".*timeout.*")
# One service
resource.attributes["service.name"] == "checkout"
# Records that carry a given attribute at all
attributes["http.route"] != nil
# Numeric threshold — only on numeric attributes
attributes["http.status_code"] >= 500
# Combine conditions (AND)
resource.attributes["service.name"] == "checkout" and severity_number >= 17
# Either/or (OR)
IsMatch(body, "ERROR") or IsMatch(body, "FATAL")
Field & operator reference
Section titled “Field & operator reference”| Field | Meaning | Signals |
|---|---|---|
body |
The log record body | logs |
severity_number |
OTel severity (ERROR = 17, WARN = 13, INFO = 9) | logs |
attributes["key"] |
Record / span / datapoint attribute | all |
resource.attributes["key"] |
Resource-level attribute | all |
| Operator / function | Use |
|---|---|
== != |
Equality / inequality |
> < >= <= |
Numeric comparison |
IsMatch(field, "regex") |
Regex test against a string field |
!= nil == nil |
Attribute exists / is absent |
and or not (...) |
Combine / negate conditions |
Validate before you ship
Section titled “Validate before you ship”Don’t guess. Open the pipeline attached to the route and use the dry-run panel to paste sample records and confirm the expression keeps exactly what you intend — especially for content and numeric filters, where a type mismatch silently fails to match. Then watch the route’s throughput on the canvas after saving to confirm live records flow as predicted.
Worked example — first match wins
Section titled “Worked example — first match wins”Two sources land on a collector: nginx-access (chatty) and everything else.
You want nginx access logs handled by a dedicated pipeline and the rest by a
general one.
-
Route A — priority 10, final. Input filter:
resource.attributes["linkmesh.source.name"] == "nginx-access"Pipeline: your nginx-tuned recipe. Because it’s final and lowest-numbered, it grabs every
nginx-accessrecord first; nothing downstream sees them. -
Route B — priority 20, final. Input filter
true(catch-all). It receives everything Route A didn’t consume — i.e. all non-nginx records.
On the canvas, Route A shows in for nginx-access and out for the
other source; Route B shows in for both (it’s a catch-all), but at runtime
only the leftovers reach it because Route A is final.
Now fan out instead of consume: flip Route A to not final. Its records
still flow through Route A and continue to Route B, so nginx-access logs are
processed by both pipelines — the “process normally, plus copy everything to a
backup destination” pattern. The badges don’t change; the difference is purely
whether Route A consumes or passes the record on.
Related
Section titled “Related”- Route — the route entity: priority, is-final, pipeline, destinations.
- Source — where
linkmesh.source.name/.idcome from. - Drop noisy logs — dropping records inside a pipeline (opposite match semantics to a route filter).
- Build your first pipeline — the pipeline a route hands matched records to.