Loki and LogQL basics

Grafana · Practical · 12 min

Loki is the logs half of the Grafana stack, and LogQL is how you go from "something is wrong" to the lines that say what.

Loki stores log lines in streams identified by a small set of labels (app, namespace, pod, level), indexing only those labels, not the text. That is why it is cheap and why high-cardinality values such as user or request IDs must stay inside the line (or in structured metadata), not become labels.

A LogQL query is a pipeline:

  • Stream selector: {app="api", env="prod"}, with =, !=, =~, !~ like PromQL. At least one non-empty matcher is required.
  • Line filters: |= "error" contains, != "healthz" does not contain, |~ "timeout|refused" regex, !~ not regex. They are fast; put them first.
  • Parsers extract fields from the line: | json, | logfmt, | pattern "<ip> - <_> <method> <path>", | regexp "...".
  • Label filters on extracted fields: | status >= 500, | duration > 2s, | method="POST".
  • Formatting: | line_format "{{.method}} {{.path}} {{.duration}}" rewrites the line, | label_format renames labels, | drop/| keep trim fields.

Metric queries wrap a log query in a range function and aggregate like PromQL: count_over_time({app="api"} |= "error" [5m]) counts lines, rate(...) per second, bytes_rate bytes per second, and sum by (status) (...) groups. With | unwrap duration you can compute quantile_over_time(0.95, ... [5m]) by (path) from values in the logs. These queries draw graphs and can feed alert rules.

In Explore, the log volume histogram above the lines shows counts by level, the Logs app (Logs Drilldown) offers a query-less browsing view, and clicking a label value adds it to the selector.

Example

# Streams of the API in prod, lines containing "error" but not health checks
{app="api", env="prod"} |= "error" != "/healthz"

# Parse JSON logs, keep slow 5xx responses, reshape the line
{app="api"} | json | status >= 500 | duration > 1s
  | line_format "{{.method}} {{.path}} {{.status}} {{.duration}}"

# logfmt lines (level=error msg="..." ) from one namespace
{namespace="payments"} | logfmt | level="error"

# Metric query: error lines per second, by app
sum by (app) (rate({env="prod"} |= "error" [5m]))

# p95 request duration computed from log fields, by path
quantile_over_time(0.95,
  {app="api"} | json | unwrap duration_ms [5m]) by (path)

Remember

  • Stream selector first, then line filters, then parsers and label filters
  • Only labels are indexed; keep high-cardinality values in the line
  • count_over_time, rate and unwrap turn logs into metrics and alerts

Watch out for

  • Putting | json before a line filter, so Loki parses every line instead of discarding most of them cheaply first.

Check yourself

  1. Which query counts error lines per second for each app in prod?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: grafana.com/docs/loki/latest/query/