Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions middleware/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<a name="Tracing"></a>
## func [Tracing](<https://github.com/go-coldbrew/workers/blob/main/middleware/tracing.go#L24>)
## func [Tracing](<https://github.com/go-coldbrew/workers/blob/main/middleware/tracing.go#L20>)

```go
func Tracing() workers.Middleware
```

Tracing creates an OTEL span per cycle via go\-coldbrew/tracing. The span is named "worker:\<name\>: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:\<name\>: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.

Expand Down
31 changes: 3 additions & 28 deletions middleware/tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package middleware

import (
"context"
"crypto/rand"

"github.com/go-coldbrew/log"
"github.com/go-coldbrew/tracing"
Expand All @@ -12,18 +11,14 @@ import (

// Tracing creates an OTEL span per cycle via go-coldbrew/tracing.
// The span is named "worker:<name>: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()

Expand All @@ -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,
}))
}
40 changes: 0 additions & 40 deletions middleware/tracing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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")
}
Loading