ObservabilityXXV · Grafana Data SourcesGrafanaDataSources
Loki as a Data Source
What you'll learn
- Provision a Loki data source in Grafana 11.x with explicit URL, auth and timeout settings
- Set maxLines and maxSeries to values that protect both the UI and Loki from runaway queries
- Configure derived fields to link log lines to JSON viewers, LogQL queries and trace backends
- Read the `/api/datasources/uid/<uid>/health` response and recognise the Loki-specific failure shapes
- Diagnose the high-frequency production failure modes: timeouts, label-name drift, and derived-field regex errors
Prerequisites
Verified against Prometheus 2.55.x · Alertmanager 0.28.x · node_exporter 1.8.x · blackbox_exporter 0.26.x · Grafana 11.x · Loki 3.x · Tempo current · OpenTelemetry Collector 0.110.x · Grafana Alloy current · Docker Engine 28.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-13
A log panel renders 200,000 lines of an outage’s worth of
error log entries. The browser freezes for thirty seconds. The
engineer on call has to scroll past 199,500 lines that are not
the line they actually need. Loki is happy; it served the data.
Grafana is not happy; it asked for too much.
A separate engineer opens the same data source in Explore, types
{job="app"} | json | latency_ms > 1000, and gets no result.
The line is in there. The query is fine. The data source is
provisioned with maxSeries: 0 (the default), which means Loki
returns the first chunk without complaint and the metric-style
aggregation silently returns nothing useful.
The lesson this time is about the controls between “Loki has the data” and “Grafana can show it”: the YAML that declares the data source, the caps that protect the UI and Loki, the derived-fields framework that turns a log line into a pivot, and the timeout that decides whether a slow query is a Grafana outage or a Loki outage.
What it is
A Loki data source in Grafana is a named, configured client of
the Loki HTTP API. The provisioning file declares the data
source’s URL, type, credentials, TLS posture, and a small set of
Loki-specific behavioural options. Once provisioned, Grafana
runs every Logs panel and Explore query through a server-side
proxy at /api/datasources/proxy/uid/<uid>/.... The credentials
live in secureJsonData on the server.
In Grafana 11.x the canonical data source type is loki. The
older form Prometheus style with type: prometheus and a Loki
URL does not work; Loki has its own query language (LogQL), its
own metric-aggregation semantics, and its own derived-fields
framework, and the data source type is the switch that turns
all of those on.
Why a sysadmin cares
Loki data source misconfigurations are the most common cause of “logs are slow” complaints that are not Loki’s fault. Four production shapes appear repeatedly:
- Unbounded
maxLines. A panel withmaxLines: 5000(the UI maximum) against a noisy service renders five thousand log lines per refresh. The browser tab consumes hundreds of megabytes of memory; the engineer on call loses the ability to scroll. Loki is fine; the UX is broken. - Unset
maxSeries. A metric-style LogQL query likequantile_over_time(0.99, \{job="app"\} | json | unwrap latency [5m])returns thousands of series. Loki happily streams them; the panel either renders a wall of indistinguishable lines or silently truncates to the first chunk. The dashboard is “kind of correct” but the answer is invisible. - Missing or wrong derived fields. A log line contains a
traceIDbut the data source has no derived-field rule for it. The on-call engineer copies the trace ID by hand, opens Tempo, and pastes it. The correlation pivot that Grafana advertises is not configured; the cost is paid every investigation. - Query timeout below the data-source default. A Grafana
query timeout of 30 seconds against a Loki doing a
24-hour range query with a heavy label-match returns
context deadline exceeded. The on-call concludes Loki is broken; Loki is in fact serving the query correctly inside its own budget, just slower than Grafana is willing to wait.
How it works: the request path
browser grafana-server loki
------- -------------- ----
| | |
|--LogQL query-->| |
| /api/ds/proxy | |
| /uid/loki/ | |
| api/v1/query |--GET /loki/api/...|
| ?query={...} | BasicAuth + TLS |
| |<--stream chunks---|
|<--panel data---| |
Three observations:
- The browser never sees the credentials. Every log query from
the browser is rewritten by Grafana into a server-side call
to
/api/datasources/proxy/uid/<uid>/loki/api/v1/.... The path is the Loki HTTP API path; Grafana adds the auth headers and the query timeout. - Loki returns streams, not single-line responses. A query
against a 24-hour range returns a stream of chunks; Grafana
reads the stream until
maxLinesis reached, until the upstream closes the stream, or until the query timeout fires. Whichever happens first shapes the panel. - Derived fields are computed after the stream lands. Grafana parses each line, applies the regex from each derived-field rule, and rewrites the line into one or more links. The parsing cost is paid per line per render; a malformed regex silently disables that field.
How to configure it
# /etc/grafana/provisioning/datasources/loki.yml
apiVersion: 1
datasources:
- name: loki-prod-eu
uid: loki-prod-eu
type: loki
access: proxy
orgId: 1
url: https://loki-prod-eu.internal:3100
isDefault: false
editable: false
basicAuth: true
basicAuthUser: grafana-reader
jsonData:
# Cap on log lines returned per query.
# 1000 is the default; raise only when the operator
# has accepted the browser-memory cost.
maxLines: 1000
# Cap on series returned by a metric-style LogQL query.
# 100 is the conservative default for dashboards;
# raise for ad-hoc Explore queries.
maxSeries: 100
# Query timeout. The default (60s) is the Grafana server
# timeout; Loki's own --query_timeout is independent.
# Set Loki's --query.timeout to something below this.
timeout: 60
tlsAuth: false
tlsAuthWithCACert: true
tlsSkipVerify: false
# Derived fields turn a log line into a pivot.
# Each rule extracts a value via regex and produces a
# link using a template that may reference ${__value.raw}.
derivedFields:
- name: traceID
matcherRegex: '"traceID":"([0-9a-f]{32})"'
url: '$${__value.raw}'
urlDisplayLabel: 'Open in Tempo'
datasourceUid: tempo-prod-eu
internalLink:
expr: '${__value.raw}'
datasourceUid: tempo-prod-eu
- name: level
matcherRegex: '"level":"(debug|info|warn|error|fatal)"'
url: '$${__value.raw}'
urlDisplayLabel: 'Filter by level'
datasourceUid: loki-prod-eu
internalLink:
query: '{job="$${__var.job:json}"}->level="${__value.raw}"'
datasourceUid: loki-prod-eu
secureJsonData:
tlsCACert: |
-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUJx...
-----END CERTIFICATE-----
basicAuthPassword: ${LOKI_PASSWORD}
A few production notes on the options:
type: loki. Anything else (includingprometheuswith a Loki URL) produces a data source that Grafana treats as Prometheus and breaks on the first derived-fields query.maxLinesis a UI-side cap. The Loki stream is not truncated server-side; the proxy stops reading aftermaxLines. Raising this to 5000 for dashboards that “need more lines” trades server memory and browser memory for very little signal.maxSeriesis the cap on distinct time-series returned by a metric-style LogQL query. Loki is the source of truth for the number; Grafana cuts off aftermaxSeriesto protect the panel. Setting it to0is the developer’s friend; setting it to a sensible number is the operator’s friend.derivedFieldsis an array, not a map. Order matters when multiple rules could match the same line; the first match wins.internalLink.datasourceUidis the UID of the destination data source. A misspelled UID produces a link that fails at click time, not at provisioning time.internalLink.queryis a LogQL expression for “follow this field to other lines”. The template variables${__value.raw}(the matched substring) and${__var.<label>}(a stream label from the current line) are the standard hooks.
How to validate it
# READ-ONLY: the data source is provisioned and reachable.
curl -fsS -u grafana-admin:$GRAFANA_ADMIN \
http://grafana.internal:3000/api/datasources/uid/loki-prod-eu
# {
# "id": 2,
# "uid": "loki-prod-eu",
# "name": "loki-prod-eu",
# "type": "loki",
# "url": "https://loki-prod-eu.internal:3100",
# "access": "proxy",
# ...
# }
# READ-ONLY: the health check returns one of four states.
curl -fsS -u grafana-admin:$GRAFANA_ADMIN \
http://grafana.internal:3000/api/datasources/uid/loki-prod-eu/health
# {"message":"Data source is working","status":"success"}
# READ-ONLY: a minimal LogQL query through the proxy.
curl -fsS -u grafana-admin:$GRAFANA_ADMIN \
--data-urlencode 'query={job="varlogs"} |= "level=error"' \
http://grafana.internal:3000/api/datasources/proxy/uid/loki-prod-eu/loki/api/v1/query
# {"status":"success","data":{"resultType":"streams","result":[...]}}
# READ-ONLY: a derived-field link renders correctly from the panel.
# In the UI: click the trace ID label on any log line and
# confirm the link opens a Tempo trace with the same trace ID.
# CONFIGURATION: reload Grafana provisioning.
sudo systemctl reload grafana-server
A clean validation: the UID is present, the health check is green, the proxy returns Loki streams, and a derived-field link opens in the configured destination data source. Each failure mode below maps to one of these signals failing.
How it can fail
The most expensive Loki data source failure modes from real production incidents.
maxLinesset too high. A panel withmaxLines: 5000renders fine in development against a quiet service. In production the same query against a busy service consumes the engineer’s browser tab for thirty seconds per refresh. The symptom is intermittent UI freezes correlated with the panel’s refresh interval.maxSeries: 0against a metric-style query. Aquantile_over_time(...)query returns thousands of series. Loki streams them; Grafana truncates silently. The symptom is a panel that “looks fine but is missing most of the answer” — a partial render rather than an error.- Derived-field regex does not match. A regex change in
the log format (a
trace_idinstead oftraceID, an extra wrapper JSON object) silently disables the field. The symptom is “the trace link is gone” with no error message in the panel. - Query timeout below Loki’s own timeout. Grafana’s
timeout: 30against a Loki with a 60-second budget returnscontext deadline exceededfor any range query over a few hours. The symptom is “logs work for the last hour and fail beyond it”. - Wrong
type. A data source provisioned astype: prometheuswith a Loki URL accepts the provisioning file without error and returns garbage from the proxy. The symptom is “every Logs panel is empty and the Explore view has no Logs toggle”. derivedFieldsreferences a missing UID. TheinternalLink.datasourceUidpoints at a Tempo data source that has been removed. The provisioning reloads cleanly; the link fails at click time. The symptom is a link that 404s when an engineer clicks it during an incident.
How to troubleshoot it
The diagnostic order is “is the data source provisioned?”, “is the proxy reachable?”, “is Loki responding?”, “is the derived-fields pipeline working?”.
- Confirm the data source exists.
GET /api/datasources/uid/<uid>. If 404, the provisioning file did not reload; checkgrafana.logfor parse errors and confirm thetypeisloki. - Confirm the health check.
GET /api/datasources/uid/<uid>/health. The four states aresuccess,error,notfound, andforbidden. - Reproduce the request through the proxy.
curlagainst/api/datasources/proxy/uid/<uid>/loki/api/v1/query?query={job="varlogs"}. This is the exact request Grafana issues for the simplest log query. - Reproduce the request directly.
curlagainst the upstream Loki URL with the same credentials. A working direct curl that fails through the proxy isolates the problem to Grafana’ssecureJsonDataortlsConfig. - Validate derived fields. Open the panel in Explore, click the field label on a sample line, and confirm the link opens the destination. A regex that matches in a regex tester but not in Grafana usually has a YAML escape problem.
- Inspect Grafana’s logs.
/var/log/grafana/grafana.logrecords every proxy call. A400 Bad Requestfrom Loki appears with the query string and the upstream message; a timeout appears ascontext deadline exceeded.
Security implications
- The proxy holds the credentials. A Loki data source with
access: directexposes the password to every browser. - Derived-field links can leak. A derived field that
extracts a session token or a customer ID and links it to
another data source turns a log line into a cross-reference
pivot. Audit the destination URLs; a regex that picks up a
customer_emailfield by mistake is a data-handling bug. - The query timeout is a DoS knob. A Loki data source
without a timeout cap can be driven into a slow-query storm
by a single Explore session. Set the timeout to a value
above Loki’s own
--query.timeoutso the upstream fails first. maxLinesandmaxSeriesare cost knobs. A Grafana with no caps is a Loki customer that the rest of the team cannot budget for. Cap them.
Performance implications
- Loki streams, Grafana reads. The proxy stops reading
after
maxLines. Loki is unaware of the cap; the cost of a highmaxLinesis paid in browser memory and render time, not in Loki’s CPU. - Metric-style LogQL is bounded by
maxSeries. A query that returns ten thousand series without a cap silently truncates. The fix is to add atopkorquantileto the LogQL itself, not to raisemaxSeriesindefinitely. - Derived fields are per-line regex. A regex with catastrophic backtracking on a long log line is a denial of service against the browser. Test regexes with adversarial inputs.
- The query timeout is the upper bound. A Loki with a 60-second budget against a Grafana with a 30-second timeout means the Grafana times out before Loki does, and the engineer gets a clean error rather than a slow render.
Production guidance
- Pin the
uid. Renaming a data source is fine; changing the UID breaks every panel and derived-field reference. - Use
access: proxyfor every Loki with credentials. - Set
maxLinesto 1000 by default; raise only with a documented reason. - Set
maxSeriesto a value that the dashboard actually consumes. A panel that shows 100 series withmaxSeries: 0is silently asking Loki for everything. - Configure derived fields for the trace ID and the request ID before the first incident that needs them. Add the rules in code review, not during a Sev 1.
- Reload Grafana provisioning through the GitOps pipeline.
- Periodically exercise derived-field links. A regex that has drifted is silent until an engineer clicks it.
Verification
You should now be able to answer:
- What is the role of
maxLinesandmaxSeriesin a Loki data source, and which side of the request enforces each cap? - How does the Grafana query timeout interact with Loki’s
--query.timeoutflag, and which side should be smaller? - Why does a silent regex drift in a derived-field rule only become visible during an incident?
- What does the
internalLink.datasourceUidactually do at click time, and what is the failure shape when it is wrong?
Quiz
Knowledge check · 8 questions
Q1. What does the `maxLines` setting on a Loki data source in Grafana actually limit?
Q2. A metric-style LogQL query returns thousands of distinct time-series. Which setting decides how many of those series the panel actually shows?
Q3. Setting the Grafana Loki query timeout *below* the Loki --query.timeout flag is the right production posture.
Q4. Which of these are valid components of a Loki `derivedFields` rule?
Q5. Name the Grafana 11.x data source `type` value that selects the Loki query language and derived-fields framework.
Q6. A derived-field rule for `traceID` shows the link in the panel but clicking it opens a Tempo page that 404s. What is most likely wrong?
Q7. Raising `maxLines` to the maximum is the right answer when a panel "needs more log lines".
Q8. A Loki panel returns `context deadline exceeded` for queries longer than a few hours. The Grafana timeout is 60s and the Loki --query.timeout is unset (default 2m). What is the right fix?
Passing score: 75%. Answers are checked in this browser.