diff --git a/middleware/README.md b/middleware/README.md index de377c2..d6c4f73 100644 --- a/middleware/README.md +++ b/middleware/README.md @@ -115,15 +115,13 @@ func Timeout(d time.Duration) workers.Middleware Timeout enforces a per\-cycle deadline. Distinct from \[workers.Worker.WithTimeout\] which controls graceful shutdown. -## func [Tracing]() +## func [Tracing]() ```go func Tracing() workers.Middleware ``` -Tracing creates an OTEL span per cycle via go\-coldbrew/tracing. The span is named "worker:\:cycle" and records errors. - -Worker spans are always sampled regardless of the global TracerProvider's sampler. This prevents silent span drops when using ParentBased\(TraceIDRatioBased\(ratio\)\), where worker root spans \(which have no parent\) would otherwise be probabilistically dropped. +Tracing creates an OTEL span per cycle via go\-coldbrew/tracing. The span is named "worker:\:cycle" and records errors. In typical use the span is a trace root because worker contexts carry no parent span; sampling is determined by the global TracerProvider's sampler. The OTEL trace ID is injected into the log context as "trace" for correlation with the tracing backend. diff --git a/middleware/tracing.go b/middleware/tracing.go index e52084c..d877259 100644 --- a/middleware/tracing.go +++ b/middleware/tracing.go @@ -2,7 +2,6 @@ package middleware import ( "context" - "crypto/rand" "github.com/go-coldbrew/log" "github.com/go-coldbrew/tracing" @@ -12,18 +11,14 @@ import ( // Tracing creates an OTEL span per cycle via go-coldbrew/tracing. // The span is named "worker::cycle" and records errors. -// -// Worker spans are always sampled regardless of the global -// TracerProvider's sampler. This prevents silent span drops when -// using ParentBased(TraceIDRatioBased(ratio)), where worker root -// spans (which have no parent) would otherwise be probabilistically -// dropped. +// In typical use the span is a trace root because worker contexts +// carry no parent span; sampling is determined by the global +// TracerProvider's sampler. // // The OTEL trace ID is injected into the log context as "trace" // for correlation with the tracing backend. func Tracing() workers.Middleware { return func(ctx context.Context, info *workers.WorkerInfo, next workers.CycleFunc) error { - ctx = ensureSampled(ctx) span, ctx := tracing.NewInternalSpan(ctx, "worker:"+info.GetName()+":cycle") defer span.Finish() @@ -38,23 +33,3 @@ func Tracing() workers.Middleware { return err } } - -// ensureSampled injects a sampled remote span context so that -// ParentBased samplers always sample the next span created from -// this context. If the context already has a sampled span, it is -// returned unchanged. -func ensureSampled(ctx context.Context) context.Context { - if oteltrace.SpanFromContext(ctx).SpanContext().IsSampled() { - return ctx - } - var traceID oteltrace.TraceID - var spanID oteltrace.SpanID - _, _ = rand.Read(traceID[:]) - _, _ = rand.Read(spanID[:]) - return oteltrace.ContextWithRemoteSpanContext(ctx, oteltrace.NewSpanContext(oteltrace.SpanContextConfig{ - TraceID: traceID, - SpanID: spanID, - TraceFlags: oteltrace.FlagsSampled, - Remote: true, - })) -} diff --git a/middleware/tracing_test.go b/middleware/tracing_test.go index 287f048..265190f 100644 --- a/middleware/tracing_test.go +++ b/middleware/tracing_test.go @@ -6,7 +6,6 @@ import ( "github.com/go-coldbrew/workers" "github.com/stretchr/testify/assert" - oteltrace "go.opentelemetry.io/otel/trace" ) func TestTracing_NoPanic(t *testing.T) { @@ -33,42 +32,3 @@ func TestTracing_PropagatesError(t *testing.T) { assert.ErrorIs(t, err, assert.AnError) } - -func TestEnsureSampled(t *testing.T) { - // From a bare context (no span), ensureSampled should inject a - // sampled remote span context. - ctx := ensureSampled(context.Background()) - sc := oteltrace.SpanContextFromContext(ctx) - - assert.True(t, sc.IsSampled(), "should be sampled") - assert.True(t, sc.IsRemote(), "should be remote") - assert.True(t, sc.HasTraceID(), "should have a trace ID") - assert.True(t, sc.HasSpanID(), "should have a span ID") -} - -func TestEnsureSampled_AlreadySampled(t *testing.T) { - // If context already has a sampled span, ensureSampled is a no-op. - existing := oteltrace.NewSpanContext(oteltrace.SpanContextConfig{ - TraceID: oteltrace.TraceID{1, 2, 3}, - SpanID: oteltrace.SpanID{4, 5, 6}, - TraceFlags: oteltrace.FlagsSampled, - Remote: true, - }) - ctx := oteltrace.ContextWithRemoteSpanContext(context.Background(), existing) - - ctx = ensureSampled(ctx) - sc := oteltrace.SpanContextFromContext(ctx) - - assert.Equal(t, existing.TraceID(), sc.TraceID(), "should keep existing trace ID") - assert.Equal(t, existing.SpanID(), sc.SpanID(), "should keep existing span ID") -} - -func TestEnsureSampled_UniqueIDs(t *testing.T) { - ctx1 := ensureSampled(context.Background()) - ctx2 := ensureSampled(context.Background()) - - sc1 := oteltrace.SpanContextFromContext(ctx1) - sc2 := oteltrace.SpanContextFromContext(ctx2) - - assert.NotEqual(t, sc1.TraceID(), sc2.TraceID(), "each call should generate unique trace IDs") -}