Prerequisites

  • Python with opentelemetry-api, opentelemetry-sdk, and prometheus-client for the standalone demonstration.
  • For the end-to-end integration: a scraped application, exemplar-enabled Prometheus-compatible storage, a trace backend such as Tempo, and Grafana access to both.

Install dependencies

python3 -m pip install opentelemetry-api opentelemetry-sdk prometheus-client

Runnable example

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor, ConsoleSpanExporter
from prometheus_client import Histogram, CollectorRegistry
from prometheus_client.openmetrics.exposition import generate_latest

provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(provider)
tracer = trace.get_tracer("exemplar-demo")
registry = CollectorRegistry()
latency = Histogram("checkout_duration_seconds", "Checkout duration", registry=registry)
with tracer.start_as_current_span("checkout"):
    ctx = trace.get_current_span().get_span_context()
    exemplar = {"trace_id": format(ctx.trace_id, "032x")} if ctx.is_valid and ctx.trace_flags.sampled else None
    latency.observe(0.42, exemplar=exemplar)
print(generate_latest(registry).decode())
provider.shutdown()

Connect your backend

The demo prints OpenMetrics text and a console span. It does not run an HTTP endpoint or upload a trace. In an application, observe the measured duration inside the active span, expose OpenMetrics through your metric endpoint, and export that same trace to Tempo. Enable exemplar storage in self-managed Prometheus using --enable-feature=exemplar-storage where required by your installed version. In Grafana’s Prometheus data source, add an exemplar internal link: select your Tempo data source and set Label name to trace_id, matching this example. Use container/network hostnames appropriate to your deployment.

Verification checklist

  1. Run the example and inspect the histogram bucket exemplar: its trace_id must match the console-exported span.
  2. Check the deployed /metrics endpoint using an Accept: application/openmetrics-text header. Confirm an exemplar is present before troubleshooting Grafana.
  3. Verify storage ingestion using Prometheus’s /api/v1/query_exemplars endpoint with the checkout_duration_seconds_bucket series and the relevant time window.
  4. Query checkout_duration_seconds_bucket in Grafana Explore, enable exemplars where available, and open a marker. Confirm the linked trace ID and operation in Tempo.

Common failures

Troubleshooting the connection
SymptomCheck
No exemplar in expositionUse a supported histogram/counter and OpenMetrics negotiation; a plain Prometheus text response omits exemplar data.
Exposition works but storage is emptyCheck exemplar storage, scrape protocol, and any remote-write path preserving exemplars.
Marker opens a missing traceHead or tail sampling, failed export, tenant mismatch, and shorter trace retention can leave dangling links. Sampled context does not guarantee final retention.
High-cardinality seriestrace_id belongs in the exemplar, never in the histogram’s ordinary label set. An exemplar is a selected observation, not the identity of every request.

Related concepts

Related signals

Related patterns

Related guides

Official documentation