Skip to content
Closed
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
24 changes: 23 additions & 1 deletion ddtrace/opentelemetry/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (t *oteltracer) Start(ctx context.Context, spanName string, opts ...oteltra
} else if sctx := oteltrace.SpanFromContext(ctx).SpanContext(); sctx.IsValid() {
// 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})))
ddopts = append(ddopts, tracer.ChildOf(tracer.FromGenericCtx(otelToDDSpanContext(sctx))))
}
}
if t := ssConfig.Timestamp(); !t.IsZero() {
Expand Down Expand Up @@ -165,3 +165,25 @@ func (c *otelCtxToDDCtx) SpanID() uint64 {
}

func (c *otelCtxToDDCtx) ForeachBaggageItem(_ func(k, v string) bool) {}

// TODO: add baggage??
func otelToDDSpanContext(otelCtx oteltrace.SpanContext) *tracer.SpanContext {
// otelCtx.TraceID() and SpanID() are arrays, so we can assign directly.
var traceID [16]byte = otelCtx.TraceID()
spanIDBytes := otelCtx.SpanID()
var spanID uint64 = binary.BigEndian.Uint64(spanIDBytes[:])

// Interpret TraceFlags as sampling priority
var samplingPriority *int
if otelCtx.IsSampled() {
p := 1
samplingPriority = &p
} else {
samplingPriority = nil
}
// Traceflags??
sc := tracer.NewSpanContextFromFields(traceID, spanID, samplingPriority, nil)
tracer.ParseTracestate(sc, otelCtx.TraceState().String())

return sc
}
22 changes: 22 additions & 0 deletions ddtrace/opentelemetry/tracer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/DataDog/dd-trace-go/v2/internal/telemetry/telemetrytest"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
otelbaggage "go.opentelemetry.io/otel/baggage"
Expand Down Expand Up @@ -398,3 +399,24 @@ func TestMergeOtelDDBaggage(t *testing.T) {
assert.Equal("otelValue", value)
})
}

func Test_DDOpenTelemetryTracer(t *testing.T) {
ddOTelTracer := 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
}),
).Tracer("")

parentSpanContext := oteltrace.NewSpanContext(oteltrace.SpanContextConfig{
TraceID: oteltrace.TraceID{0xAA},
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 := ddOTelTracer.Start(ctx, "test")
span.End()

childSpanContext := span.SpanContext()
require.Equal(t, parentSpanContext.TraceID(), childSpanContext.TraceID())
require.True(t, childSpanContext.IsSampled(), "parent span is sampled, but child span is not sampled") // this test fails
}
18 changes: 18 additions & 0 deletions ddtrace/tracer/spancontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -709,3 +709,21 @@ func spanIDHexEncoded(u uint64, padding int) string {
}
return string(buf[i:])
}

// NewSpanContextFromFields creates a new SpanContext from primitive fields.
// This is intended for use by integrations (e.g., OpenTelemetry).
func NewSpanContextFromFields(traceID [16]byte, spanID uint64, samplingPriority *int, baggage map[string]string) *SpanContext {
sc := &SpanContext{
traceID: traceID,
spanID: spanID,
baggage: make(map[string]string, len(baggage)),
trace: newTrace(),
}
for k, v := range baggage {
sc.setBaggageItem(k, v)
}
if samplingPriority != nil {
sc.setSamplingPriority(*samplingPriority, samplernames.Unknown)
}
return sc
}
6 changes: 3 additions & 3 deletions ddtrace/tracer/textmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -1146,7 +1146,7 @@ func (*propagatorW3c) extractTextMap(reader TextMapReader) (*SpanContext, error)
if err := parseTraceparent(&ctx, parentHeader); err != nil {
return nil, err
}
parseTracestate(&ctx, stateHeader)
ParseTracestate(&ctx, stateHeader)
return &ctx, nil
}

Expand Down Expand Up @@ -1231,7 +1231,7 @@ func parseTraceparent(ctx *SpanContext, header string) error {
return nil
}

// parseTracestate attempts to parse tracestateHeader which is a list
// ParseTracestate attempts to parse tracestateHeader which is a list
// with up to 32 comma-separated (,) list-members.
// An example value would be: `vendorname1=opaqueValue1,vendorname2=opaqueValue2,dd=s:1;o:synthetics`,
// Where `dd` list contains values that would be in x-datadog-tags as well as those needed for propagation information.
Expand All @@ -1240,7 +1240,7 @@ func parseTraceparent(ctx *SpanContext, header string) error {
// `origin` = `o`
// `last parent` = `p`
// `_dd.p.` prefix = `t.`
func parseTracestate(ctx *SpanContext, header string) {
func ParseTracestate(ctx *SpanContext, header string) {
if header == "" {
// The W3C spec says tracestate can be empty but should avoid sending it.
// https://www.w3.org/TR/trace-context-1/#tracestate-header-field-values
Expand Down
2 changes: 1 addition & 1 deletion ddtrace/tracer/textmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2390,7 +2390,7 @@ func FuzzComposeTracestate(f *testing.F) {
t.Skipf("Skipping invalid tags")
}
traceState := composeTracestate(sendCtx, priority, oldState)
parseTracestate(recvCtx, traceState)
ParseTracestate(recvCtx, traceState)
setPropagatingTag(sendCtx, tracestateHeader, traceState)
if !reflect.DeepEqual(sendCtx.trace.propagatingTags, recvCtx.trace.propagatingTags) {
t.Fatalf(`Inconsistent composing/parsing:
Expand Down
Loading