fix(contrib/net/http): opt-in single server span for instrumented routers#5009
fix(contrib/net/http): opt-in single server span for instrumented routers#5009kakkoyun wants to merge 1 commit into
Conversation
…ters Under Orchestrion, net/http wraps http.Server.Serve and starts a server span, then a router's middleware starts a second server span parented to it, so a router endpoint produces two http.request/span.kind:server spans. The outer net/http span stamps error.msg on the trace root and hides the error set on the router span, and request counts double when grouped by http.request. Add DD_TRACE_HTTP_ROUTER_ROOT_SPAN (default off). When enabled, net/http skips wrapping a handler whose type a router integration registered via httptrace.RegisterRoutingHandlerType, so the router span becomes the trace root. The guard lives in the shared wrap.Handler, so it applies to both Orchestrion and manual WrapHandler. Fixes #3369
Config Audit |
|
BenchmarksBenchmark execution time: 2026-07-09 14:38:25 Comparing candidate commit b5433de in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 326 metrics, 0 unstable metrics, 1 flaky benchmarks without significant changes.
|
What
Adds an opt-in environment variable
DD_TRACE_HTTP_ROUTER_ROOT_SPAN(default off). When enabled, the net/http server instrumentation does not wrap a handler that is already an instrumented router (chi, gin, echo, gorilla/mux, httprouter). The router's span becomes the trace root instead of a redundant net/httphttp.requestserver span above it.Why
Fixes #3369. Under Orchestrion every layer is instrumented: the net/http aspect wraps
http.Server.Serveand starts a server span, then the router middleware starts a second server span parented to it. Both arespan.kind:server,type:web,name:http.request. Two problems reported on the issue:error.msg(e.g.500: Internal Server Error) on the trace root, hiding the error the router set on its own span, so Error Tracking queries the root and loses the real message.http.request.How
instrumentation/httptrace:RegisterRoutingHandlerType[T]andIsRoutingHandler. Each router contrib registers, in itsinit(), the concrete type it assigns tohttp.Server.Handler.wrap.Handler(used by both Orchestrion and the manualWrapHandler) returns a registered handler unwrapped when the flag is on.http.TimeoutHandler) does not match, so the redundant span is kept. The worst case of a miss is an extra span, not a lost trace.Covered integrations: chi (v1, v5), gin, gorilla/mux, julienschmidt/httprouter, labstack/echo (v4, v5). fasthttp and fiber are unaffected because they do not use net/http.
Test plan
wrap.Handlerskip on and off, through both the Orchestrion and the manualWrapHandlerentry points.TestRouterRootcases enable the flag and assert a single server span (client then router). Runs receive exactly 2 spans.go build,go vet,gofmt, andgo test -racepass on every touched module.