temporalio / temporalio/sdk-rust
[Feature Request] Support Prometheus exemplars on Core metrics
@Sushisource is already working on this.
Since Aug 25, 2026.
- Dominant language
- Rust
- Stars
- 523
- Forks
- 161
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 67
Description
Status: proposal / request for comment
Motivation
Core's Prometheus metrics use a deliberately low-cardinality label set: namespace, task_queue, workflow_type, activity_type, poller_type. Per-execution identifiers — run_id, workflow_id, activity_id — are absent, correctly, since as labels they would multiply series count by every execution the worker handles.
The cost is that latency histograms are unactionable at the tail. When temporal_workflow_task_execution_latency p99 jumps, the metrics establish that something was slow and offer no path to what. Exemplars are the OpenMetrics feature for exactly this: a bounded number of high-cardinality sample pointers attached to buckets, stored outside the series and ignored by aggregation.
For Core this is unusually cheap, because the identifiers are already in scope at the record sites. In crates/sdk-core/src/worker/workflow/managed_run.rs, the call recording workflow task latency sits two lines from an existing use of self.wfm.machines.run_id. No propagation, no changes to the worker/client boundary.
Proposal
- An
exemplarsoption onPrometheusExporterOptions, default off. When off, exposition output is byte-identical to today. - Core attaches
run_idwhere it already has it — starting with the workflow task and end-to-end latency histograms. - Lang SDKs can supply exemplar labels for custom metrics, via an additive trait method with a defaulted body, so existing
CoreMeterimplementations compile unchanged and ignore exemplars. - OpenMetrics exposition, content-negotiated. Exemplars have no representation in text format 0.0.4, so emit OpenMetrics on
Accept: application/openmetrics-textwhen the option is on; every other request keeps the currentTextEncoderpath.
Non-goals: trace_id exemplars — Core has no distributed tracing, tracing-opentelemetry is not a dependency and SpanContext/trace_id appear nowhere in crates/common/src/telemetry/, so SDKs would supply these via item 3; gauges, which OpenMetrics disallows; and the OTel and buffered meters, which ignore exemplars via the defaulted method.
Implementation notes
The prometheus crate's histogram type has no exemplar slot, and we depend on it with default-features = false, so there is no Exemplar type in scope either. Cheapest path is a side table on the custom Registry in prometheus_meter.rs, keyed by family name, label set and bucket index, plus a small OpenMetrics encoder. Switching to prometheus-client would provide both natively but means rewriting ~900 lines, including the custom registry that permits identical names with differing label sets.
Exemplar label validation (OpenMetrics: name charset, combined length ≤128 UTF-8 characters) belongs in Core so there is one implementation for all SDKs. Invalid exemplars get dropped with a warning and the observation still recorded — instrumentation must not break the caller.
What I would like decided
- Which internal metrics get automatic exemplars, and carrying which identifier?
- Should automatic
run_idexemplars be implied byexemplars = true, or be a separate opt-in? Some operators will want the mechanism for their own custom metrics only. - OpenMetrics text, or enable the
prometheuscrate'sprotobuffeature instead? Protobuf avoids hand-writing an encoder but is not widely enabled on scrapers.
Two smaller notes: exemplar storage is still feature-gated on the Prometheus server, so operators must opt in there as well; and OpenMetrics requires counters to end in _total, which conflicts with counters_total_suffix defaulting to false — simplest is to force the suffix on OpenMetrics responses only.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.