diff --git a/ddtrace/opentelemetry/tracer.go b/ddtrace/opentelemetry/tracer.go index e813fc7d93..3920349832 100644 --- a/ddtrace/opentelemetry/tracer.go +++ b/ddtrace/opentelemetry/tracer.go @@ -44,6 +44,9 @@ func (t *oteltracer) Start(ctx context.Context, spanName string, opts ...oteltra // if the span doesn't originate from the Datadog tracer, // use SpanContextW3C implementation struct to pass span context information ddopts = append(ddopts, tracer.ChildOf(tracer.FromGenericCtx(&otelCtxToDDCtx{sctx}))) + if sctx.IsSampled() { + ddopts = append(ddopts, tracer.Tag(ext.ManualKeep, true)) + } } } if t := ssConfig.Timestamp(); !t.IsZero() { diff --git a/ddtrace/opentelemetry/tracer_test.go b/ddtrace/opentelemetry/tracer_test.go index 98de1eb5f2..6345411b4f 100644 --- a/ddtrace/opentelemetry/tracer_test.go +++ b/ddtrace/opentelemetry/tracer_test.go @@ -121,6 +121,31 @@ func TestSpanContext(t *testing.T) { assert.Equal(true, sctx.IsRemote()) } +func TestSamplingDecision(t *testing.T) { + assert := assert.New(t) + tp := NewTracerProvider( + tracer.WithSamplingRules([]tracer.SamplingRule{ + {Rate: 0}, // This should be applied only when a brand new root span is started and should be ignored for a non-root span + }), + ) + defer tp.Shutdown() + otel.SetTracerProvider(tp) + tr := otel.Tracer("") + + parentSpanContext := oteltrace.NewSpanContext(oteltrace.SpanContextConfig{ + TraceID: oteltrace.TraceID{0xAB}, + SpanID: oteltrace.SpanID{0x01}, + TraceFlags: oteltrace.FlagsSampled, // the parent span is sampled, so its child spans should be sampled too + }) + ctx := oteltrace.ContextWithSpanContext(context.Background(), parentSpanContext) + _, span := tr.Start(ctx, "test") + span.End() + + childSpanContext := span.SpanContext() + assert.Equal(parentSpanContext.TraceID(), childSpanContext.TraceID()) + assert.True(childSpanContext.IsSampled(), "parent span is sampled, but child span is not sampled") +} + func TestForceFlush(t *testing.T) { const ( UNSET = iota