feat: emit OpenTelemetry exemplars from HTTP duration histogram - #826
Merged
thlpkee20-wq merged 3 commits intoAug 31, 2026
Merged
Conversation
Wire exemplars on http_request_duration_seconds using the active OTel span context so operators can jump directly from Grafana panels to Tempo traces. - Add tracing.ExemplarLabels(ctx) as the canonical trace→exemplar helper - Refactor metrics.MetricsMiddleware to use tracing.ExemplarLabels - Add middleware.ExemplarAwareMiddleware for span propagation checks - Add 29 new tests covering happy paths, edge cases, concurrency, backward compatibility, and failure modes - Fix pre-existing compile errors in tracing package Closes Stellabill#792 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
|
@graceanya796-maker Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat: emit OpenTelemetry exemplars from HTTP duration histogram
Closes #792
Summary
Wire OpenTelemetry exemplars on the
http_request_duration_secondsPrometheus histogram so operators can jump directly from Grafana dashboards to a specific Tempo trace for any slow request, eliminating the manual context-switch that was previously required.Motivation
The Prometheus histograms emitted by the HTTP middleware did not carry exemplars. Operators had to manually copy a request ID, search Tempo, and hope they found the right trace. With exemplars, every histogram bucket observation is tagged with
trace_idandspan_id, enabling one-click drill-down from metric panels to distributed traces.Implementation
internal/tracing/tracing.go— canonical exemplar extractionAdded
ExemplarLabels(ctx context.Context) prometheus.Labels— a reusable, well-documented helper that:trace.SpanFromContextIsValid,IsSampled,IsRecording)nilfor unsampled, non-recording, invalid, or absent spansprometheus.Labels{"trace_id": ..., "span_id": ...}for active spansThis centralises the trace→exemplar conversion in the tracing package so any downstream consumer can use it without importing OTel internals.
internal/metrics/metrics.go— exemplar-aware HTTP histogramRefactored
MetricsMiddlewareto calltracing.ExemplarLabels()instead of the now-removed privatespanExemplar(). The middleware:HTTPRequestDurationtracing.ExemplarLabels(ctx)to extract exemplar labelsprometheus.ExemplarObserver, callsObserveWithExemplar(duration, exemplars)— attachingtrace_idandspan_idObserve(duration)when exemplars are unavailable (unsampled spans, no active span)This preserves full backward compatibility: Prometheus scrapes that don't enable exemplar storage see no change.
internal/middleware/middleware.go— exemplar-aware middlewareAdded
ExemplarAwareMiddleware— a contract-checkpoint middleware that:X-Exemplar-Available: trueresponse header when the span is sampled and recordingotelgin.Middleware) in the handler chainFiles Changed
internal/tracing/tracing.goExemplarLabels()helper + imports (prometheus,trace)internal/metrics/metrics.gospanExemplar()withtracing.ExemplarLabels()call; removed directotel/traceimportinternal/middleware/middleware.goExemplarAwareMiddleware+traceimportinternal/tracing/exemplar_test.goExemplarLabelsinternal/tracing/sampler.gointernal/tracing/sampler_test.gointernal/tracing/tail_sampling_test.gointernal/metrics/metrics_test.gotracing.ExemplarLabels; added 10 new testsinternal/middleware/exemplar_test.goExemplarAwareMiddlewareTest Coverage
internal/tracing/exemplar_test.go(12 tests)TestExemplarLabels_SampledRecordingTestExemplarLabels_UnsampledTestExemplarLabels_NoSpanTestExemplarLabels_EndedSpanTestExemplarLabels_InvalidSpanContextTestExemplarLabels_SampledButNotRecordingTestExemplarLabels_VerifyHexFormatTestExemplarLabels_CorruptContextTestExemplarLabels_ConcurrentSafetyTestExemplarLabels_ConsistencyAcrossCallsTestExemplarLabels_TraceIDMatchesSpanContextspan.SpanContext()valuesTestExemplarLabels_NeverSampleProviderinternal/metrics/metrics_test.go(10 exemplar-specific tests)TestExemplarLabels_SampledRecordingtracing.ExemplarLabels— happy pathTestExemplarLabels_UnsampledTestExemplarLabels_NoSpanTestExemplarLabels_EndedSpanTestExemplarLabels_EmptyTraceIDTestExemplarLabels_CorruptContextTestMetricsMiddleware_ExemplarAttachedOnSampledRequestTestMetricsMiddleware_NoExemplarOnUnsampledRequestTestMetricsMiddleware_ExemplarTraceIDAndSpanIDInLabelsTestMetricsMiddleware_ExemplarFallbackToPlainObserveTestMetricsMiddleware_BackwardCompatibilityTestExemplarLabels_ConcurrentSafetyTestExemplarLabels_MultipleRequestsEachGetUniqueTraceIDTestExemplarLabels_VerifyLabelValuesTestMetricsMiddleware_ExemplarDoesNotBreakDBTimerinternal/middleware/exemplar_test.go(7 tests)TestExemplarAwareMiddleware_SampledSpanX-Exemplar-Available: trueheaderTestExemplarAwareMiddleware_NoSpanTestExemplarAwareMiddleware_UnsampledSpanTestExemplarAwareMiddleware_EndedSpanTestExemplarAwareMiddleware_PreservesNextHandlerStatusTestExemplarAwareMiddleware_BackwardCompatibilityTestExemplarAwareMiddleware_InvalidSpanContextSecurity & Data-Integrity
trace_idandspan_id(opaque 128-bit and 64-bit identifiers) are attached — no user data, tenant IDs, or request bodies.prometheus.ExemplarObservertype assertion ensures the code falls back to plainObserve()if the histogram doesn't support exemplars. No existing scrape configurations break.Failure Modes
ExemplarLabelsreturns nil → plainObserve()ExemplarLabelsreturns nil → plainObserve()ExemplarLabelsreturns nil → plainObserve()ExemplarLabelsreturns nil → plainObserve()ExemplarObserverObserve()ExemplarLabelsis goroutine-safe (read-only on immutable span context)Backward Compatibility
--enable-feature=exemplar-storageenabled.tracing.ExemplarLabels()andmiddleware.ExemplarAwareMiddleware().prometheus/client_golangandgo.opentelemetry.io/otelversions already ingo.mod.Scrape Configuration
To expose exemplars in Prometheus/Grafana, ensure your scrape config includes:
Grafana panels can then use the exemplar link to jump directly to Tempo traces.
Regression Coverage
TestMetricsMiddleware_ExemplarDoesNotBreakDBTimer)TestExemplarLabels_ConcurrentSafety)