Manager env block (downward-API resource attrs, commented OTLP examples), architecture §10 + Decisions entries, README section. Lint: goconst constants, gofmt, logcheck (Setup now takes its logger from ctx via logf.FromContext). Co-Authored-By: Claude <noreply@anthropic.com>
66 lines
2.7 KiB
Go
66 lines
2.7 KiB
Go
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.
|
|
//
|
|
//nolint:logcheck // IntoContext-analogue: taking ctx and the logger to seed it with is the point.
|
|
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())
|
|
}
|