LogQL reference

The LogQL subset Rune supports — parsed once into a query AST, executed natively by whichever backend you run.

Rune speaks a deliberate subset of LogQL. The ObserveService parses a query once into an AST; each backend then executes that AST natively — an in-process scan (embedded), re-rendered LogQL (Loki), or SQL (ClickHouse). Constructs outside the subset fail at parse time; constructs a specific backend can't honor fail with a clear "unsupported" error — never silently-wrong results.

Stream selectors

Every query starts with a selector over the enriched labels (namespace, service, instance, node, level, stream, plus your service labels):

{service="api"}                          # exact match
{service!="api"}                         # negated exact
{service=~"api|worker"}                  # regex
{namespace=~".+", level!~"debug|trace"}  # combined; regex-negated

All four matchers: =, !=, =~, !~.

Line filters

Chained after the selector, applied in order:

{service="api"} |= "timeout"             # keep lines containing
{service="api"} != "healthz"             # drop lines containing
{service="api"} |~ "5\\d\\d"             # keep lines matching regex
{service="api"} !~ "(?i)debug"           # drop lines matching regex

Parser stages & label filters

Extract structured fields from log lines, then filter on them:

{service="api"} | logfmt | status="500"
{service="api"} | json | dur > 250
{service="api"} | logfmt | region=~"eu-.*" | dur >= 100
  • | logfmt — extracts key=value pairs.
  • | json — extracts top-level scalar fields; nested objects and arrays are skipped.
  • Label filters after a parser: string ops =, !=, =~, !~; numeric ops >, >=, <, <=, ==.

:::note Parser stages run on embedded and Loki (Core tier). The ClickHouse backend does not lower them to SQL — use its raw SQL mode (JSONExtractString & friends) instead. :::

Range aggregations

count_over_time({service="api"}[5m])             # line count per bucket
rate({service="api"} |= "error" [1m])            # per-second rate
bytes_over_time({namespace="default"}[1h])       # log volume in bytes

Vector aggregation

sum by (...) is the supported grouping:

sum by (service) (count_over_time({namespace="default"}[5m]))
sum by (node) (bytes_over_time({namespace=~".+"}[1h]))

Advanced tier (ClickHouse only)

quantile_over_time(dur, 0.95) ({service="api"})  # p95 of a parsed numeric field

— plus raw SQL mode in the dashboard, the escape hatch for everything below.

Deliberately not supported

ConstructStatus
unwrap expressionsnot supported — quantile_over_time(field, q) covers the main use, on ClickHouse
label_format / relabelingnot supported
Cross-stream joins / binary ops between queriesnot supported — SQL mode on ClickHouse
avg / min / max / topk vector aggregationsnot supported — sum by only; SQL mode for the rest
Nested JSON extraction (`json a.b.c`)

The subset is a floor, not a ceiling — it's chosen so every query behaves identically on every backend that accepts it, and so the alerting evaluator (which wraps your selector in count_over_time) can run anywhere.

Where queries run

SurfaceNotes
Log ExplorerFull subset, histogram + facets.
Saved viewsAny Core-tier query; validated at save time.
Alert rulesYou write the selector (with line/label filters); the alerter wraps it in count_over_time({...}[window]) itself.
SourcesBuilt on Core-tier queries, so it works on every backend.

See also