feat: add explicit tracer header (#117686)

* feat: add explicit tracer header

* update-workspace

* chore: review feedback

* update workspace
This commit is contained in:
Costa Alexoglou 2026-02-17 15:08:57 +01:00 committed by GitHub
parent c74d6d3d1f
commit c5604d6d64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 123 additions and 1 deletions

View file

@ -0,0 +1,28 @@
package filters
import (
"net/http"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
)
// WithExtractUpstreamTraceLink extracts the Grafana-Upstream-Traceparent
// custom header (injected by the ST proxy) and adds a span link to the
// current span. This preserves trace relationships across the vanilla K8s
// API server, which drops incoming W3C trace context.
func WithExtractUpstreamTraceLink(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
upstreamTP := req.Header.Get("Grafana-Upstream-Traceparent")
if upstreamTP != "" {
prop := propagation.TraceContext{}
carrier := propagation.MapCarrier{"traceparent": upstreamTP}
extractedCtx := prop.Extract(req.Context(), carrier)
if sc := trace.SpanContextFromContext(extractedCtx); sc.IsValid() && sc.IsRemote() {
currentSpan := trace.SpanFromContext(req.Context())
currentSpan.AddLink(trace.Link{SpanContext: sc})
}
}
handler.ServeHTTP(w, req)
})
}

View file

@ -0,0 +1,94 @@
package filters
import (
"net/http"
"net/http/httptest"
"testing"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
)
func TestWithExtractUpstreamTraceLink(t *testing.T) {
tests := []struct {
name string
headerValue string
expectLink bool
expectCalled bool
}{
{
name: "valid upstream traceparent adds span link",
headerValue: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
expectLink: true,
expectCalled: true,
},
{
name: "missing header is a no-op",
headerValue: "",
expectLink: false,
expectCalled: true,
},
{
name: "invalid traceparent is a no-op",
headerValue: "not-a-valid-traceparent",
expectLink: false,
expectCalled: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
recorder := tracetest.NewSpanRecorder()
tp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder))
tracer := tp.Tracer("test")
called := false
inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
called = true
w.WriteHeader(http.StatusOK)
})
handler := WithExtractUpstreamTraceLink(inner)
req := httptest.NewRequest(http.MethodGet, "/test", nil)
if tc.headerValue != "" {
req.Header.Set("Grafana-Upstream-Traceparent", tc.headerValue)
}
// Start a span to simulate WithTracing having already run.
ctx, span := tracer.Start(req.Context(), "test-span")
req = req.WithContext(ctx)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
span.End()
if called != tc.expectCalled {
t.Errorf("expected inner handler called=%v, got %v", tc.expectCalled, called)
}
spans := recorder.Ended()
if len(spans) != 1 {
t.Fatalf("expected 1 span, got %d", len(spans))
}
links := spans[0].Links()
if tc.expectLink {
if len(links) != 1 {
t.Fatalf("expected 1 span link, got %d", len(links))
}
if got := links[0].SpanContext.TraceID().String(); got != "4bf92f3577b34da6a3ce929d0e0e4736" {
t.Errorf("unexpected link trace ID: %s", got)
}
if got := links[0].SpanContext.SpanID().String(); got != "00f067aa0ba902b7" {
t.Errorf("unexpected link span ID: %s", got)
}
} else {
if len(links) != 0 {
t.Errorf("expected no span links, got %d", len(links))
}
}
})
}
}

View file

@ -10,6 +10,7 @@ require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/contrib/propagators/jaeger v1.39.0
go.opentelemetry.io/otel v1.40.0
go.opentelemetry.io/otel/sdk v1.40.0
go.opentelemetry.io/otel/trace v1.40.0
k8s.io/apimachinery v0.35.1
k8s.io/apiserver v0.35.1
@ -84,7 +85,6 @@ require (
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect