mirror of
https://github.com/grafana/grafana.git
synced 2026-02-18 18:20:52 -05:00
feat: add explicit tracer header (#117686)
* feat: add explicit tracer header * update-workspace * chore: review feedback * update workspace
This commit is contained in:
parent
c74d6d3d1f
commit
c5604d6d64
3 changed files with 123 additions and 1 deletions
28
pkg/apiserver/endpoints/filters/upstream_trace_link.go
Normal file
28
pkg/apiserver/endpoints/filters/upstream_trace_link.go
Normal 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)
|
||||
})
|
||||
}
|
||||
94
pkg/apiserver/endpoints/filters/upstream_trace_link_test.go
Normal file
94
pkg/apiserver/endpoints/filters/upstream_trace_link_test.go
Normal 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))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue