Skip to content

fix(server,grpc): continue inbound W3C traces in HTTP middleware - #60

Merged
jasoet merged 1 commit into
nextfrom
fix/http-traceparent-extraction
Aug 17, 2026
Merged

fix(server,grpc): continue inbound W3C traces in HTTP middleware#60
jasoet merged 1 commit into
nextfrom
fix/http-traceparent-extraction

Conversation

@jasoet

@jasoet jasoet commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Problem

Both HTTP tracing middlewares started their server span from the raw request context:

ctx, span := tracer.Start(req.Context(), ...)   // server/otel_middleware.go
ctx, span := tracer.Start(ctx, ...)             // grpc gateway

No Extract, so an inbound traceparent header was discarded and every server span became a new root.

This is inconsistent within the library itself:

path behaviour
rest client outbound injects traceparent
grpc unary/stream interceptors extracts ✅
server HTTP middleware dropped
grpc gateway HTTP middleware dropped

So a restserver call made with this library on both ends rendered as two disconnected traces.

Change

Extract W3C trace context + baggage from request headers before starting the span, reusing the composite propagator the gRPC interceptors already use. With no inbound headers this is a no-op and the span is still a root.

Tests (written first, observed failing)

New tests assert the span joins the caller's trace. Before the fix:

expected: "4bf92f3577b34da6a3ce929d0e0e4736"
actual  : "a1be0badcd4d1db205624bba8acf1c48"   <- fresh root
expected: "00f067aa0ba902b7"
actual  : "0000000000000000"                   <- no parent

Added for both packages, plus a companion test pinning the no-header case to a root span so the fix can't regress into always-remote-parent.

Verification

task ci:check — all packages pass, golangci-lint 0 issues. go vet -tags='example integration argo' ./... clean.

Note for the migration guide

Existing users gain correctly-parented server spans. Traces that previously appeared as separate roots will now join their caller — a visible change in any tracing backend, in the correct direction. Closes the "Shared gap (server + grpc)" item in docs/plans/2026-07-22-v3-audit-backlog.md.

Both HTTP tracing middlewares started their server span from the raw request
context, so an inbound traceparent header was discarded and every span became
a new root. A caller instrumented with this library's own rest client injects
traceparent on the way out (rest/otel_middleware.go), and the grpc interceptors
extract it on the way in — but the HTTP side of server and the gRPC gateway did
not, so a rest -> server hop rendered as two disconnected traces.

Extract W3C trace context and baggage from the request headers before starting
the span, using the same composite propagator the gRPC interceptors already
use. With no inbound headers this is a no-op and the span is still a root.

Covers both packages together because they share the defect and the fix, and a
consumer hitting one usually hits the other.
@jasoet
jasoet merged commit 92d9045 into next Aug 17, 2026
1 check passed
@jasoet
jasoet deleted the fix/http-traceparent-extraction branch August 17, 2026 11:15
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