Skip to content

fix(observability): make the trace/log/metric cross-links actually work - #2041

Merged
Smana merged 1 commit into
mainfrom
worktree-otel-dashboard
Sep 13, 2026
Merged

Smana merged 1 commit into
mainfrom
worktree-otel-dashboard

Conversation

@Smana

@Smana Smana commented Sep 13, 2026

Copy link
Copy Markdown
Owner

What you see today

Five panels on the "All in One" demo dashboard say No data, and three more
advertise "click exemplar dots to view traces" — an interaction that cannot
happen. Investigating that turned up something wider: every cross-datasource
link in this platform has silently done nothing since it was written
, on both
clusters.

Four independent defects, none visible to CI

Defect Why nothing caught it
6 panel expressions query http.server.request.count, removed by the semconv v2 migration the alert rules were migrated at the time, the dashboard was not; P95 kept working because it reads duration_bucket
datasourceName is in no Grafana schema — only datasourceUid is the datasource loads fine, the link just never appears
tracesToLogs v1 is superseded by tracesToLogsV2, which is what Grafana reads correcting keys inside a block nothing reads looks like a fix and is not one
neither datasource pinned a uid, so references to the literal names matched nothing the operator assigns a random uid; the reference silently resolves to nothing

flux schema validate sees a well-formed map, polaris never reads datasource
config, and a documentation claim only fails when it pins the exact value.

Exemplars: ruled out, permanently

The dashboard's trace correlation was built on exemplars. They cannot work here:

  • /api/v1/query_exemplars is a hardcoded stub in app/vmselect/main.go
    returning {"status":"success","data":[]} — byte-identical to what the live
    cluster returns
  • real support (PR #5982) was merged upstream 2024-05-07 and reverted
    2024-07-03, with a maintainer rationale rejecting exemplars as immature
  • the OTLP ingestion parser contains zero references to exemplars, so
    switching from exponential to classic histograms would change nothing

No flag, no version bump, no ingestion change fixes this. The dead affordances
are removed rather than left as a promise the data cannot keep.

What replaces them

A traces row — a search panel and a service map on the existing VictoriaTraces
datasource — plus a data link on the latency panel that opens that panel
filtered to slow traces over the clicked time range. Every referent verified:
the target panel id exists, the min_trace_duration variable is defined, and
the traces target actually reads it.

One deliberate use of a deprecated field

The uid is pinned at spec.datasource.uid, which the CRD marks deprecated.
spec.uid is immutable and the API server rejects it outright on an
already-created object:

The GrafanaDatasource "vt-datasource" is invalid: spec: Invalid value: spec.uid is immutable

Flux would have hit that on every reconcile — a change that fixes dead links by
wedging the Kustomization that delivers them. Confirmed by server-side dry-run.
Renaming both objects so they are re-created was the alternative; it churns live
datasources for no gain. The reasoning is recorded in both files.

Also fixed

The load generator exported no telemetry at all — resource detection failed
with user: Current requires cgo or $USER set in environment, so every trace
started at the server span rather than at the generator. The App composition
injects USER for the app containers; the plain CronJob manifest never got one.

Documentation

dashboards-and-alerts.md reproduced the broken configuration verbatim as the
pattern to copy
, so fixing only the manifests would have guaranteed it comes
back. logs.md cited the old regex and a nonexistent metric as evidence for two
rules that are themselves correct. ADR-0010 is amended, not edited — its
driver stays as the record of what was believed, with a dated note under
Consequences recording that the linking was inert.

Verification

  • ./scripts/validate-manifests.sh -> All gates passed
  • ./scripts/validate-doc-claims.sh -> 28 claims / 49 page checks
  • ./scripts/validate-links.sh -> all resolve
  • both datasource CRs -> configured on a server-side dry-run against the live CRD
  • escaping invariant -> 0 bare ${name} across all three YAML files

Not verified, deliberately flagged

  • the traces/nodeGraph panels have not been seen rendering. They use
    queryType: search against the Jaeger-type datasource; the shape is right but
    no live Grafana has displayed them, and nothing in CI can.
  • tracesToLogsV2 semantics. VictoriaLogs speaks LogsQL, not LogQL, so
    filterByTraceID may not translate; if that direction stays empty the fix is
    customQuery with a LogsQL expression. Noted in the file. The reverse
    direction — logs to trace — is the one the demo relies on and is fixed here.

The "All in One" demo dashboard showed "No data" on five panels and offered
three "click exemplar dots to view traces" affordances that could never fire.
Investigating those turned up a wider problem: every cross-datasource link in
this platform has silently done nothing since the day it was written, on both
clusters.

Metrics. Six panel expressions queried `http.server.request.count`, removed by
the OpenTelemetry semconv v2 migration. The alerting rules were updated at the
time; the dashboard was not. They now use the histogram count series,
`http.server.request.duration_count`, the same shape the VMRules already use.
The P95/P50 panels were always correct because they read `duration_bucket` --
which is why exactly one panel kept rendering.

Datasource links. Three defects, each invisible:

  - `datasourceName` is not a key in any Grafana schema; only `datasourceUid`
    is. Trace-to-logs, trace-to-metrics, log-to-trace and the service map were
    all inert.
  - `tracesToLogs` (v1) is superseded by `tracesToLogsV2`, which is what
    current Grafana reads, so correcting keys inside the v1 block would have
    changed nothing.
  - Neither datasource pinned a uid, so the operator assigned a random one and
    references written as the literal strings `VictoriaLogs`/`VictoriaTraces`
    matched nothing.

The uid is pinned at `spec.datasource.uid`, the field the CRD marks deprecated,
deliberately: `spec.uid` is immutable and the API server rejects it outright on
an already-created object, so Flux would fail every reconcile. Confirmed by
server-side dry-run. Renaming the objects to re-create them was the
alternative and churns live datasources for no gain.

`tracesToMetrics` also named metrics that do not exist and span attributes that
do not exist -- `http.method`/`http.status_code` rather than the semconv v2
`http.request.method`/`http.response.status_code` -- and passed `tags` as bare
strings where the schema requires key/value objects.

Exemplars. The dashboard's trace correlation was built on them and they cannot
work here: VictoriaMetrics answers `/api/v1/query_exemplars` from a hardcoded
empty stub, real support was merged upstream in 2024 and then deliberately
reverted, and the OTLP ingestion parser never reads the exemplar field at all.
No flag or histogram change helps. The dead affordances are removed and
replaced with a traces row -- a search panel and a service map on the existing
VictoriaTraces datasource -- plus a data link from the latency panel that opens
that panel filtered to slow traces over the clicked time range.

Load generator. It exported no telemetry at all, failing resource detection
with "user: Current requires cgo or $USER set in environment". The App
composition injects USER for the app containers; the plain CronJob manifest
never got one, so every trace started at the server span rather than at the
generator.

Documentation. `dashboards-and-alerts.md` reproduced the broken configuration
verbatim as the pattern to copy, so fixing only the manifests would have
guaranteed someone copies it back. `logs.md` cited the old regex and a
nonexistent metric as evidence for two rules that are themselves correct.
ADR-0010 is amended rather than edited: the driver it lists stays as the record
of what was believed, with a dated note under Consequences recording that the
linking was inert, because no gate in this repo can catch this class of defect
-- the schema validator sees a well-formed map, polaris never reads datasource
config, and the link simply never appears.

Verified: manifest gate all green, doc claims 28/49 pages, links resolve, both
datasource CRs accepted by a server-side dry-run against the live CRD.
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Rendered manifest diff — this PR vs main (desired state)

4 changed · 0 added · 0 removed

Rendered with kustomize build + helm template (source of truth = git), so Helm-expanded workloads are included. Shows what Flux will apply — not a diff against live cluster state (drift is alerted on separately), and not CRD-defaulted / webhook-mutated output. Secret values are redacted; per-render noise (webhook caBundles, checksum/* annotations, render timestamps) is normalized out.

🟡 changed — batch/v1/CronJob/apps/image-gallery-loadgen
--- a/batch/v1/CronJob/apps/image-gallery-loadgen
+++ b/batch/v1/CronJob/apps/image-gallery-loadgen
@@ -37,6 +37,8 @@
               value: http://victoria-traces-vt-single-server.observability.svc.cluster.local:10428/insert/opentelemetry/v1/traces
             - name: OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
               value: http://vmsingle-victoria-metrics-k8s-stack.observability.svc.cluster.local:8428/opentelemetry/v1/metrics
+            - name: USER
+              value: loadgen
             image: ghcr.io/smana/image-gallery:2.0.0
             imagePullPolicy: IfNotPresent
             name: loadgen
🟡 changed — grafana.integreatly.org/v1beta1/GrafanaDashboard/apps/app-all-in-one
--- a/grafana.integreatly.org/v1beta1/GrafanaDashboard/apps/app-all-in-one
+++ b/grafana.integreatly.org/v1beta1/GrafanaDashboard/apps/app-all-in-one
@@ -9,4 +9,4 @@
   instanceSelector:
     matchLabels:
       dashboards: grafana
-  json: "{\n  \"title\": \"Observability Demo - All in One\",\n  \"description\": \"RED metrics with exemplar-based trace correlation and log context\",\n  \"tags\": [\"observability\", \"traces\", \"metrics\", \"exemplars\", \"demo\"],\n  \"timezone\": \"browser\",\n  \"editable\": true,\n  \"graphTooltip\": 1,\n  \"time\": {\n    \"from\": \"now-30m\",\n    \"to\": \"now\"\n  },\n  \"templating\": {\n    \"list\": [\n      {\n        \"name\": \"datasource\",\n        \"type\": \"datasource\",\n        \"query\": \"prometheus\",\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"VictoriaMetrics\",\n          \"value\": \"VictoriaMetrics\"\n        },\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"options\": [],\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"type\": \"datasource\"\n      },\n      {\n        \"name\": \"trace_datasource\",\n        \"label\": \"Trace Datasource\",\n        \"type\": \"datasource\",\n        \"query\": \"jaeger\",\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"VictoriaTraces\",\n          \"value\": \"VictoriaTraces\"\n        },\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"options\": [],\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"type\": \"datasource\"\n      },\n      {\n        \"name\": \"logs_datasource\",\n        \"label\": \"Logs Datasource\",\n        \"type\": \"datasource\",\n        \"query\": \"victoriametrics-logs-datasource\",\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"VictoriaLogs\",\n          \"value\": \"VictoriaLogs\"\n        },\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"options\": [],\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"type\": \"datasource\"\n      },\n      {\n        \"name\": \"namespace\",\n        \"type\": \"constant\",\n        \"query\": \"apps\",\n        \"current\": {\n          \"text\": \"apps\",\n          \"value\": \"apps\"\n        },\n        \"hide\": 2\n      },\n      {\n        \"name\": \"service\",\n        \"type\": \"constant\",\n        \"query\": \"image-gallery\",\n        \"current\": {\n          \"selected\": true,\n          \"text\": \"image-gallery\",\n          \"value\": \"image-gallery\"\n        },\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"name\": \"service\",\n        \"options\": [\n          {\n            \"selected\": true,\n            \"text\": \"image-gallery\",\n            \"value\": \"image-gallery\"\n          }\n        ],\n        \"skipUrlSync\": false,\n        \"type\": \"constant\"\n      }\n    ]\n  },\n  \"panels\": [\n    {\n      \"type\": \"row\",\n      \"title\": \"\U0001F4CA METRICS: Service Health\",\n      \"gridPos\": {\"x\": 0, \"y\": 0, \"w\": 24, \"h\": 1},\n      \"collapsed\": false\n    },\n    {\n      \"type\": \"timeseries\",\n      \"title\": \"HTTP Request Rate (requests/sec) [Click exemplar dots to view traces]\",\n      \"gridPos\": {\"x\": 0, \"y\": 1, \"w\": 12, \"h\": 8},\n      \"datasource\": {\"type\": \"prometheus\", \"uid\": \"$${datasource}\"},\n      \"targets\": [\n        {\n          \"expr\": \"sum(rate(http.server.request.count{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\"}[1m]))\",\n          \"legendFormat\": \"Total requests/sec\",\n          \"refId\": \"A\",\n          \"exemplar\": true\n        },\n        {\n          \"expr\": \"sum by (http.response.status_code) (rate(http.server.request.count{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\"}[1m]))\",\n          \"legendFormat\": \"{{http.response.status_code}}\",\n          \"refId\": \"B\",\n          \"exemplar\": true\n        }\n      ],\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\"mode\": \"palette-classic\"},\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisLabel\": \"requests/sec\",\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"lineWidth\": 2,\n            \"showPoints\": \"auto\"\n          },\n          \"unit\": \"reqps\",\n          \"links\": [\n            {\n              \"title\": \"View trace for this exemplar\",\n              \"url\": \"/explore?left={\\\"datasource\\\":\\\"$${trace_datasource}\\\",\\\"queries\\\":[{\\\"query\\\":\\\"$${__value.labels.trace_id}\\\",\\\"refId\\\":\\\"A\\\"}]}\"\n            }\n          ]\n        }\n      },\n      \"options\": {\n        \"tooltip\": {\"mode\": \"multi\"},\n        \"legend\": {\"displayMode\": \"list\", \"placement\": \"bottom\"}\n      }\n    },\n    {\n      \"type\": \"timeseries\",\n      \"title\": \"Response Time (P95 latency) [Click exemplar dots to view traces]\",\n      \"gridPos\": {\"x\": 12, \"y\": 1, \"w\": 12, \"h\": 8},\n      \"datasource\": {\"type\": \"prometheus\", \"uid\": \"$${datasource}\"},\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(rate(http.server.request.duration_bucket{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\"}[1m])) by (vmrange, http.route))\",\n          \"legendFormat\": \"P95 - {{http.route}}\",\n          \"refId\": \"A\",\n          \"exemplar\": true\n        },\n        {\n          \"expr\": \"histogram_quantile(0.50, sum(rate(http.server.request.duration_bucket{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\"}[1m])) by (vmrange))\",\n          \"legendFormat\": \"P50 (median)\",\n          \"refId\": \"B\",\n          \"exemplar\": true\n        }\n      ],\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\"mode\": \"palette-classic\"},\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisLabel\": \"seconds\",\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"lineWidth\": 2\n          },\n          \"unit\": \"s\",\n          \"links\": [\n            {\n              \"title\": \"View trace for this exemplar\",\n              \"url\": \"/explore?left={\\\"datasource\\\":\\\"$${trace_datasource}\\\",\\\"queries\\\":[{\\\"query\\\":\\\"$${__value.labels.trace_id}\\\",\\\"refId\\\":\\\"A\\\"}]}\"\n            }\n          ]\n        }\n      },\n      \"options\": {\n        \"tooltip\": {\"mode\": \"multi\"},\n        \"legend\": {\"displayMode\": \"list\", \"placement\": \"bottom\"}\n      }\n    },\n    {\n      \"type\": \"stat\",\n      \"title\": \"Error Rate %\",\n      \"gridPos\": {\"x\": 0, \"y\": 9, \"w\": 6, \"h\": 4},\n      \"datasource\": {\"type\": \"prometheus\", \"uid\": \"$${datasource}\"},\n      \"targets\": [\n        {\n          \"expr\": \"sum(rate(http.server.request.count{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\",http.response.status_code=~\\\"5..\\\"}[1m])) / sum(rate(http.server.request.count{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\"}[1m])) * 100\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\"mode\": \"thresholds\"},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\"color\": \"green\", \"value\": null},\n              {\"color\": \"yellow\", \"value\": 1},\n              {\"color\": \"orange\", \"value\": 5},\n              {\"color\": \"red\", \"value\": 10}\n            ]\n          },\n          \"unit\": \"percent\",\n          \"decimals\": 2\n        }\n      },\n      \"options\": {\n        \"colorMode\": \"background\",\n        \"graphMode\": \"area\",\n        \"textMode\": \"value_and_name\"\n      }\n    },\n    {\n      \"type\": \"stat\",\n      \"title\": \"Request Rate\",\n      \"gridPos\": {\"x\": 6, \"y\": 9, \"w\": 6, \"h\": 4},\n      \"datasource\": {\"type\": \"prometheus\", \"uid\": \"$${datasource}\"},\n      \"targets\": [\n        {\n          \"expr\": \"sum(rate(http.server.request.count{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\"}[1m]))\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\"mode\": \"palette-classic\"},\n          \"unit\": \"reqps\",\n          \"decimals\": 1\n        }\n      },\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"textMode\": \"value_and_name\"\n      }\n    },\n    {\n      \"type\": \"stat\",\n      \"title\": \"P95 Latency\",\n      \"gridPos\": {\"x\": 12, \"y\": 9, \"w\": 6, \"h\": 4},\n      \"datasource\": {\"type\": \"prometheus\", \"uid\": \"$${datasource}\"},\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(rate(http.server.request.duration_bucket{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\"}[1m])) by (vmrange))\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\"mode\": \"thresholds\"},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\"color\": \"green\", \"value\": null},\n              {\"color\": \"yellow\", \"value\": 0.5},\n              {\"color\": \"orange\", \"value\": 1},\n              {\"color\": \"red\", \"value\": 5}\n            ]\n          },\n          \"unit\": \"s\",\n          \"decimals\": 3\n        }\n      },\n      \"options\": {\n        \"colorMode\": \"background\",\n        \"graphMode\": \"area\",\n        \"textMode\": \"value_and_name\"\n      }\n    },\n    {\n      \"type\": \"stat\",\n      \"title\": \"Total 5xx Errors (last 5m)\",\n      \"gridPos\": {\"x\": 18, \"y\": 9, \"w\": 6, \"h\": 4},\n      \"datasource\": {\"type\": \"prometheus\", \"uid\": \"$${datasource}\"},\n      \"targets\": [\n        {\n          \"expr\": \"sum(increase(http.server.request.count{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\",http.response.status_code=~\\\"5..\\\"}[5m]))\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\"mode\": \"thresholds\"},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\"color\": \"green\", \"value\": null},\n              {\"color\": \"red\", \"value\": 1}\n            ]\n          },\n          \"unit\": \"short\",\n          \"decimals\": 0\n        }\n      },\n      \"options\": {\n        \"colorMode\": \"background\",\n        \"graphMode\": \"none\",\n        \"textMode\": \"value_and_name\"\n      }\n    },\n    {\n      \"type\": \"timeseries\",\n      \"title\": \"Request Count by Status Code [Click exemplar dots to view traces]\",\n      \"gridPos\": {\"x\": 0, \"y\": 13, \"w\": 24, \"h\": 6},\n      \"datasource\": {\"type\": \"prometheus\", \"uid\": \"$${datasource}\"},\n      \"targets\": [\n        {\n          \"expr\": \"sum by (http.response.status_code) (rate(http.server.request.count{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\"}[1m]))\",\n          \"legendFormat\": \"HTTP {{http.response.status_code}}\",\n          \"refId\": \"A\",\n          \"exemplar\": true\n        }\n      ],\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\"mode\": \"palette-classic\"},\n          \"custom\": {\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"lineWidth\": 2,\n            \"stacking\": {\"mode\": \"normal\"}\n          },\n          \"unit\": \"reqps\",\n          \"links\": [\n            {\n              \"title\": \"View trace for this exemplar\",\n              \"url\": \"/explore?left={\\\"datasource\\\":\\\"$${trace_datasource}\\\",\\\"queries\\\":[{\\\"query\\\":\\\"$${__value.labels.trace_id}\\\",\\\"refId\\\":\\\"A\\\"}]}\"\n            }\n          ]\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\"id\": \"byRegexp\", \"options\": \".*5.*\"},\n            \"properties\": [\n              {\"id\": \"color\", \"value\": {\"mode\": \"fixed\", \"fixedColor\": \"red\"}}\n            ]\n          },\n          {\n            \"matcher\": {\"id\": \"byRegexp\", \"options\": \".*200.*\"},\n            \"properties\": [\n              {\"id\": \"color\", \"value\": {\"mode\": \"fixed\", \"fixedColor\": \"green\"}}\n            ]\n          }\n        ]\n      },\n      \"options\": {\n        \"tooltip\": {\"mode\": \"multi\"},\n        \"legend\": {\"displayMode\": \"list\", \"placement\": \"bottom\"}\n      }\n    },\n    {\n      \"type\": \"row\",\n      \"title\": \"\U0001F4CB Error Logs with Context\",\n      \"gridPos\": {\"x\": 0, \"y\": 19, \"w\": 24, \"h\": 1},\n      \"collapsed\": false\n    },\n    {\n      \"type\": \"logs\",\n      \"title\": \"Application Error Logs (Click on log line to see \xB17 lines of context)\",\n      \"description\": \"Shows error-level logs from the application. Click any log line to view surrounding context (\xB17 lines before/after).\",\n      \"gridPos\": {\"x\": 0, \"y\": 20, \"w\": 24, \"h\": 10},\n      \"datasource\": {\"type\": \"victoriametrics-logs-datasource\", \"uid\": \"$${logs_datasource}\"},\n      \"targets\": [\n        {\n          \"expr\": \"{kubernetes.container_name=$$service} | unpack_json | log.level:error\",\n          \"refId\": \"A\",\n          \"queryType\": \"raw\"\n        }\n      ],\n      \"options\": {\n        \"showTime\": true,\n        \"showLabels\": false,\n        \"showCommonLabels\": false,\n        \"wrapLogMessage\": true,\n        \"prettifyLogMessage\": true,\n        \"enableLogDetails\": true,\n        \"dedupStrategy\": \"none\",\n        \"sortOrder\": \"Descending\",\n        \"enableContext\": true\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        }\n      }\n    }\n  ]\n}\n"
+  json: "{\n  \"title\": \"Observability Demo - All in One\",\n  \"description\": \"RED metrics, trace search and service map, and log context\",\n  \"tags\": [\"observability\", \"traces\", \"metrics\", \"demo\"],\n  \"timezone\": \"browser\",\n  \"editable\": true,\n  \"graphTooltip\": 1,\n  \"time\": {\n    \"from\": \"now-30m\",\n    \"to\": \"now\"\n  },\n  \"templating\": {\n    \"list\": [\n      {\n        \"name\": \"datasource\",\n        \"type\": \"datasource\",\n        \"query\": \"prometheus\",\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"VictoriaMetrics\",\n          \"value\": \"VictoriaMetrics\"\n        },\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"options\": [],\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"type\": \"datasource\"\n      },\n      {\n        \"name\": \"trace_datasource\",\n        \"label\": \"Trace Datasource\",\n        \"type\": \"datasource\",\n        \"query\": \"jaeger\",\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"VictoriaTraces\",\n          \"value\": \"VictoriaTraces\"\n        },\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"options\": [],\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"type\": \"datasource\"\n      },\n      {\n        \"name\": \"logs_datasource\",\n        \"label\": \"Logs Datasource\",\n        \"type\": \"datasource\",\n        \"query\": \"victoriametrics-logs-datasource\",\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"VictoriaLogs\",\n          \"value\": \"VictoriaLogs\"\n        },\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"options\": [],\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"type\": \"datasource\"\n      },\n      {\n        \"name\": \"namespace\",\n        \"type\": \"constant\",\n        \"query\": \"apps\",\n        \"current\": {\n          \"text\": \"apps\",\n          \"value\": \"apps\"\n        },\n        \"hide\": 2\n      },\n      {\n        \"name\": \"service\",\n        \"type\": \"constant\",\n        \"query\": \"image-gallery\",\n        \"current\": {\n          \"selected\": true,\n          \"text\": \"image-gallery\",\n          \"value\": \"image-gallery\"\n        },\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"name\": \"service\",\n        \"options\": [\n          {\n            \"selected\": true,\n            \"text\": \"image-gallery\",\n            \"value\": \"image-gallery\"\n          }\n        ],\n        \"skipUrlSync\": false,\n        \"type\": \"constant\"\n      },\n      {\n        \"name\": \"min_trace_duration\",\n        \"label\": \"Min Trace Duration\",\n        \"type\": \"textbox\",\n        \"query\": \"\",\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"\",\n          \"value\": \"\"\n        },\n        \"hide\": 0,\n        \"skipUrlSync\": false\n      }\n    ]\n  },\n  \"panels\": [\n    {\n      \"type\": \"row\",\n      \"title\": \"\U0001F4CA METRICS: Service Health\",\n      \"gridPos\": {\"x\": 0, \"y\": 0, \"w\": 24, \"h\": 1},\n      \"collapsed\": false\n    },\n    {\n      \"type\": \"timeseries\",\n      \"title\": \"HTTP Request Rate (requests/sec)\",\n      \"gridPos\": {\"x\": 0, \"y\": 1, \"w\": 12, \"h\": 8},\n      \"datasource\": {\"type\": \"prometheus\", \"uid\": \"$${datasource}\"},\n      \"targets\": [\n        {\n          \"expr\": \"sum(rate(http.server.request.duration_count{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\"}[1m]))\",\n          \"legendFormat\": \"Total requests/sec\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum by (http.response.status_code) (rate(http.server.request.duration_count{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\"}[1m]))\",\n          \"legendFormat\": \"{{http.response.status_code}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\"mode\": \"palette-classic\"},\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisLabel\": \"requests/sec\",\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"lineWidth\": 2,\n            \"showPoints\": \"auto\"\n          },\n          \"unit\": \"reqps\"\n        }\n      },\n      \"options\": {\n        \"tooltip\": {\"mode\": \"multi\"},\n        \"legend\": {\"displayMode\": \"list\", \"placement\": \"bottom\"}\n      }\n    },\n    {\n      \"type\": \"timeseries\",\n      \"title\": \"Response Time (P95 latency)\",\n      \"gridPos\": {\"x\": 12, \"y\": 1, \"w\": 12, \"h\": 8},\n      \"datasource\": {\"type\": \"prometheus\", \"uid\": \"$${datasource}\"},\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(rate(http.server.request.duration_bucket{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\"}[1m])) by (vmrange, http.route))\",\n          \"legendFormat\": \"P95 - {{http.route}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.50, sum(rate(http.server.request.duration_bucket{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\"}[1m])) by (vmrange))\",\n          \"legendFormat\": \"P50 (median)\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\"mode\": \"palette-classic\"},\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisLabel\": \"seconds\",\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"lineWidth\": 2\n          },\n          \"unit\": \"s\",\n          \"links\": [\n            {\n              \"title\": \"View traces for this time range\",\n              \"url\": \"?viewPanel=1000&var-min_trace_duration=500ms&$${__url_time_range}\"\n            }\n          ]\n        }\n      },\n      \"options\": {\n        \"tooltip\": {\"mode\": \"multi\"},\n        \"legend\": {\"displayMode\": \"list\", \"placement\": \"bottom\"}\n      }\n    },\n    {\n      \"type\": \"stat\",\n      \"title\": \"Error Rate %\",\n      \"gridPos\": {\"x\": 0, \"y\": 9, \"w\": 6, \"h\": 4},\n      \"datasource\": {\"type\": \"prometheus\", \"uid\": \"$${datasource}\"},\n      \"targets\": [\n        {\n          \"expr\": \"sum(rate(http.server.request.duration_count{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\",http.response.status_code=~\\\"5..\\\"}[1m])) / sum(rate(http.server.request.duration_count{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\"}[1m])) * 100\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\"mode\": \"thresholds\"},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\"color\": \"green\", \"value\": null},\n              {\"color\": \"yellow\", \"value\": 1},\n              {\"color\": \"orange\", \"value\": 5},\n              {\"color\": \"red\", \"value\": 10}\n            ]\n          },\n          \"unit\": \"percent\",\n          \"decimals\": 2\n        }\n      },\n      \"options\": {\n        \"colorMode\": \"background\",\n        \"graphMode\": \"area\",\n        \"textMode\": \"value_and_name\"\n      }\n    },\n    {\n      \"type\": \"stat\",\n      \"title\": \"Request Rate\",\n      \"gridPos\": {\"x\": 6, \"y\": 9, \"w\": 6, \"h\": 4},\n      \"datasource\": {\"type\": \"prometheus\", \"uid\": \"$${datasource}\"},\n      \"targets\": [\n        {\n          \"expr\": \"sum(rate(http.server.request.duration_count{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\"}[1m]))\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\"mode\": \"palette-classic\"},\n          \"unit\": \"reqps\",\n          \"decimals\": 1\n        }\n      },\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"textMode\": \"value_and_name\"\n      }\n    },\n    {\n      \"type\": \"stat\",\n      \"title\": \"P95 Latency\",\n      \"gridPos\": {\"x\": 12, \"y\": 9, \"w\": 6, \"h\": 4},\n      \"datasource\": {\"type\": \"prometheus\", \"uid\": \"$${datasource}\"},\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(rate(http.server.request.duration_bucket{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\"}[1m])) by (vmrange))\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\"mode\": \"thresholds\"},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\"color\": \"green\", \"value\": null},\n              {\"color\": \"yellow\", \"value\": 0.5},\n              {\"color\": \"orange\", \"value\": 1},\n              {\"color\": \"red\", \"value\": 5}\n            ]\n          },\n          \"unit\": \"s\",\n          \"decimals\": 3\n        }\n      },\n      \"options\": {\n        \"colorMode\": \"background\",\n        \"graphMode\": \"area\",\n        \"textMode\": \"value_and_name\"\n      }\n    },\n    {\n      \"type\": \"stat\",\n      \"title\": \"Total 5xx Errors (last 5m)\",\n      \"gridPos\": {\"x\": 18, \"y\": 9, \"w\": 6, \"h\": 4},\n      \"datasource\": {\"type\": \"prometheus\", \"uid\": \"$${datasource}\"},\n      \"targets\": [\n        {\n          \"expr\": \"sum(increase(http.server.request.duration_count{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\",http.response.status_code=~\\\"5..\\\"}[5m]))\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\"mode\": \"thresholds\"},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\"color\": \"green\", \"value\": null},\n              {\"color\": \"red\", \"value\": 1}\n            ]\n          },\n          \"unit\": \"short\",\n          \"decimals\": 0\n        }\n      },\n      \"options\": {\n        \"colorMode\": \"background\",\n        \"graphMode\": \"none\",\n        \"textMode\": \"value_and_name\"\n      }\n    },\n    {\n      \"type\": \"timeseries\",\n      \"title\": \"Request Count by Status Code\",\n      \"gridPos\": {\"x\": 0, \"y\": 13, \"w\": 24, \"h\": 6},\n      \"datasource\": {\"type\": \"prometheus\", \"uid\": \"$${datasource}\"},\n      \"targets\": [\n        {\n          \"expr\": \"sum by (http.response.status_code) (rate(http.server.request.duration_count{service.name=\\\"$service\\\",http.route!~\\\"/healthz|/readyz\\\"}[1m]))\",\n          \"legendFormat\": \"HTTP {{http.response.status_code}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\"mode\": \"palette-classic\"},\n          \"custom\": {\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"lineWidth\": 2,\n            \"stacking\": {\"mode\": \"normal\"}\n          },\n          \"unit\": \"reqps\"\n        },\n        \"overrides\": [\n          {\n            \"matcher\": {\"id\": \"byRegexp\", \"options\": \".*5.*\"},\n            \"properties\": [\n              {\"id\": \"color\", \"value\": {\"mode\": \"fixed\", \"fixedColor\": \"red\"}}\n            ]\n          },\n          {\n            \"matcher\": {\"id\": \"byRegexp\", \"options\": \".*200.*\"},\n            \"properties\": [\n              {\"id\": \"color\", \"value\": {\"mode\": \"fixed\", \"fixedColor\": \"green\"}}\n            ]\n          }\n        ]\n      },\n      \"options\": {\n        \"tooltip\": {\"mode\": \"multi\"},\n        \"legend\": {\"displayMode\": \"list\", \"placement\": \"bottom\"}\n      }\n    },\n    {\n      \"type\": \"row\",\n      \"title\": \"\U0001F4CB Error Logs with Context\",\n      \"gridPos\": {\"x\": 0, \"y\": 19, \"w\": 24, \"h\": 1},\n      \"collapsed\": false\n    },\n    {\n      \"type\": \"logs\",\n      \"title\": \"Application Error Logs (Click on log line to see \xB17 lines of context)\",\n      \"description\": \"Shows error-level logs from the application. Click any log line to view surrounding context (\xB17 lines before/after).\",\n      \"gridPos\": {\"x\": 0, \"y\": 20, \"w\": 24, \"h\": 10},\n      \"datasource\": {\"type\": \"victoriametrics-logs-datasource\", \"uid\": \"$${logs_datasource}\"},\n      \"targets\": [\n        {\n          \"expr\": \"{kubernetes.container_name=$$service} | unpack_json | log.level:error\",\n          \"refId\": \"A\",\n          \"queryType\": \"raw\"\n        }\n      ],\n      \"options\": {\n        \"showTime\": true,\n        \"showLabels\": false,\n        \"showCommonLabels\": false,\n        \"wrapLogMessage\": true,\n        \"prettifyLogMessage\": true,\n        \"enableLogDetails\": true,\n        \"dedupStrategy\": \"none\",\n        \"sortOrder\": \"Descending\",\n        \"enableContext\": true\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        }\n      }\n    },\n    {\n      \"type\": \"row\",\n      \"title\": \"\U0001F50D TRACES: Search & Service Map\",\n      \"gridPos\": {\"x\": 0, \"y\": 30, \"w\": 24, \"h\": 1},\n      \"collapsed\": false\n    },\n    {\n      \"id\": 1000,\n      \"type\": \"traces\",\n      \"title\": \"Recent Traces (pick a slow one to open the waterfall)\",\n      \"description\": \"Trace search against VictoriaTraces via the Jaeger-compatible API, filtered to this service. Linked from the P95 latency panel, which sets a minimum duration to land on the slow traces.\",\n      \"gridPos\": {\"x\": 0, \"y\": 31, \"w\": 12, \"h\": 10},\n      \"datasource\": {\"type\": \"jaeger\", \"uid\": \"$${trace_datasource}\"},\n      \"targets\": [\n        {\n          \"queryType\": \"search\",\n          \"service\": \"$${service}\",\n          \"operation\": \"\",\n          \"tags\": \"\",\n          \"minDuration\": \"$${min_trace_duration}\",\n          \"maxDuration\": \"\",\n          \"limit\": 20,\n          \"refId\": \"A\"\n        }\n      ],\n      \"options\": {}\n    },\n    {\n      \"type\": \"nodeGraph\",\n      \"title\": \"Service Map\",\n      \"description\": \"Derived from the same trace search via the datasource's serviceMap/nodeGraph support.\",\n      \"gridPos\": {\"x\": 12, \"y\": 31, \"w\": 12, \"h\": 10},\n      \"datasource\": {\"type\": \"jaeger\", \"uid\": \"$${trace_datasource}\"},\n      \"targets\": [\n        {\n          \"queryType\": \"search\",\n          \"service\": \"$${service}\",\n          \"operation\": \"\",\n          \"tags\": \"\",\n          \"minDuration\": \"\",\n          \"maxDuration\": \"\",\n          \"limit\": 20,\n          \"refId\": \"A\"\n        }\n      ],\n      \"options\": {}\n    }\n  ]\n}\n"
🟡 changed — grafana.integreatly.org/v1beta1/GrafanaDatasource/observability/vl-datasource
--- a/grafana.integreatly.org/v1beta1/GrafanaDatasource/observability/vl-datasource
+++ b/grafana.integreatly.org/v1beta1/GrafanaDatasource/observability/vl-datasource
@@ -9,13 +9,15 @@
     access: proxy
     jsonData:
       derivedFields:
-      - datasourceName: VictoriaTraces
-        matcherRegex: '"log\.trace_id":"([0-9a-f]+)"'
+      - datasourceUid: victoriatraces
+        matcherRegex: log.trace_id
+        matcherType: label
         name: TraceID
         url: $${__value.raw}
         urlDisplayLabel: View Trace
     name: VictoriaLogs
     type: victoriametrics-logs-datasource
+    uid: victorialogs
     url: http://victoria-logs-victoria-logs-single-server.observability:9428
   instanceSelector:
     matchLabels:
🟡 changed — grafana.integreatly.org/v1beta1/GrafanaDatasource/observability/vt-datasource
--- a/grafana.integreatly.org/v1beta1/GrafanaDatasource/observability/vt-datasource
+++ b/grafana.integreatly.org/v1beta1/GrafanaDatasource/observability/vt-datasource
@@ -11,33 +11,27 @@
       nodeGraph:
         enabled: true
       serviceMap:
-        datasourceName: VictoriaMetrics
-      tracesToLogs:
-        datasourceName: VictoriaLogs
+        datasourceUid: VictoriaMetrics
+      tracesToLogsV2:
+        datasourceUid: victorialogs
         filterBySpanID: false
         filterByTraceID: true
-        mappedTags:
-        - key: service.name
-          value: service_name
         spanEndTimeShift: 1h
         spanStartTimeShift: -1h
-        tags:
-        - trace_id
-        - traceId
-        - traceID
       tracesToMetrics:
-        datasourceName: VictoriaMetrics
+        datasourceUid: VictoriaMetrics
         queries:
         - name: Request rate
-          query: rate(http_server_requests_total{$$__tags}[5m])
-        - name: Request duration
-          query: histogram_quantile(0.95, rate(http_server_duration_seconds_bucket{$$__tags}[5m]))
+          query: sum(rate(http.server.request.duration_count{$$__tags}[5m]))
+        - name: Request duration (p95)
+          query: histogram_quantile(0.95, sum(rate(http.server.request.duration_bucket{$$__tags}[5m])) by (vmrange))
         tags:
-        - service.name
-        - http.method
-        - http.status_code
+        - key: service.name
+        - key: http.request.method
+        - key: http.response.status_code
     name: VictoriaTraces
     type: jaeger
+    uid: victoriatraces
     url: http://victoria-traces-vt-single-server.observability:10428/select/jaeger
   instanceSelector:
     matchLabels:

@Smana
Smana merged commit c858d47 into main Sep 13, 2026
9 checks passed
@Smana
Smana deleted the worktree-otel-dashboard branch September 13, 2026 10:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant