package tracing import ( "context" "github.com/go-logr/logr" "go.opentelemetry.io/otel/trace" logf "sigs.k8s.io/controller-runtime/pkg/log" ) // baseLoggerKey stores the pre-enrichment logger. logr sinks never see a // context, so trace IDs must ride on the logger as WithValues — and zap does // not dedupe repeated keys, so each nested span must re-derive from the same // base instead of stacking traceID/spanID onto an already-enriched logger. type baseLoggerKey struct{} // Start begins a span from the global tracer and returns a context whose // logf.FromContext logger carries the span's traceID/spanID. With tracing // disabled the span is a no-op with an invalid span context and the logger // is left untouched. // // Trade-off, documented: values pushed via logf.IntoContext *between* two // Start calls are dropped by the inner Start's re-derivation. Nothing // first-party does that; controller-runtime's per-reconcile logger // (reconcileID etc.) is captured as the base and survives. func Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) { return StartSpan(ctx, newOptions(nil).tracer(), name, opts...) } // StartSpan is Start with an explicit tracer, for decorators that carry // their own (test-injected) TracerProvider. func StartSpan(ctx context.Context, tracer trace.Tracer, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) { ctx, span := tracer.Start(ctx, name, opts...) sc := span.SpanContext() if !sc.IsValid() { return ctx, span } base, ok := ctx.Value(baseLoggerKey{}).(logr.Logger) if !ok { base = logf.FromContext(ctx) ctx = context.WithValue(ctx, baseLoggerKey{}, base) } return logf.IntoContext(ctx, withSpanValues(base, sc)), span } // ContextWithLogger seeds ctx with base as both the current logger and the // base for span enrichment. If ctx already carries a valid span (the HTTP // middleware calls this inside the otelhttp handler), the logger is // enriched immediately. func ContextWithLogger(ctx context.Context, base logr.Logger) context.Context { ctx = context.WithValue(ctx, baseLoggerKey{}, base) if sc := trace.SpanContextFromContext(ctx); sc.IsValid() { return logf.IntoContext(ctx, withSpanValues(base, sc)) } return logf.IntoContext(ctx, base) } // withSpanValues uses lowerCamel keys to match the house style // (reconcileID, providerID); Grafana/Loki derived fields must match // "traceID", not the trace_id default. func withSpanValues(base logr.Logger, sc trace.SpanContext) logr.Logger { return base.WithValues("traceID", sc.TraceID().String(), "spanID", sc.SpanID().String()) }