Skip to main content
RunBook Academy

ObservabilityLI · Correlating Metrics, Logs, and TracesCorrelation

Metric to Log Workflow

Intermediate⏱ ~22 minbashcurllogcli

What you'll learn

  • Identify the shared label set that the metric-to-log pivot relies on
  • Configure a Grafana data link that opens Loki with the same labels as the clicked datapoint
  • Recognise the failure modes that break the open-in-Loki drill from a metric panel
  • Validate the pivot end to end with a synthetic request and a Loki query

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

Not yet marked complete on this device.

The error rate for the checkout service is eighteen percent. The on-call engineer opens the dashboard and clicks the spike. They need to know what the application was logging at the moment the errors happened. The dashboard has no log panel. The engineer has to open a new tab, open Explore, type the service name into the Loki query bar, and guess the time range. Three minutes later they have the log lines. The investigation is now three minutes old and the on-call engineer has lost focus.

The metric-to-log pivot removes the dead time. The panellist clicks the spike. Grafana opens Loki with the same labels as the metric, the same time range as the dashboard, and the relevant log lines in front of them. The drill is two seconds, not three minutes.

What it is

The metric-to-log pivot is a Grafana data link that, on click, opens a Loki Explore page using the labels of the clicked metric series as the LogQL stream selector. The pivot is configured per panel, not per data source. The mechanism is the Grafana data link template syntax and the Loki stream label contract.

The shared identifier is the label set. The metric series is labelled with job, instance, status, and so on. The Loki stream is labelled with the same values. The pivot substitutes the labels of the clicked metric into the Loki stream selector and opens the query.

  Prometheus series
  +--------------------------------------+
  | http_server_requests_total           |
  |   job="checkout"                     |
  |   instance="10.0.1.5:8080"           |
  |   status="500"                       |
  +----------------------+---------------+
                         |
                         | click
                         v
  Loki query (auto-built)
  +--------------------------------------+
  | {job="checkout",                    |
  |  instance="10.0.1.5:8080",          |
  |  status="500"}                      |
  +--------------------------------------+

Why a sysadmin cares

The metric-to-log pivot is the most common pivot in an on-call runbook. It is the first drill an analyst runs when the metric panel flags an anomaly. The drill is the difference between “I have an alert” and “I have a hypothesis with evidence”.

Three operational payoffs.

  1. Speed. The pivot removes the label-copy step. The on-call engineer does not need to know which label names match between Prometheus and Loki. The link does the matching.
  2. Correctness. The pivot preserves the time range of the dashboard. The engineer is looking at the same ten-minute window as the metric panel, not the dashboard’s default range.
  3. Breadth. The pivot can be configured with extra label filters (for example, level="error") so the engineer lands on the relevant severity, not on every log line.

The cost is the discipline of keeping the Prometheus and Loki label sets aligned. The convention is the same set of labels on both sides: job, instance, status, method, path.

The Grafana data link is a URL template. The template is substituted at click time using the values of the clicked metric datapoint. The key substitutions:

  • ${__series.labels} — the full label set of the series, rendered as {job="x", instance="y"}.
  • ${__series.name} — the metric name.
  • ${__value.time} — the timestamp of the datapoint.
  • ${__url_time_range} — the dashboard’s current time range.
  • ${__data.fields.label} — a specific field value from a table panel.

The standard metric-to-log pivot URL opens the Loki Explore page with the same labels:

/explore?schemaVersion=1&panes=%7B%22logs%22%3A%7B%22datasource%22%3A%22loki%22%2C%22queries%22%3A%5B%7B%22refId%22%3A%22A%22%2C%22expr%22%3A%22%7B${__series.labels}%7D%22%7D%5D%7D%7D

The ${__series.labels} is replaced at click time with the label set of the clicked series. The datasource field selects the Loki data source by UID. The result is a Loki query pane with the stream selector pre-filled.

How to configure it

The data link is configured on the panel. The cleanest path in a managed Grafana is the provisioning YAML; the dashboard JSON is acceptable for one-off panels.

# grafana/provisioning/dashboards/checkout.yaml
apiVersion: 1
providers:
  - name: checkout
    folder: Observability
    type: file
    options:
      path: /var/lib/grafana/dashboards

The dashboard JSON carries the data link on the relevant panels. Two patterns are common.

Pattern A — link on the metric panel that opens Explore with the same labels:

{
  "targets": [
    {
      "expr": "sum by(job, instance, status) (rate(http_server_requests_total{job=\"checkout\"}[1m]))",
      "legendFormat": "{{status}} on {{instance}}"
    }
  ],
  "options": {
    "dataLinks": [
      {
        "title": "Logs for {{job}} {{instance}} {{status}}",
        "url": "/explore?schemaVersion=1&panes=%7B%22logs%22%3A%7B%22datasource%22%3A%22loki%22%2C%22queries%22%3A%5B%7B%22refId%22%3A%22A%22%2C%22expr%22%3A%22%7B${__series.labels}%7D%20%7C%3D%20%22error%22%22%7D%5D%7D%7D&orgId=1"
      }
    ]
  }
}

Pattern B — link as a side panel that opens with the same time range, for a deeper drill:

{
  "type": "logs",
  "datasource": { "type": "loki", "uid": "loki" },
  "targets": [
    {
      "expr": "{${__series.labels}}",
      "refId": "A"
    }
  ],
  "options": {
    "dataLinks": [
      {
        "title": "Trace for this error",
        "url": "/explore?schemaVersion=1&panes=%7B%22traces%22%3A%7B%22datasource%22%3A%22tempo%22%2C%22queries%22%3A%5B%7B%22query%22%3A%22$${__value.raw}%22%2C%22queryType%22%3A%22traceql%22%7D%5D%7D%7D&orgId=1"
      }
    ]
  }
}

The Loki side — the data source is configured with the same label set as Prometheus. The Prometheus OTel receiver sets the job and instance automatically. The Loki pipeline sets the same labels via the relabel pipeline.

# grafana/provisioning/datasources/loki.yaml
apiVersion: 1
datasources:
  - name: Loki
    type: loki
    uid: loki
    url: http://loki:3100
    jsonData:
      maxLines: 1000

The application side — the Prometheus exporter and the Loki agent must agree on the labels. The Alloy pipeline that scrapes the application and ships to Loki:

# /etc/alloy/config.alloy
loki.relabel "checkout" {
  rule {
    action   = "labelmap"
    regex    = "job"
    target_label = "job"
  }
  rule {
    action   = "labelmap"
    regex    = "instance"
    target_label = "instance"
  }
  rule {
    source_labels = ["level"]
    target_label  = "level"
  }
}

loki.write "checkout" {
  endpoint {
    url = "http://loki:3100/loki/api/v1/push"
  }
}

How to validate it

# 1. The metric has the labels we expect.
curl -s 'http://prometheus:9090/api/v1/series?match[]=http_server_requests_total{job="checkout"}' \
  | jq '.data[0]'
# {"job":"checkout","instance":"10.0.1.5:8080","status":"500","method":"POST","path":"/api/v1/orders"}

# 2. The Loki stream has the same labels.
logcli query --since=10m \
  '{job="checkout",instance="10.0.1.5:8080",status="500"}'
# 2026-08-13T03:00:12Z {job="checkout" instance="10.0.1.5:8080" status="500"} msg="payment failed"

# 3. The pivot URL renders correctly. The ${__series.labels}
#    substitution expands to the same label set.
echo '/explore?schemaVersion=1&panes=%7B%22logs%22%3A%7B%22datasource%22%3A%22loki%22%2C%22queries%22%3A%5B%7B%22refId%22%3A%22A%22%2C%22expr%22%3A%22%7B${__series.labels}%7D%22%7D%5D%7D%7D&orgId=1' \
  | sed 's|${__series.labels}|{job="checkout", instance="10.0.1.5:8080", status="500"}|'
# /explore?schemaVersion=1&panes=...{job="checkout", instance="10.0.1.5:8080", status="500"}...

# 4. The dashboard data link is loaded.
curl -s -u admin:admin http://grafana:3000/api/dashboards/uid/checkout \
  | jq '.dashboard.panels[0].options.dataLinks'
# [{"title":"Logs for ...","url":"/explore?...{__series.labels}..."}]

How it can fail

Six recurring failure shapes.

  1. The label set is misaligned. The Prometheus series is labelled with instance="10.0.1.5:8080". The Loki stream is labelled with instance="checkout-7d4b8". The substitution produces a Loki query that returns zero rows. Symptom: the pivot opens Explore, the Loki query returns no results, the operator assumes the logs are missing.
  2. The data link uses ${__field.labels}. The field substitution only works on table panels. On a time series panel, the field is empty. Symptom: the pivot opens Explore with {job=""}, the query errors out.
  3. The dashboard JSON is not re-imported. The data link is added in the panel editor but the provisioning file is not updated. The next time the dashboard is loaded from provisioning, the link is gone. Symptom: the link works briefly, then disappears after a Grafana restart.
  4. The Loki data source UID is wrong. The link uses datasource=loki but the data source UID is loki-prod. Symptom: the pivot opens Explore with the wrong data source, or with the default data source picker.
  5. The time range is empty. The link uses ${__url_time_range} but the dashboard has no time range set. Symptom: the pivot opens Explore with the browser’s default time range (last six hours), not the dashboard’s range.
  6. The label values are wrapped in extra quotes. The Loki pipeline adds a quote pair around every label value. The substitution produces {job=""checkout""}. Symptom: the Loki query is a parse error.

How to troubleshoot it

The diagnostic order is “is the label set aligned?”, “does the link render?”, “does the substitution expand?”, “does the resulting Loki query return rows?”.

  1. Is the label set aligned? curl -s 'http://prometheus:9090/api/v1/series?match[]=...' and logcli query --since=10m '{...}'. The label sets must match by name and value.
  2. Does the link render? Open the panel editor, hover over the data link field. The link text is shown. The presence of a ${__series.labels} substitution is the sanity check.
  3. Does the substitution expand? Open the dashboard, click the panel, observe the URL. The ${__series.labels} should be replaced with the full label set. If the URL still contains ${__series.labels}, the template parser is not running.
  4. Does the resulting Loki query return rows? Run the expanded query in logcli or in the Explore query bar. The query should return the log lines for the label set. If it returns zero rows, the label set is misaligned (failure mode 1).

Security implications

The metric-to-log pivot does not introduce new attack surface. The URL is internal to the Grafana instance and does not leave the cluster. The substitution variables are derived from the clicked datapoint, not from user input.

The risk is around the Loki query expansion. A label value that contains a Loki parser keyword (for example, job="__name__") can produce a query that errors out. The mitigation is to sanitise the label values at the Loki pipeline boundary and to reject label values that match the parser keywords.

The second-order risk is around the Explore page being open in a context that has broader permissions than the dashboard. The default is “Editor” or “Viewer” depending on the user’s role. A panel that links to an Explore page with a query that returns credentials is a misconfiguration. The mitigation is to limit the Explore data sources to the same set as the dashboard.

Performance implications

The pivot is a single Explore page load. The Loki query that runs is the same shape as a regular dashboard query. The performance cost is the cost of the Loki query, not the cost of the pivot.

The cost to watch is the cardinality of the stream selector. A panel that pivots on a high-cardinality label (for example, user_id) produces a stream selector that scans every stream in the Loki index. The discipline is to pivot on labels with low cardinality (job, instance, status) and to filter on the high-cardinality labels (user_id) inside the query, not in the stream selector.

Production guidance

  • Align the label set. The Prometheus exporter and the Loki pipeline must agree on the label names and the label values. The audit is a single shell command that compares the two series.
  • Pivot on lowercase labels. Loki stream labels are case-sensitive. The convention is lowercase labels everywhere, with no underscores converted to camelCase.
  • Use the bare ${__series.labels} substitution. The field substitution is fragile. The field set is populated only on table panels.
  • Limit the pivot to a row in the dashboard. The data link is a panel-level setting. The pivot only works from the panel that declares it. Operators who want to pivot from a different panel need to declare the link on that panel too.

Verification

You should now be able to answer:

  • Which shared labels does the metric-to-log pivot rely on?
  • What is the correct Grafana data link template variable for a metric-panel pivot?
  • What is the failure shape of a label-set mismatch between Prometheus and Loki?
  • How do you validate the pivot end to end with a synthetic request?

Quiz

Knowledge check · 8 questions

  1. Q1. Which shared identifier does the metric-to-log pivot rely on?

  2. Q2. Which Grafana data link template variable carries the full label set of the clicked metric series?

  3. Q3. A metric-to-log pivot requires shared labels between Prometheus and Loki.

  4. Q4. You click a metric datapoint and the pivot opens Loki with `{job="checkout", instance="", status=""}`. What is the most likely cause?

  5. Q5. Which conditions are required for the metric-to-log pivot to produce a useful Loki query?

  6. Q6. Name the Grafana data link field that drives a metric-to-log pivot.

  7. Q7. The Grafana data link carries the dashboard time range into the Explore page automatically.

  8. Q8. The pivot is silently producing a Loki query with no rows. The first diagnostic step is:

Passing score: 75%. Answers are checked in this browser.