ObservabilityLIV · Dashboard-to-Logs WorkflowsDashboardToLogs
Dashboard-to-Logs Anatomy
What you'll learn
- Identify the two Grafana 11.x mechanisms that pivot from a metric panel to Loki: panel-level data links and dashboard-level datasource correlations
- Name the four template variables that drive a dashboard-to-logs URL (time, series labels, field values, data source UID)
- Explain why a "click and see every log line" pivot is operationally useless and what shape the right pivot takes
- Distinguish a Loki stream query (`{job="x"} |= "msg"`) from a metric-style LogQL aggregation in the context of a panel pivot
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 Grafana dashboard shows a time-series panel: HTTP 5xx rate,
grouped by service and route, over the last hour. The line
for checkout-svc / POST /v2/cart climbs sharply at 14:32.
The on-call engineer clicks the line. They expect to land in
Explore with the right time window, the right labels, and a
Loki stream that contains the actual errors that explain the
spike.
What usually happens is one of three things. They land on a
Loki page that returns every log line from the service for
that hour — 47,000 of them — and the engineer scrolls for ten
minutes. They land on a Loki page that returns nothing because
the URL template used {{route}} but the metric label is
called handler. They land on an Explore view that opens the
whole-stack Loki Explore page with no filter at all, and the
“correlation” they were promised is the same blank panel they
started from.
The dashboard-to-logs workflow is the bridge between “the metric says something is wrong” and “the log line that explains it”. It is small in lines of YAML but unforgiving in detail: the URL template either captures the right values or it captures nothing.
What it is
A dashboard-to-logs pivot is a click in a Grafana panel that opens Loki (Explore, a Logs panel, or the Loki data source’s drilldown view) pre-populated with a query that is already scoped to the thing the operator clicked. Grafana 11.x exposes two mechanisms for this:
- Panel-level data links — the per-panel “Data links” tab in the panel editor. Each rule is a named link with a URL template that may reference series labels, field values, and the dashboard time range. This is the classical mechanism and remains the right answer for one-off panels.
- Dashboard-level
datasource_correlations— a Grafana 11.0 feature that declares, at the dashboard JSON level, a correlation between a metric data source and a logs data source. The metric panel then renders a “View logs” button on each data point; the click opens Explore with the query pre-populated from a label-mapping the dashboard author defines once. This is the right answer when the same metric to-logs mapping appears on many panels across a dashboard or across the catalogue.
Both mechanisms do the same job: take the panel the engineer is looking at, and the label values on the line they clicked, and turn them into a Loki query that returns a small, relevant stream of log lines for the same time window. The shape of the resulting query, not the mechanism, is what matters to the engineer on call.
Why a sysadmin cares
The pivot is the difference between “we have metrics and we have logs” and “we use them together”. Four production shapes appear in teams that have not designed the pivot:
- The scroll-of-shame pivot. The URL template passes the
service name and the time range but not the route or the
status code. The resulting Loki stream is 100,000 lines of
mixed
info,warn, anderroroutput. The engineer scrolls past the noise and gives up. The “one-click correlation” the dashboard advertises is a UI that makes incidents worse. - The silent label-name drift. The panel uses
handlerin its metric legend; the URL template references${__series.labels.handler}but the panel was renamed toroutein a refactor six months ago. The link is rendered, the click goes to Loki, the query returns zero lines. The engineer concludes the service produced no logs. - The wrong data source pivot. The URL template writes
{job="${service}"}against the default Loki data source. In a multi-cluster setup that default points at the EU cluster; the engineer is paged for the US cluster. The pivot opens and returns nothing; the engineer concludes the US service produced no logs. The two services are unrelated. - The time-window pivot. The dashboard is showing the last 6 hours; the panel data link does not pass a time range to Loki. The Explore view opens Loki with Loki’s default 6-hour window but at the wrong offset, or with the dashboard’s “now” which has since drifted forward by the time the engineer clicks.
How it works
The mental model is “the click is a query”. When the operator clicks a panel point, Grafana collects the labels on the series, the time of the point, and the dashboard’s current time range. It renders the data link’s URL template against those values. It opens that URL in a new tab (or in Explore in place, depending on configuration). The destination interprets the URL parameters; in the case of Loki, the parameters become a LogQL stream selector and a time range.
metric panel data link Loki Explore
------------- ---------- ------------
series: checkout-svc title='View logs' query: {service="checkout-svc",
/v2/cart url=/explore? route="/v2/cart",
value: 5xx rate = 17.3 ds=loki-prod-us& status="500"}
time: 2026-08-13T14:32:00Z query={service= from: 14:31:30
"${service}", to: 14:32:30
route="${route}",
status="500"}
&from=${__value.time:date-seconds}-60
&to=${__value.time:date-seconds}+60
Four observations on this shape:
- Labels become LogQL stream selectors. The metric series
carries labels; the LogQL stream selector uses the same
names. A name like
handleron the metric side has to match the Loki label name on the log side. The bridge either preserves names or has an explicit mapping. - Time becomes the time range. The point the operator clicked is the centre of a small window (60 s before, 60 s after by convention). The window is small enough that the resulting stream fits on one screen; the operator is investigating the anomaly, not the whole hour.
- Filter values become pipe-filters. If the metric panel
is
5xx rate by route, the data link passesstatus="500"to the Loki query as a pipe filter. The stream is pre-narrowed to the lines that explain the spike. - The data source UID becomes the destination. The URL template names the data source by UID, not by display name. A misspelled UID opens the wrong cluster, the wrong environment, or nothing.
How to configure it
There are two configurations that matter. The first is the
panel-level data link. The second is the dashboard-level
datasource_correlations block, which is the right choice
when the same pivot is shared across many panels.
{
"type": "timeseries",
"title": "5xx rate by service and route",
"datasource": { "type": "prometheus", "uid": "prom-prod-us" },
"targets": [
{
"expr": "sum by(service, route) (rate(http_requests_total{status=~\"5..\"}[5m]))",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"links": [
{
"title": "View logs for ${__series.labels.service} ${__series.labels.route}",
"url": "/explore?schemaVersion=1&panes=%7B%22logs%22%3A%7B%22datasource%22%3A%22loki-prod-us%22%2C%22queries%22%3A%5B%7B%22expr%22%3A%22%7Bservice%3D%5C%22%24%7B__series.labels.service%7D%5C%22%2Croute%3D%5C%22%24%7B__series.labels.route%7D%5C%22%2Cstatus%3D%5C%225%5C%22%7D%22%7D%5D%7D%7D&from=${__value.time:date-seconds}-120&to=${__value.time:date-seconds}+120",
"targetBlank": true,
"includeVars": true
}
]
}
}
}
The relevant options:
titleis the link’s display label. The template${__series.labels.<name>}is replaced with the value of that label on the clicked series. The label makes the link easy to scan when several appear on one row.urlis the full Explore URL with the LogQL query, the data source UID, and the time range. Thepanesparameter is the URL-encoded JSON that Grafana 11.x uses to restore Explore state.expris the LogQL stream selector;${__series.labels.<name>}becomes the matched label value.targetBlank: trueopens the link in a new tab. The alternative — opening in the same tab — replaces the dashboard the engineer is investigating. Prefer a new tab for incident workflows.includeVars: trueforwards the dashboard template variables (e.g.${env}) to the destination. Useful when the destination data source uses the same variable scheme.
The dashboard-level equivalent for the same pivot is a
datasource_correlations block:
{
"panels": [ /* ... the panel above ... */ ],
"__elements": {},
"__requires": [],
"datasource_correlations": [
{
"uid": "prom-prod-us-loki-prod-us",
"sourceUID": "prom-prod-us",
"targetUID": "loki-prod-us",
"label": "Logs",
"description": "Pivot from any 5xx-rate series to the underlying log lines",
"config": {
"field": "service",
"target": {
"expr": "{service=\"${__field.labels.service}\", route=\"${__field.labels.route}\", status=\"5\"}"
},
"type": "logs"
}
}
]
}
The field value is the source label the operator clicked;
__field.labels.<name> resolves to the clicked series’s
label value. The dashboard-level form is harder to author by
hand (the schema has shipped revisions across 11.x) and is
the right choice when the same pivot appears on dozens of
panels.
How to validate it
# READ-ONLY: list the data links on a panel.
curl -fsS -u grafana-admin:$GRAFANA_ADMIN \
"http://grafana.internal:3000/api/dashboards/uid/${DASH_UID}" \
| jq '.dashboard.panels[].fieldConfig.defaults.links // []'
# READ-ONLY: substitute the template with one set of label
# values and confirm the resulting URL renders to a valid
# Explore page.
SVC=checkout-svc
ROUTE=/v2/cart
T=1755100320 # 2026-08-13T14:32:00Z, in seconds since epoch
URL="http://grafana.internal:3000/explore?schemaVersion=1\
&panes=%7B%22logs%22%3A%7B%22datasource%22%3A%22loki-prod-us%22%2C%22queries%22%3A%5B%7B%22expr%22%3A%22%7Bservice%3D%5C%22${SVC}%5C%22%2Croute%3D%5C%22${ROUTE}%5C%22%2Cstatus%3D%5C%225%5C%22%7D%22%7D%5D%7D%7D\
&from=$((T-120))&to=$((T+120))"
curl -fsS -u grafana-admin:$GRAFANA_ADMIN "$URL" \
-o /dev/null -w '%{http_code}\n'
# 200
# READ-ONLY: a direct LogQL probe against the same selector.
curl -fsS -u grafana-admin:$GRAFANA_ADMIN \
--data-urlencode 'query={service="checkout-svc",route="/v2/cart",status="5"}' \
--data-urlencode "start=$((T-120))000000000" \
--data-urlencode "end=$((T+120))000000000" \
--data-urlencode 'limit=20' \
http://grafana.internal:3000/api/datasources/proxy/uid/loki-prod-us/loki/api/v1/query_range
# {"status":"success","data":{"resultType":"streams","result":[ ... ]}}
A clean validation: the rendered URL returns 200, the proxy returns Loki streams, and the streams contain log lines from the time window. A failure mode below maps to one of these signals failing.
How it can fail
The most expensive pivot failure modes from real production incidents.
- Label-name drift. The metric legend uses
handler; the Loki stream label isroute. The URL template references${__series.labels.route}; the substitution is empty; the LogQL{service="x", route="", status="5"}returns no lines. The symptom is a link that opens to “no results” without an error message. - Wrong data source UID. The URL targets
loki-prod-euwhile the metric comes fromprom-prod-us. The Explore view opens against the EU Loki; the lines for the US service are not there. The symptom is “the logs are missing” for an outage in the US cluster. - No time range in the URL. The link opens Explore with Loki’s default time range, which is “last 6 hours” or whatever the data source default is. The panel was showing a 30-day window; the Explore view shows the wrong window. The symptom is “the click took me to the wrong time” and the engineer spends minutes adjusting the time picker.
- No narrow-by-status filter. The URL passes
{service="${service}"}and nothing else. Loki returns every line from the service for the window — includinginfoanddebug. The symptom is the scroll-of-shame pivot that drives engineers to ignore the link entirely. - Double-encoded JSON. The
panesparameter is encoded once when it should be twice, or twice when it should be once. The link opens to a malformed Explore page; the URL bar shows the wrong characters. The symptom is a 200 with an empty panel. includeVars: falseon a dashboard with environment variables. The link forwards no variable context. The Explore view uses the data source’s default variable values. The symptom is “the pivot opened against staging when I was looking at production”.
How to troubleshoot it
The diagnostic order is “does the URL render?”, “does the URL return what is expected?”, “does the destination data source respond?”, “does the resulting LogQL query return lines?”.
- Render the template by hand. Pick a panel point, substitute the labels and the time into the URL template manually. If the resulting URL does not parse, the template is wrong.
- Open the URL in a private browser tab. A session with bad cookies, missing data source permissions, or a stale Grafana version produces different results from a fresh session. The private tab is the baseline.
- Check the destination data source. Run the same query
through
/api/datasources/proxy/uid/<uid>/loki/api/v1/query. A working direct query that returns nothing through the link isolates the problem to the URL template. - Check the label names. Run a metric query that returns
the labels on the panel series. Compare the labels to the
LogQL stream selector. A name like
handleron the metric side androuteon the log side is a name drift. - Check the time range. The metric panel renders at a
specific time. The data link should pass a window around
that time, not the dashboard’s full range. A link that
passes
from=now-6h&to=nowis a link that opens the wrong window. - Inspect Grafana’s logs.
/var/log/grafana/grafana.logrecords every data link resolution and every proxy call. A 4xx response from Loki appears with the query string and the upstream message.
Security implications
- The pivot passes label values into a URL. A label like
customer_idthat exists on the metric becomes a URL parameter; the URL lands in browser history, in the server access log, and in the destination data source’s query log. Audit the labels a pivot passes; acustomer_idpivot is a data-handling bug. - The destination data source has its own permissions. A pivot that targets a Loki data source with broader read permissions than the source Prometheus allows an operator with metric-only access to read logs they should not. Review the data source’s RBAC alongside the pivot.
- The
includeVarsflag forwards dashboard variables. If a variable contains a secret or a PII label, the variable value lands in the destination URL. Treat variable values as values that flow.
Performance implications
- A wide pivot is a slow query. A pivot that passes
{service="x"}and nothing else asks Loki for every line from the service. Loki returns them; the browser freezes. The cost of a wide pivot is paid in Loki CPU and browser memory. - The time window matters. A 30-day window with no status filter asks Loki to scan the service’s chunk for the whole retention period. The query is slow at best, rejected at worst.
- A correct pivot is fast. A pivot that passes
{service="x", route="y", status="5"}with a 4-minute window around the spike returns tens of lines in a few hundred milliseconds. The cost is paid once and the engineer sees the answer.
Production guidance
- Define the pivot once per (metric data source, logs data
source) pair. Use
datasource_correlationsfor the common case; use panel-level data links for the panel-specific case. - Name the bridge labels explicitly. The metric legend and
the Loki stream selector must agree on the label names.
Map
handlertorouteat the source if necessary; do not silently rely on luck. - Always pass a small time window. Two minutes before the point, two minutes after. The window is the anomaly, not the hour.
- Always pass a status or severity filter when the metric
is a rate of failures. The pivot should not return
infolines. - Pin the data source by UID, not by name. A rename of the data source in the catalogue should not break every pivot.
- Exercise pivots quarterly. A pivot that has not been clicked in production is a pivot that has drifted.
Verification
You should now be able to answer:
- What are the two Grafana 11.x mechanisms that pivot from a metric panel to Loki, and which is the right choice for a pivot that is shared across many panels?
- Which four template variables drive the URL the operator clicks, and which one controls the time window?
- What is the “scroll-of-shame pivot” and which two template-variable mistakes cause it?
- Why does the data source UID matter more than the data source display name when authoring a pivot?
Quiz
Knowledge check · 8 questions
Q1. In Grafana 11.x, which two mechanisms pivot from a metric panel to Loki?
Q2. Which URL template variable carries the clicked series label values into the data link URL?
Q3. A data link that targets a Loki data source by display name rather than by UID will break silently if the data source is renamed.
Q4. Which of these make a pivot operationally useful?
Q5. Name the URL template variable that resolves to the timestamp of the clicked panel point, in seconds since the epoch.
Q6. The metric panel labels a series as `handler`. The Loki stream selector for the same request uses `route`. What is the most likely failure when the data link is clicked?
Q7. The right time window for a dashboard-to-logs pivot is the full dashboard time range.
Q8. A pivot opens Explore against `loki-prod-eu` while the metric panel was showing data from `prom-prod-us`. What has gone wrong?
Passing score: 75%. Answers are checked in this browser.