From 5df192879c3ad8fcb018dffc2a5eb1b297f24ca1 Mon Sep 17 00:00:00 2001 From: Katie Hockman Date: Thu, 30 Mar 2023 07:27:25 -0400 Subject: [PATCH] tracer: support 128-bit trace ids (#1833) --- ddtrace/ddtrace.go | 18 +- ddtrace/example_test.go | 9 + ddtrace/tracer/context_test.go | 41 +- ddtrace/tracer/span.go | 16 +- ddtrace/tracer/span_test.go | 66 ++- ddtrace/tracer/spancontext.go | 88 +++- ddtrace/tracer/spancontext_test.go | 24 +- ddtrace/tracer/textmap.go | 124 ++++-- ddtrace/tracer/textmap_test.go | 637 ++++++++++++++++------------- ddtrace/tracer/tracer.go | 2 +- ddtrace/tracer/tracer_test.go | 96 ++++- 11 files changed, 751 insertions(+), 370 deletions(-) diff --git a/ddtrace/ddtrace.go b/ddtrace/ddtrace.go index 76b55529bc..c4d106458c 100644 --- a/ddtrace/ddtrace.go +++ b/ddtrace/ddtrace.go @@ -20,6 +20,19 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/log" ) +// SpanContextW3C represents a SpanContext with an additional method to allow +// access of the 128-bit trace id of the span, if present. +type SpanContextW3C interface { + SpanContext + + // TraceID128 returns the hex-encoded 128-bit trace ID that this context is carrying. + // The string will be exactly 32 bytes and may include leading zeroes. + TraceID128() string + + // TraceID128 returns the raw bytes of the 128-bit trace ID that this context is carrying. + TraceID128Bytes() [16]byte +} + // Tracer specifies an implementation of the Datadog tracer which allows starting // and propagating spans. The official implementation if exposed as functions // within the "tracer" package. @@ -124,8 +137,9 @@ type StartSpanConfig struct { // new span. Tags map[string]interface{} - // Force-set the SpanID, rather than use a random number. If no Parent SpanContext is present, - // then this will also set the TraceID to the same value. + // SpanID will be the SpanID of the Span, overriding the random number that would + // be generated. If no Parent SpanContext is present, then this will also set the + // TraceID to the same value. SpanID uint64 // Context is the parent context where the span should be stored. diff --git a/ddtrace/example_test.go b/ddtrace/example_test.go index bda3f1d3ee..d691052f1d 100644 --- a/ddtrace/example_test.go +++ b/ddtrace/example_test.go @@ -6,11 +6,13 @@ package ddtrace_test import ( + "fmt" "log" "os" opentracing "github.com/opentracing/opentracing-go" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/opentracer" @@ -33,6 +35,13 @@ func Example_datadog() { child := tracer.StartSpan("read.file", tracer.ChildOf(span.Context())) child.SetTag(ext.ResourceName, "test.json") + // If you are using 128 bit trace ids and want to generate the high + // order bits, cast the span's context to ddtrace.SpanContextW3C. + // See Issue #1677 + if w3Cctx, ok := child.Context().(ddtrace.SpanContextW3C); ok { + fmt.Printf("128 bit trace id = %s\n", w3Cctx.TraceID128()) + } + // Perform an operation. _, err := os.ReadFile("~/test.json") diff --git a/ddtrace/tracer/context_test.go b/ddtrace/tracer/context_test.go index 53f4374151..49a2eff4c4 100644 --- a/ddtrace/tracer/context_test.go +++ b/ddtrace/tracer/context_test.go @@ -7,10 +7,13 @@ package tracer import ( "context" + "encoding/binary" + "encoding/hex" "testing" "github.com/stretchr/testify/assert" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/internal" ) @@ -49,8 +52,8 @@ func TestStartSpanFromContext(t *testing.T) { _, _, _, stop := startTestTracer(t) defer stop() - parent := &span{context: &spanContext{spanID: 123, traceID: 456}} - parent2 := &span{context: &spanContext{spanID: 789, traceID: 456}} + parent := &span{context: &spanContext{spanID: 123, traceID: traceIDFrom64Bits(456)}} + parent2 := &span{context: &spanContext{spanID: 789, traceID: traceIDFrom64Bits(456)}} pctx := ContextWithSpan(context.Background(), parent) child, ctx := StartSpanFromContext( pctx, @@ -107,6 +110,40 @@ func TestStartSpanFromContextRace(t *testing.T) { assert.ElementsMatch(t, outputs, expectedTraceIDs) } +func Test128(t *testing.T) { + _, _, _, stop := startTestTracer(t) + defer stop() + + span, _ := StartSpanFromContext(context.Background(), "http.request") + assert.NotZero(t, span.Context().TraceID()) + w3cCtx, ok := span.Context().(ddtrace.SpanContextW3C) + if !ok { + assert.Fail(t, "couldn't cast to ddtrace.SpanContextW3C") + } + id128 := w3cCtx.TraceID128() + assert.Len(t, id128, 32) // ensure there are enough leading zeros + idBytes, err := hex.DecodeString(id128) + assert.NoError(t, err) + assert.Equal(t, uint64(0), binary.BigEndian.Uint64(idBytes[:8])) // high 64 bits should be 0 + assert.Equal(t, span.Context().TraceID(), binary.BigEndian.Uint64(idBytes[8:])) + + // Enable 128 bit trace ids + t.Setenv("DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED", "true") + span128, _ := StartSpanFromContext(context.Background(), "http.request") + assert.NotZero(t, span128.Context().TraceID()) + w3cCtx, ok = span128.Context().(ddtrace.SpanContextW3C) + if !ok { + assert.Fail(t, "couldn't cast to ddtrace.SpanContextW3C") + } + id128bit := w3cCtx.TraceID128() + assert.NotEmpty(t, id128bit) + assert.Len(t, id128bit, 32) + // Ensure that the lower order bits match the span's 64-bit trace id + b, err := hex.DecodeString(id128bit) + assert.NoError(t, err) + assert.Equal(t, span128.Context().TraceID(), binary.BigEndian.Uint64(b[8:])) +} + func TestStartSpanFromNilContext(t *testing.T) { _, _, _, stop := startTestTracer(t) defer stop() diff --git a/ddtrace/tracer/span.go b/ddtrace/tracer/span.go index fb0a541f78..f646605c5e 100644 --- a/ddtrace/tracer/span.go +++ b/ddtrace/tracer/span.go @@ -25,6 +25,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/internal" + sharedinternal "gopkg.in/DataDog/dd-trace-go.v1/internal" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" @@ -71,7 +72,7 @@ type span struct { Meta map[string]string `msg:"meta,omitempty"` // arbitrary map of metadata Metrics map[string]float64 `msg:"metrics,omitempty"` // arbitrary map of numeric metrics SpanID uint64 `msg:"span_id"` // identifier of this span - TraceID uint64 `msg:"trace_id"` // identifier of the root span + TraceID uint64 `msg:"trace_id"` // lower 64-bits of the root span identifier ParentID uint64 `msg:"parent_id"` // identifier of the span's direct parent Error int32 `msg:"error"` // error status of the span; 0 means no errors @@ -589,6 +590,7 @@ func (s *span) String() string { fmt.Sprintf("Service: %s", s.Service), fmt.Sprintf("Resource: %s", s.Resource), fmt.Sprintf("TraceID: %d", s.TraceID), + fmt.Sprintf("TraceID128: %s", s.context.TraceID128()), fmt.Sprintf("SpanID: %d", s.SpanID), fmt.Sprintf("ParentID: %d", s.ParentID), fmt.Sprintf("Start: %s", time.Unix(0, s.Start)), @@ -630,7 +632,14 @@ func (s *span) Format(f fmt.State, c rune) { fmt.Fprintf(f, "dd.version=%s ", v) } } - fmt.Fprintf(f, `dd.trace_id="%d" dd.span_id="%d"`, s.TraceID, s.SpanID) + var traceID string + if sharedinternal.BoolEnv("DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED", false) && s.context.traceID.HasUpper() { + traceID = s.context.TraceID128() + } else { + traceID = fmt.Sprintf("%d", s.TraceID) + } + fmt.Fprintf(f, `dd.trace_id=%q `, traceID) + fmt.Fprintf(f, `dd.span_id="%d"`, s.SpanID) default: fmt.Fprintf(f, "%%!%c(ddtrace.Span=%v)", c, s) } @@ -662,9 +671,10 @@ const ( keySingleSpanSamplingMPS = "_dd.span_sampling.max_per_second" // keyPropagatedUserID holds the propagated user identifier, if user id propagation is enabled. keyPropagatedUserID = "_dd.p.usr.id" - //keyTracerHostname holds the tracer detected hostname, only present when not connected over UDS to agent. keyTracerHostname = "_dd.tracer_hostname" + // keyTraceID128 is the lowercase, hex encoded upper 64 bits of a 128-bit trace id, if present. + keyTraceID128 = "_dd.p.tid" ) // The following set of tags is used for user monitoring and set through calls to span.SetUser(). diff --git a/ddtrace/tracer/span_test.go b/ddtrace/tracer/span_test.go index 0bcb65ee8f..5813290756 100644 --- a/ddtrace/tracer/span_test.go +++ b/ddtrace/tracer/span_test.go @@ -375,7 +375,7 @@ func TestTraceManualKeepAndManualDrop(t *testing.T) { t.Run(fmt.Sprintf("%s/non-local", scenario.tag), func(t *testing.T) { tracer := newTracer() defer tracer.Stop() - spanCtx := &spanContext{traceID: 42, spanID: 42} + spanCtx := &spanContext{traceID: traceIDFrom64Bits(42), spanID: 42} spanCtx.setSamplingPriority(scenario.p, samplernames.RemoteRate) span := tracer.StartSpan("non-local root span", ChildOf(spanCtx)).(*span) span.SetTag(scenario.tag, true) @@ -708,6 +708,70 @@ func TestSpanLog(t *testing.T) { expect := fmt.Sprintf(`dd.service=tracer.test dd.env=testenv dd.version=1.2.3 dd.trace_id="%d" dd.span_id="%d"`, span.TraceID, span.SpanID) assert.Equal(expect, fmt.Sprintf("%v", span)) }) + + t.Run("128-bit-generation-only", func(t *testing.T) { + // Generate 128 bit trace ids, but don't log them. So only the lower + // 64 bits should be logged in decimal form. + t.Setenv("DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED", "true") + // DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED is false by default + assert := assert.New(t) + tracer, _, _, stop := startTestTracer(t, WithService("tracer.test"), WithEnv("testenv")) + defer stop() + span := tracer.StartSpan("test.request").(*span) + span.TraceID = 12345678 + span.SpanID = 87654321 + span.Finish() + expect := `dd.service=tracer.test dd.env=testenv dd.trace_id="12345678" dd.span_id="87654321"` + assert.Equal(expect, fmt.Sprintf("%v", span)) + }) + + t.Run("128-bit-logging-only", func(t *testing.T) { + // Logging 128-bit trace ids is enabled, but it's not present in + // the span. So only the lower 64 bits should be logged in decimal form. + t.Setenv("DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED", "true") + // DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED is false by default + assert := assert.New(t) + tracer, _, _, stop := startTestTracer(t, WithService("tracer.test"), WithEnv("testenv")) + defer stop() + span := tracer.StartSpan("test.request").(*span) + span.TraceID = 12345678 + span.SpanID = 87654321 + span.Finish() + expect := `dd.service=tracer.test dd.env=testenv dd.trace_id="12345678" dd.span_id="87654321"` + assert.Equal(expect, fmt.Sprintf("%v", span)) + }) + + t.Run("128-bit-logging-with-generation", func(t *testing.T) { + // Logging 128-bit trace ids is enabled, and a 128-bit trace id, so + // a quoted 32 byte hex string should be printed for the dd.trace_id. + t.Setenv("DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED", "true") + t.Setenv("DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED", "true") + assert := assert.New(t) + tracer, _, _, stop := startTestTracer(t, WithService("tracer.test"), WithEnv("testenv")) + defer stop() + span := tracer.StartSpan("test.request").(*span) + span.SpanID = 87654321 + span.Finish() + expect := fmt.Sprintf(`dd.service=tracer.test dd.env=testenv dd.trace_id=%q dd.span_id="87654321"`, span.context.TraceID128()) + assert.Equal(expect, fmt.Sprintf("%v", span)) + v, _ := span.context.meta(keyTraceID128) + assert.NotEmpty(v) + }) + + t.Run("128-bit-logging-with-small-upper-bits", func(t *testing.T) { + // Logging 128-bit trace ids is enabled, and a 128-bit trace id, so + // a quoted 32 byte hex string should be printed for the dd.trace_id. + t.Setenv("DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED", "true") + assert := assert.New(t) + tracer, _, _, stop := startTestTracer(t, WithService("tracer.test"), WithEnv("testenv")) + defer stop() + span := tracer.StartSpan("test.request", WithSpanID(87654321)).(*span) + span.context.traceID.SetUpper(1) + span.Finish() + assert.Equal(`dd.service=tracer.test dd.env=testenv dd.trace_id="00000000000000010000000005397fb1" dd.span_id="87654321"`, fmt.Sprintf("%v", span)) + v, _ := span.context.meta(keyTraceID128) + assert.Equal("0000000000000001", v) + }) } func TestRootSpanAccessor(t *testing.T) { diff --git a/ddtrace/tracer/spancontext.go b/ddtrace/tracer/spancontext.go index 3de7d478c2..ef702985f2 100644 --- a/ddtrace/tracer/spancontext.go +++ b/ddtrace/tracer/spancontext.go @@ -6,19 +6,74 @@ package tracer import ( + "encoding/binary" + "encoding/hex" "strconv" "sync" "sync/atomic" + "time" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/internal" ginternal "gopkg.in/DataDog/dd-trace-go.v1/internal" + sharedinternal "gopkg.in/DataDog/dd-trace-go.v1/internal" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" ) var _ ddtrace.SpanContext = (*spanContext)(nil) +type traceID [16]byte // traceID in big endian, i.e. + +var emptyTraceID traceID + +func (t *traceID) HexEncoded() string { + return hex.EncodeToString(t[:]) +} + +func (t *traceID) Lower() uint64 { + return binary.BigEndian.Uint64(t[8:]) +} + +func (t *traceID) Upper() uint64 { + return binary.BigEndian.Uint64(t[:8]) +} + +func (t *traceID) SetLower(i uint64) { + binary.BigEndian.PutUint64(t[8:], i) +} + +func (t *traceID) SetUpper(i uint64) { + binary.BigEndian.PutUint64(t[:8], i) +} + +func (t *traceID) SetUpperFromHex(s string) { + u, err := strconv.ParseUint(s, 16, 64) + if err != nil { + log.Debug("Attempted to decode an invalid hex traceID %s", s) + return + } + t.SetUpper(u) +} + +func (t *traceID) Empty() bool { + return *t == emptyTraceID +} + +func (t *traceID) HasUpper() bool { + //TODO: in go 1.20 we can simplify this + for _, b := range t[:8] { + if b != 0 { + return true + } + } + return false +} + +func (t *traceID) UpperHex() string { + return hex.EncodeToString(t[:8]) +} + // SpanContext represents a span state that can propagate to descendant spans // and across process boundaries. It contains all the information needed to // spawn a direct descendant of the span that it belongs to. It can be used @@ -34,7 +89,7 @@ type spanContext struct { // the below group should propagate cross-process - traceID uint64 + traceID traceID spanID uint64 mu sync.RWMutex // guards below fields @@ -50,11 +105,12 @@ type spanContext struct { // for the same span. func newSpanContext(span *span, parent *spanContext) *spanContext { context := &spanContext{ - traceID: span.TraceID, - spanID: span.SpanID, - span: span, + spanID: span.SpanID, + span: span, } + context.traceID.SetLower(span.TraceID) if parent != nil { + context.traceID.SetUpper(parent.traceID.Upper()) context.trace = parent.trace context.origin = parent.origin context.errors = parent.errors @@ -62,6 +118,15 @@ func newSpanContext(span *span, parent *spanContext) *spanContext { context.setBaggageItem(k, v) return true }) + } else if sharedinternal.BoolEnv("DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED", false) { + // add 128 bit trace id, if enabled, formatted as big-endian: + // <32-bit unix seconds> <32 bits of zero> <64 random bits> + id128 := time.Duration(span.Start) / time.Second + // casting from int64 -> uint32 should be safe since the start time won't be + // negative, and the seconds should fit within 32-bits for the foreseeable future. + // (We only want 32 bits of time, then the rest is zero) + tUp := uint64(uint32(id128)) << 32 // We need the time at the upper 32 bits of the uint + context.traceID.SetUpper(tUp) } if context.trace == nil { context.trace = newTrace() @@ -83,7 +148,17 @@ func newSpanContext(span *span, parent *spanContext) *spanContext { func (c *spanContext) SpanID() uint64 { return c.spanID } // TraceID implements ddtrace.SpanContext. -func (c *spanContext) TraceID() uint64 { return c.traceID } +func (c *spanContext) TraceID() uint64 { return c.traceID.Lower() } + +// TraceID128 implements ddtrace.SpanContextW3C. +func (c *spanContext) TraceID128() string { + return c.traceID.HexEncoded() +} + +// TraceID128Bytes implements ddtrace.SpanContextW3C. +func (c *spanContext) TraceID128Bytes() [16]byte { + return c.traceID +} // ForeachBaggageItem implements ddtrace.SpanContext. func (c *spanContext) ForeachBaggageItem(handler func(k, v string) bool) { @@ -339,6 +414,9 @@ func (t *trace) finishedOne(s *span) { s.setMeta(k, v) } } + if s.context != nil && s.context.traceID.HasUpper() { + s.setMeta(keyTraceID128, s.context.traceID.UpperHex()) + } if len(t.spans) != t.finished { return } diff --git a/ddtrace/tracer/spancontext_test.go b/ddtrace/tracer/spancontext_test.go index 2aee2a6166..4dc38d9799 100644 --- a/ddtrace/tracer/spancontext_test.go +++ b/ddtrace/tracer/spancontext_test.go @@ -252,7 +252,7 @@ func TestNewSpanContext(t *testing.T) { } ctx := newSpanContext(span, nil) assert := assert.New(t) - assert.Equal(ctx.traceID, span.TraceID) + assert.Equal(ctx.traceID.Lower(), span.TraceID) assert.Equal(ctx.spanID, span.SpanID) assert.NotNil(ctx.trace) assert.Nil(ctx.trace.priority) @@ -269,7 +269,7 @@ func TestNewSpanContext(t *testing.T) { } ctx := newSpanContext(span, nil) assert := assert.New(t) - assert.Equal(ctx.traceID, span.TraceID) + assert.Equal(ctx.traceID.Lower(), span.TraceID) assert.Equal(ctx.spanID, span.SpanID) assert.Equal(ctx.TraceID(), span.TraceID) assert.Equal(ctx.SpanID(), span.SpanID) @@ -292,9 +292,9 @@ func TestNewSpanContext(t *testing.T) { sctx, ok := ctx.(*spanContext) assert.True(ok) span := StartSpan("some-span", ChildOf(ctx)) - assert.EqualValues(sctx.traceID, 1) - assert.EqualValues(sctx.spanID, 2) - assert.EqualValues(*sctx.trace.priority, 3) + assert.EqualValues(uint64(1), sctx.traceID.Lower()) + assert.EqualValues(2, sctx.spanID) + assert.EqualValues(3, *sctx.trace.priority) assert.Equal(sctx.trace.root, span) }) } @@ -336,7 +336,7 @@ func TestSpanContextParent(t *testing.T) { t.Run(name, func(t *testing.T) { ctx := newSpanContext(s, parentCtx) assert := assert.New(t) - assert.Equal(ctx.traceID, s.TraceID) + assert.Equal(ctx.traceID.Lower(), s.TraceID) assert.Equal(ctx.spanID, s.SpanID) if parentCtx.trace != nil { assert.Equal(len(ctx.trace.spans), len(parentCtx.trace.spans)) @@ -458,3 +458,15 @@ func TestSetSamplingPriorityLocked(t *testing.T) { assert.Equal(t, "-1", tr.propagatingTags[keyDecisionMaker]) }) } + +func TestTraceIDHexEncoded(t *testing.T) { + tid := traceID([16]byte{}) + tid[15] = 5 + assert.Equal(t, "00000000000000000000000000000005", tid.HexEncoded()) +} + +func TestTraceIDEmpty(t *testing.T) { + tid := traceID([16]byte{}) + tid[15] = 5 + assert.False(t, tid.Empty()) +} diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index c2b1bce4be..66499572bc 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -285,11 +285,16 @@ func (p *propagator) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) er func (p *propagator) injectTextMap(spanCtx ddtrace.SpanContext, writer TextMapWriter) error { ctx, ok := spanCtx.(*spanContext) - if !ok || ctx.traceID == 0 || ctx.spanID == 0 { + if !ok || ctx.traceID.Empty() || ctx.spanID == 0 { return ErrInvalidSpanContext } // propagate the TraceID and the current active SpanID - writer.Set(p.cfg.TraceHeader, strconv.FormatUint(ctx.traceID, 10)) + if ctx.traceID.HasUpper() { + setPropagatingTag(ctx, keyTraceID128, ctx.traceID.UpperHex()) + } else if ctx.trace != nil { + ctx.trace.unsetPropagatingTag(keyTraceID128) + } + writer.Set(p.cfg.TraceHeader, strconv.FormatUint(ctx.traceID.Lower(), 10)) writer.Set(p.cfg.ParentHeader, strconv.FormatUint(ctx.spanID, 10)) if sp, ok := ctx.samplingPriority(); ok { writer.Set(p.cfg.PriorityHeader, strconv.Itoa(sp)) @@ -356,10 +361,12 @@ func (p *propagator) extractTextMap(reader TextMapReader) (ddtrace.SpanContext, key := strings.ToLower(k) switch key { case p.cfg.TraceHeader: - ctx.traceID, err = parseUint64(v) + var lowerTid uint64 + lowerTid, err = parseUint64(v) if err != nil { return ErrSpanContextCorrupted } + ctx.traceID.SetLower(lowerTid) case p.cfg.ParentHeader: ctx.spanID, err = parseUint64(v) if err != nil { @@ -385,7 +392,11 @@ func (p *propagator) extractTextMap(reader TextMapReader) (ddtrace.SpanContext, if err != nil { return nil, err } - if ctx.traceID == 0 || (ctx.spanID == 0 && ctx.origin != "synthetics") { + if ctx.trace != nil { + // TODO: this always assumed it was valid so I copied that logic here, maybe we shouldn't + ctx.traceID.SetUpperFromHex(ctx.trace.propagatingTags[keyTraceID128]) + } + if ctx.traceID.Empty() || (ctx.spanID == 0 && ctx.origin != "synthetics") { return nil, ErrSpanContextNotFound } return &ctx, nil @@ -443,10 +454,18 @@ func (p *propagatorB3) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) func (*propagatorB3) injectTextMap(spanCtx ddtrace.SpanContext, writer TextMapWriter) error { ctx, ok := spanCtx.(*spanContext) - if !ok || ctx.traceID == 0 || ctx.spanID == 0 { + if !ok || ctx.traceID.Empty() || ctx.spanID == 0 { return ErrInvalidSpanContext } - writer.Set(b3TraceIDHeader, fmt.Sprintf("%016x", ctx.traceID)) + if !ctx.traceID.HasUpper() { // 64-bit trace id + writer.Set(b3TraceIDHeader, fmt.Sprintf("%016x", ctx.traceID.Lower())) + } else { // 128-bit trace id + var w3Cctx ddtrace.SpanContextW3C + if w3Cctx, ok = spanCtx.(ddtrace.SpanContextW3C); !ok { + return ErrInvalidSpanContext + } + writer.Set(b3TraceIDHeader, w3Cctx.TraceID128()) + } writer.Set(b3SpanIDHeader, fmt.Sprintf("%016x", ctx.spanID)) if p, ok := ctx.samplingPriority(); ok { if p >= ext.PriorityAutoKeep { @@ -474,12 +493,8 @@ func (*propagatorB3) extractTextMap(reader TextMapReader) (ddtrace.SpanContext, key := strings.ToLower(k) switch key { case b3TraceIDHeader: - if len(v) > 16 { - v = v[len(v)-16:] - } - ctx.traceID, err = strconv.ParseUint(v, 16, 64) - if err != nil { - return ErrSpanContextCorrupted + if err := extractTraceID128(&ctx, v); err != nil { + return nil } case b3SpanIDHeader: ctx.spanID, err = strconv.ParseUint(v, 16, 64) @@ -499,7 +514,7 @@ func (*propagatorB3) extractTextMap(reader TextMapReader) (ddtrace.SpanContext, if err != nil { return nil, err } - if ctx.traceID == 0 || ctx.spanID == 0 { + if ctx.traceID.Empty() || ctx.spanID == 0 { return nil, ErrSpanContextNotFound } return &ctx, nil @@ -520,11 +535,21 @@ func (p *propagatorB3SingleHeader) Inject(spanCtx ddtrace.SpanContext, carrier i func (*propagatorB3SingleHeader) injectTextMap(spanCtx ddtrace.SpanContext, writer TextMapWriter) error { ctx, ok := spanCtx.(*spanContext) - if !ok || ctx.traceID == 0 || ctx.spanID == 0 { + if !ok || ctx.traceID.Empty() || ctx.spanID == 0 { return ErrInvalidSpanContext } sb := strings.Builder{} - sb.WriteString(fmt.Sprintf("%016x-%016x", ctx.traceID, ctx.spanID)) + var traceID string + if !ctx.traceID.HasUpper() { // 64-bit trace id + traceID = fmt.Sprintf("%016x", ctx.traceID.Lower()) + } else { // 128-bit trace id + var w3Cctx ddtrace.SpanContextW3C + if w3Cctx, ok = spanCtx.(ddtrace.SpanContextW3C); !ok { + return ErrInvalidSpanContext + } + traceID = w3Cctx.TraceID128() + } + sb.WriteString(fmt.Sprintf("%s-%016x", traceID, ctx.spanID)) if p, ok := ctx.samplingPriority(); ok { if p >= ext.PriorityAutoKeep { sb.WriteString("-1") @@ -554,12 +579,8 @@ func (*propagatorB3SingleHeader) extractTextMap(reader TextMapReader) (ddtrace.S case b3SingleHeader: b3Parts := strings.Split(v, "-") if len(b3Parts) >= 2 { - if len(b3Parts[0]) > 16 { - b3Parts[0] = b3Parts[0][len(b3Parts[0])-16:] - } - ctx.traceID, err = strconv.ParseUint(b3Parts[0], 16, 64) - if err != nil { - return ErrSpanContextCorrupted + if err = extractTraceID128(&ctx, b3Parts[0]); err != nil { + return err } ctx.spanID, err = strconv.ParseUint(b3Parts[1], 16, 64) if err != nil { @@ -587,7 +608,7 @@ func (*propagatorB3SingleHeader) extractTextMap(reader TextMapReader) (ddtrace.S if err != nil { return nil, err } - if ctx.traceID == 0 || ctx.spanID == 0 { + if ctx.traceID.Empty() || ctx.spanID == 0 { return nil, ErrSpanContextNotFound } return &ctx, nil @@ -622,7 +643,7 @@ func (p *propagatorW3c) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) // where each list-member is managed by a vendor or instrumentation library. func (*propagatorW3c) injectTextMap(spanCtx ddtrace.SpanContext, writer TextMapWriter) error { ctx, ok := spanCtx.(*spanContext) - if !ok || ctx.traceID == 0 || ctx.spanID == 0 { + if !ok || ctx.traceID.Empty() || ctx.spanID == 0 { return ErrInvalidSpanContext } flags := "" @@ -634,18 +655,16 @@ func (*propagatorW3c) injectTextMap(spanCtx ddtrace.SpanContext, writer TextMapW } var traceID string - // if previous traceparent is valid, do NOT update the trace ID - if ctx.trace != nil && ctx.trace.propagatingTags != nil { - tag := ctx.trace.propagatingTags[w3cTraceIDTag] - if len(tag) == 32 { - id, err := strconv.ParseUint(tag[16:], 16, 64) - if err == nil && id != 0 { - traceID = tag - } + if ctx.traceID.HasUpper() { + setPropagatingTag(ctx, keyTraceID128, ctx.traceID.UpperHex()) + if w3Cctx, ok := spanCtx.(ddtrace.SpanContextW3C); ok { + traceID = w3Cctx.TraceID128() } - } - if len(traceID) == 0 { + } else { traceID = fmt.Sprintf("%032x", ctx.traceID) + if ctx.trace != nil { + ctx.trace.unsetPropagatingTag(keyTraceID128) + } } writer.Set(traceparentHeader, fmt.Sprintf("00-%s-%016x-%v", traceID, ctx.spanID, flags)) // if context priority / origin / tags were updated after extraction, @@ -837,16 +856,13 @@ func parseTraceparent(ctx *spanContext, header string) error { if ok := validIDRgx.MatchString(fullTraceID); !ok { return ErrSpanContextCorrupted } - if ctx.traceID, err = strconv.ParseUint(fullTraceID[16:], 16, 64); err != nil { - return ErrSpanContextCorrupted + if ctx.trace != nil { + // Ensure that the 128-bit trace id tag doesn't propagate + ctx.trace.unsetPropagatingTag(keyTraceID128) } - if ctx.traceID == 0 { - if strings.Trim(fullTraceID[:16], "0") == "" { - return ErrSpanContextNotFound - } + if err := extractTraceID128(ctx, fullTraceID); err != nil { + return err } - // setting trace-id to be used for span context propagation - setPropagatingTag(ctx, w3cTraceIDTag, fullTraceID) // parsing spanID spanID := strings.Trim(parts[2], nonWordCutset) if len(spanID) != 16 { @@ -915,3 +931,29 @@ func parseTracestate(ctx *spanContext, header string) error { } return nil } + +// extractTraceID128 extracts the trace id from v and populates the traceID +// field, and the traceID128 field (if applicable) of the provided ctx, +// returning an error if v is invalid. +func extractTraceID128(ctx *spanContext, v string) error { + if len(v) > 32 { + v = v[len(v)-32:] + } + v = strings.TrimLeft(v, "0") + var err error + if len(v) <= 16 { // 64-bit trace id + var tid uint64 + tid, err = strconv.ParseUint(v, 16, 64) + ctx.traceID.SetLower(tid) + } else { // 128-bit trace id + idUpper := v[:len(v)-16] + ctx.traceID.SetUpperFromHex(idUpper) + var l uint64 + l, err = strconv.ParseUint(v[len(idUpper):], 16, 64) + ctx.traceID.SetLower(l) + } + if err != nil { + return ErrSpanContextCorrupted + } + return nil +} diff --git a/ddtrace/tracer/textmap_test.go b/ddtrace/tracer/textmap_test.go index 3e865e6d3f..ad7b773d1e 100644 --- a/ddtrace/tracer/textmap_test.go +++ b/ddtrace/tracer/textmap_test.go @@ -27,6 +27,19 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" ) +func traceIDFrom64Bits(i uint64) traceID { + t := traceID{} + t.SetLower(i) + return t +} + +func traceIDFrom128Bits(u, l uint64) traceID { + t := traceID{} + t.SetLower(l) + t.SetUpper(u) + return t +} + func TestHTTPHeadersCarrierSet(t *testing.T) { h := http.Header{} c := HTTPHeadersCarrier(h) @@ -102,7 +115,7 @@ func TestTextMapPropagatorErrors(t *testing.T) { assert.Equal(ErrInvalidSpanContext, err) err = propagator.Inject(&spanContext{}, TextMapCarrier(map[string]string{})) assert.Equal(ErrInvalidSpanContext, err) // no traceID and spanID - err = propagator.Inject(&spanContext{traceID: 1}, TextMapCarrier(map[string]string{})) + err = propagator.Inject(&spanContext{traceID: traceIDFrom64Bits(1)}, TextMapCarrier(map[string]string{})) assert.Equal(ErrInvalidSpanContext, err) // no spanID _, err = propagator.Extract(2) @@ -272,7 +285,7 @@ func TestExtractOriginSynthetics(t *testing.T) { t.Fatal("not a *spanContext") } assert.Equal(t, sctx.spanID, uint64(0)) - assert.Equal(t, sctx.traceID, uint64(3)) + assert.Equal(t, sctx.traceID.Lower(), uint64(3)) assert.Equal(t, sctx.origin, "synthetics") } @@ -407,27 +420,39 @@ func TestEnvVars(t *testing.T) { t.Setenv(k, v) } var tests = []struct { - in []uint64 - out map[string]string + tid traceID + spanID uint64 + out map[string]string }{ { - []uint64{1412508178991881, 1842642739201064}, - map[string]string{ + tid: traceIDFrom128Bits(9863134987902842, 1412508178991881), + spanID: 1842642739201064, + out: map[string]string{ + b3TraceIDHeader: "00230a7811535f7a000504ab30404b09", + b3SpanIDHeader: "00068bdfb1eb0428", + }, + }, + { + tid: traceIDFrom64Bits(1412508178991881), + spanID: 1842642739201064, + out: map[string]string{ b3TraceIDHeader: "000504ab30404b09", b3SpanIDHeader: "00068bdfb1eb0428", }, }, { - []uint64{9530669991610245, 9455715668862222}, - map[string]string{ + tid: traceIDFrom64Bits(9530669991610245), + spanID: 9455715668862222, + out: map[string]string{ b3TraceIDHeader: "0021dc1807524785", b3SpanIDHeader: "002197ec5d8a250e", }, }, { - []uint64{1, 1}, - map[string]string{ - b3TraceIDHeader: "0000000000000001", + tid: traceIDFrom128Bits(1, 1), + spanID: 1, + out: map[string]string{ + b3TraceIDHeader: "00000000000000010000000000000001", b3SpanIDHeader: "0000000000000001", }, }, @@ -438,8 +463,8 @@ func TestEnvVars(t *testing.T) { defer tracer.Stop() root := tracer.StartSpan("web.request").(*span) ctx, ok := root.Context().(*spanContext) - ctx.traceID = test.in[0] - ctx.spanID = test.in[1] + ctx.traceID = test.tid + ctx.spanID = test.spanID headers := TextMapCarrier(map[string]string{}) err := tracer.Inject(ctx, headers) @@ -467,28 +492,40 @@ func TestEnvVars(t *testing.T) { } var tests = []struct { in TextMapCarrier - out []uint64 // contains [, ] + tid traceID + sid uint64 }{ { TextMapCarrier{ b3TraceIDHeader: "1", b3SpanIDHeader: "1", }, - []uint64{1, 1}, + traceIDFrom64Bits(1), + 1, + }, + { + TextMapCarrier{ + b3TraceIDHeader: "20000000000000001", + b3SpanIDHeader: "1", + }, + traceIDFrom128Bits(2, 1), + 1, }, { TextMapCarrier{ b3TraceIDHeader: "feeb0599801f4700", b3SpanIDHeader: "f8f5c76089ad8da5", }, - []uint64{18368781661998368512, 17939463908140879269}, + traceIDFrom64Bits(18368781661998368512), + 17939463908140879269, }, { TextMapCarrier{ - b3TraceIDHeader: "6e96719ded9c1864a21ba1551789e3f5", + b3TraceIDHeader: "feeb0599801f4700a21ba1551789e3f5", b3SpanIDHeader: "a1eb5bf36e56e50e", }, - []uint64{11681107445354718197, 11667520360719770894}, + traceIDFrom128Bits(18368781661998368512, 11681107445354718197), + 11667520360719770894, }, } for _, test := range tests { @@ -500,9 +537,8 @@ func TestEnvVars(t *testing.T) { assert.Nil(err) sctx, ok := ctx.(*spanContext) assert.True(ok) - - assert.Equal(sctx.traceID, test.out[0]) - assert.Equal(sctx.spanID, test.out[1]) + assert.Equal(test.tid, sctx.traceID) + assert.Equal(test.sid, sctx.spanID) }) } } @@ -530,12 +566,12 @@ func TestEnvVars(t *testing.T) { }, }, } - for _, test := range tests { + for _, tc := range tests { t.Run(fmt.Sprintf("extract with env=%q", testEnv), func(t *testing.T) { tracer := newTracer(WithHTTPClient(c), withStatsdClient(&statsd.NoOpClient{})) defer tracer.Stop() assert := assert.New(t) - _, err := tracer.Extract(test.in) + _, err := tracer.Extract(tc.in) assert.NotNil(err) }) } @@ -553,49 +589,55 @@ func TestEnvVars(t *testing.T) { t.Setenv(k, v) } var tests = []struct { - in TextMapCarrier - out []uint64 // contains [, , ] + in TextMapCarrier + traceID128 string + out []uint64 // contains [, , ] }{ { TextMapCarrier{ b3SingleHeader: "1-2", }, + "", []uint64{1, 2}, }, { TextMapCarrier{ b3SingleHeader: "feeb0599801f4700-f8f5c76089ad8da5-1", }, + "", []uint64{18368781661998368512, 17939463908140879269, 1}, }, { TextMapCarrier{ b3SingleHeader: "6e96719ded9c1864a21ba1551789e3f5-a1eb5bf36e56e50e-0", }, + "", []uint64{11681107445354718197, 11667520360719770894, 0}, }, { TextMapCarrier{ b3SingleHeader: "6e96719ded9c1864a21ba1551789e3f5-a1eb5bf36e56e50e-d", }, + "", []uint64{11681107445354718197, 11667520360719770894, 1}, }, } - for _, test := range tests { + for _, tc := range tests { t.Run(fmt.Sprintf("extract with env=%q", testEnv), func(t *testing.T) { tracer := newTracer(WithHTTPClient(c), withStatsdClient(&statsd.NoOpClient{})) defer tracer.Stop() assert := assert.New(t) - ctx, err := tracer.Extract(test.in) + ctx, err := tracer.Extract(tc.in) require.Nil(t, err) sctx, ok := ctx.(*spanContext) assert.True(ok) - assert.Equal(test.out[0], sctx.traceID) - assert.Equal(test.out[1], sctx.spanID) - if len(test.out) > 2 { + assert.Equal(tc.out[0], sctx.traceID.Lower()) + assert.Equal(tc.out[1], sctx.spanID) + // assert.Equal(test.traceID128, id128FromSpan(assert, ctx)) // add when 128-bit trace id support is enabled + if len(tc.out) > 2 { require.NotNil(t, sctx.trace) - assert.Equal(float64(test.out[2]), *sctx.trace.priority) + assert.Equal(float64(tc.out[2]), *sctx.trace.priority) } }) } @@ -605,7 +647,7 @@ func TestEnvVars(t *testing.T) { t.Run("b3 single header inject", func(t *testing.T) { t.Setenv(headerPropagationStyleInject, "b3 single header") var tests = []struct { - in []uint64 + in []uint64 // contains [, , ] out string }{ { @@ -617,20 +659,20 @@ func TestEnvVars(t *testing.T) { "a21ba1551789e3f5-a1eb5bf36e56e50e-0", }, } - for i, test := range tests { + for i, tc := range tests { t.Run(fmt.Sprintf("b3 single header inject #%d", i), func(t *testing.T) { tracer := newTracer(WithHTTPClient(c), withStatsdClient(&statsd.NoOpClient{})) defer tracer.Stop() root := tracer.StartSpan("myrequest").(*span) ctx, ok := root.Context().(*spanContext) require.True(t, ok) - ctx.traceID = test.in[0] - ctx.spanID = test.in[1] - ctx.setSamplingPriority(int(test.in[2]), samplernames.Unknown) + ctx.traceID = traceIDFrom64Bits(tc.in[0]) + ctx.spanID = tc.in[1] + ctx.setSamplingPriority(int(tc.in[2]), samplernames.Unknown) headers := TextMapCarrier(map[string]string{}) err := tracer.Inject(ctx, headers) require.Nil(t, err) - assert.Equal(t, test.out, headers[b3SingleHeader]) + assert.Equal(t, tc.out, headers[b3SingleHeader]) }) } }) @@ -649,7 +691,7 @@ func TestEnvVars(t *testing.T) { t.Setenv(k, v) } var tests = []struct { - in []uint64 + in []uint64 // contains [, ] out map[string]string }{ { @@ -674,22 +716,22 @@ func TestEnvVars(t *testing.T) { }, }, } - for _, test := range tests { + for _, tc := range tests { t.Run(fmt.Sprintf("inject with env=%q", testEnv), func(t *testing.T) { tracer := newTracer(WithPropagator(NewPropagator(&PropagatorConfig{B3: true})), WithHTTPClient(c), withStatsdClient(&statsd.NoOpClient{})) defer tracer.Stop() root := tracer.StartSpan("web.request").(*span) ctx, ok := root.Context().(*spanContext) - ctx.traceID = test.in[0] - ctx.spanID = test.in[1] + ctx.traceID = traceIDFrom64Bits(tc.in[0]) + ctx.spanID = tc.in[1] headers := TextMapCarrier(map[string]string{}) err := tracer.Inject(ctx, headers) assert := assert.New(t) assert.True(ok) assert.Nil(err) - assert.Equal(test.out[b3TraceIDHeader], headers[b3TraceIDHeader]) - assert.Equal(test.out[b3SpanIDHeader], headers[b3SpanIDHeader]) + assert.Equal(tc.out[b3TraceIDHeader], headers[b3TraceIDHeader]) + assert.Equal(tc.out[b3SpanIDHeader], headers[b3SpanIDHeader]) }) } } @@ -707,8 +749,9 @@ func TestEnvVars(t *testing.T) { t.Setenv(k, v) } var tests = []struct { - in TextMapCarrier - out uint64 + in TextMapCarrier + traceID128Full string + out []uint64 // contains [, , ] }{ { TextMapCarrier{ @@ -716,33 +759,54 @@ func TestEnvVars(t *testing.T) { b3SpanIDHeader: "1", b3SampledHeader: "1", }, - 1, + "", + []uint64{1, 1, 1}, }, { TextMapCarrier{ - DefaultTraceIDHeader: "2", - DefaultParentIDHeader: "2", - DefaultPriorityHeader: "2", + b3TraceIDHeader: "20000000000000001", + b3SpanIDHeader: "1", + b3SampledHeader: "2", }, - 2, + "0000000000000002", + []uint64{1, 1, 2}, + }, + { + TextMapCarrier{ + b3TraceIDHeader: "feeb0599801f4700", + b3SpanIDHeader: "f8f5c76089ad8da5", + b3SampledHeader: "1", + }, + "", + []uint64{18368781661998368512, 17939463908140879269, 1}, + }, + { + TextMapCarrier{ + b3TraceIDHeader: "feeb0599801f4700a21ba1551789e3f5", + b3SpanIDHeader: "a1eb5bf36e56e50e", + b3SampledHeader: "0", + }, + "feeb0599801f4700", + []uint64{11681107445354718197, 11667520360719770894, 0}, }, } - for _, test := range tests { + for _, tc := range tests { t.Run(fmt.Sprintf("extract with env=%q", testEnv), func(t *testing.T) { tracer := newTracer(WithHTTPClient(c), withStatsdClient(&statsd.NoOpClient{})) defer tracer.Stop() assert := assert.New(t) - ctx, err := tracer.Extract(test.in) + ctx, err := tracer.Extract(tc.in) assert.Nil(err) sctx, ok := ctx.(*spanContext) assert.True(ok) - assert.Equal(sctx.traceID, test.out) - assert.Equal(sctx.spanID, test.out) + // assert.Equal(test.traceID128Full, id128FromSpan(assert, ctx)) // add when 128-bit trace id support is enabled + assert.Equal(tc.out[0], sctx.traceID.Lower()) + assert.Equal(tc.out[1], sctx.spanID) p, ok := sctx.samplingPriority() assert.True(ok) - assert.Equal(int(test.out), p) + assert.Equal(int(tc.out[2]), p) }) } } @@ -760,7 +824,7 @@ func TestEnvVars(t *testing.T) { t.Setenv(k, v) } var tests = []struct { - in []uint64 + in []uint64 // contains [, ] out map[string]string }{ { @@ -785,7 +849,7 @@ func TestEnvVars(t *testing.T) { }, }, } - for _, test := range tests { + for _, tc := range tests { t.Run(fmt.Sprintf("inject and extract with env=%q", testEnv), func(t *testing.T) { tracer := newTracer(WithHTTPClient(c), withStatsdClient(&statsd.NoOpClient{})) defer tracer.Stop() @@ -793,8 +857,8 @@ func TestEnvVars(t *testing.T) { root.SetTag(ext.SamplingPriority, -1) root.SetBaggageItem("item", "x") ctx, ok := root.Context().(*spanContext) - ctx.traceID = test.in[0] - ctx.spanID = test.in[1] + ctx.traceID = traceIDFrom64Bits(tc.in[0]) + ctx.spanID = tc.in[1] headers := TextMapCarrier(map[string]string{}) err := tracer.Inject(ctx, headers) @@ -803,7 +867,7 @@ func TestEnvVars(t *testing.T) { assert.Nil(err) sctx, err := tracer.Extract(headers) - assert.Nil(err) + require.Nil(t, err) xctx, ok := sctx.(*spanContext) assert.True(ok) @@ -830,10 +894,8 @@ func TestEnvVars(t *testing.T) { } var tests = []struct { in TextMapCarrier - traceID uint64 - fullTraceID string - spanID uint64 - priority int + out []uint64 // contains [, ] + tid traceID origin string propagatingTags map[string]string }{ @@ -842,13 +904,10 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "00-00000000000000001111111111111111-2222222222222222-01", tracestateHeader: "dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64~~,othervendor=t61rcWkgMzE", }, - fullTraceID: "00000000000000001111111111111111", - traceID: 1229782938247303441, - spanID: 2459565876494606882, - priority: 2, - origin: "rum", + tid: traceIDFrom64Bits(1229782938247303441), + out: []uint64{2459565876494606882, 2}, + origin: "rum", propagatingTags: map[string]string{ - "w3cTraceID": "00000000000000001111111111111111", "_dd.p.dm": "-4", "_dd.p.usr.id": "baz64==", "tracestate": "dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64~~,othervendor=t61rcWkgMzE", @@ -859,13 +918,10 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "00-10000000000000000000000000000000-2222222222222222-01", tracestateHeader: "dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64~~,othervendor=t61rcWkgMzE", }, - fullTraceID: "10000000000000000000000000000000", - traceID: 0x0, - spanID: 2459565876494606882, - priority: 2, - origin: "rum", + out: []uint64{2459565876494606882, 2}, + tid: traceIDFrom128Bits(1152921504606846976, 0), + origin: "rum", propagatingTags: map[string]string{ - "w3cTraceID": "10000000000000000000000000000000", "_dd.p.dm": "-4", "_dd.p.usr.id": "baz64==", "tracestate": "dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64~~,othervendor=t61rcWkgMzE", @@ -876,13 +932,11 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "00-00000000000000001111111111111111-2222222222222222-03", tracestateHeader: "dd=s:0;o:rum;t.dm:-2;t.usr.id:baz64~~,othervendor=t61rcWkgMzE", }, - fullTraceID: "00000000000000001111111111111111", - traceID: 1229782938247303441, - spanID: 2459565876494606882, - priority: 1, - origin: "rum", + out: []uint64{2459565876494606882, 1}, + tid: traceIDFrom64Bits(1229782938247303441), + + origin: "rum", propagatingTags: map[string]string{ - "w3cTraceID": "00000000000000001111111111111111", "_dd.p.dm": "-2", "_dd.p.usr.id": "baz64==", "tracestate": "dd=s:0;o:rum;t.dm:-2;t.usr.id:baz64~~,othervendor=t61rcWkgMzE"}, @@ -892,13 +946,10 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "00-00000000000000001111111111111111-2222222222222222-01", tracestateHeader: "dd=s:2;o:rum:rum;t.dm:-4;t.usr.id:baz64~~,othervendor=t61rcWkgMzE", }, - fullTraceID: "00000000000000001111111111111111", - traceID: 1229782938247303441, - spanID: 2459565876494606882, - priority: 2, // tracestate priority takes precedence - origin: "rum:rum", + out: []uint64{2459565876494606882, 2}, // tracestate priority takes precedence + tid: traceIDFrom64Bits(1229782938247303441), + origin: "rum:rum", propagatingTags: map[string]string{ - "w3cTraceID": "00000000000000001111111111111111", "_dd.p.dm": "-4", "_dd.p.usr.id": "baz64==", "tracestate": "dd=s:2;o:rum:rum;t.dm:-4;t.usr.id:baz64~~,othervendor=t61rcWkgMzE", @@ -909,13 +960,10 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "00-00000000000000001111111111111111-2222222222222222-01", tracestateHeader: "dd=s:;o:rum:rum;t.dm:-4;t.usr.id:baz64~~,othervendor=t61rcWkgMzE", }, - fullTraceID: "00000000000000001111111111111111", - traceID: 1229782938247303441, - spanID: 2459565876494606882, - priority: 1, // traceparent priority takes precedence - origin: "rum:rum", + out: []uint64{2459565876494606882, 1}, // tracestate priority takes precedence + tid: traceIDFrom64Bits(1229782938247303441), + origin: "rum:rum", propagatingTags: map[string]string{ - "w3cTraceID": "00000000000000001111111111111111", "_dd.p.dm": "-4", "_dd.p.usr.id": "baz64==", "tracestate": "dd=s:;o:rum:rum;t.dm:-4;t.usr.id:baz64~~,othervendor=t61rcWkgMzE", @@ -926,14 +974,12 @@ func TestEnvVars(t *testing.T) { traceparentHeader: " \t-00-00000000000000001111111111111111-2222222222222222-01 \t-", tracestateHeader: "othervendor=t61rcWkgMzE,dd=o:rum:rum;s:;t.dm:-4;t.usr.id:baz64~~", }, - fullTraceID: "00000000000000001111111111111111", - traceID: 1229782938247303441, - spanID: 2459565876494606882, - priority: 1, // traceparent priority takes precedence - origin: "rum:rum", + out: []uint64{2459565876494606882, 1}, // tracestate priority takes precedence + tid: traceIDFrom64Bits(1229782938247303441), + + origin: "rum:rum", propagatingTags: map[string]string{ "tracestate": "othervendor=t61rcWkgMzE,dd=o:rum:rum;s:;t.dm:-4;t.usr.id:baz64~~", - "w3cTraceID": "00000000000000001111111111111111", "_dd.p.dm": "-4", "_dd.p.usr.id": "baz64==", }, @@ -943,14 +989,11 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "00-00000000000000001111111111111111-2222222222222222-01", tracestateHeader: "othervendor=t61rcWkgMzE,dd=o:2;s:fake_origin;t.dm:-4;t.usr.id:baz64~~,", }, - fullTraceID: "00000000000000001111111111111111", - traceID: 1229782938247303441, - spanID: 2459565876494606882, - priority: 1, - origin: "2", + out: []uint64{2459565876494606882, 1}, // tracestate priority takes precedence + tid: traceIDFrom64Bits(1229782938247303441), + origin: "2", propagatingTags: map[string]string{ "tracestate": "othervendor=t61rcWkgMzE,dd=o:2;s:fake_origin;t.dm:-4;t.usr.id:baz64~~,", - "w3cTraceID": "00000000000000001111111111111111", "_dd.p.dm": "-4", "_dd.p.usr.id": "baz64==", }, @@ -960,14 +1003,11 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "00-00000000000000001111111111111111-2222222222222222-01", tracestateHeader: "othervendor=t61rcWkgMzE,dd=o:~_~;s:fake_origin;t.dm:-4;t.usr.id:baz64~~,", }, - fullTraceID: "00000000000000001111111111111111", - traceID: 1229782938247303441, - spanID: 2459565876494606882, - priority: 1, - origin: "=_=", + out: []uint64{2459565876494606882, 1}, // tracestate priority takes precedence + tid: traceIDFrom64Bits(1229782938247303441), + origin: "=_=", propagatingTags: map[string]string{ "tracestate": "othervendor=t61rcWkgMzE,dd=o:~_~;s:fake_origin;t.dm:-4;t.usr.id:baz64~~,", - "w3cTraceID": "00000000000000001111111111111111", "_dd.p.dm": "-4", "_dd.p.usr.id": "baz64==", }, @@ -977,40 +1017,36 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "cc-00000000000000001111111111111111-2222222222222222-01-what-the-future-will-be-like", tracestateHeader: "othervendor=t61rcWkgMzE,dd=o:~_~;s:fake_origin;t.dm:-4;t.usr.id:baz64~~,", }, - fullTraceID: "00000000000000001111111111111111", - traceID: 1229782938247303441, - spanID: 2459565876494606882, - priority: 1, - origin: "=_=", + out: []uint64{2459565876494606882, 1}, // tracestate priority takes precedence + tid: traceIDFrom64Bits(1229782938247303441), + origin: "=_=", propagatingTags: map[string]string{ "tracestate": "othervendor=t61rcWkgMzE,dd=o:~_~;s:fake_origin;t.dm:-4;t.usr.id:baz64~~,", - "w3cTraceID": "00000000000000001111111111111111", "_dd.p.dm": "-4", "_dd.p.usr.id": "baz64==", }, }, } - for i, test := range tests { + for i, tc := range tests { t.Run(fmt.Sprintf("#%v extract/valid with env=%q", i, testEnv), func(t *testing.T) { tracer := newTracer(WithHTTPClient(c), withStatsdClient(&statsd.NoOpClient{})) defer tracer.Stop() assert := assert.New(t) - ctx, err := tracer.Extract(test.in) + ctx, err := tracer.Extract(tc.in) if err != nil { t.Fatal(err) } sctx, ok := ctx.(*spanContext) assert.True(ok) - assert.Equal(test.traceID, sctx.traceID) - assert.Equal(test.spanID, sctx.spanID) - assert.Equal(test.origin, sctx.origin) + assert.Equal(tc.tid, sctx.traceID) + assert.Equal(tc.out[0], sctx.spanID) + assert.Equal(tc.origin, sctx.origin) p, ok := sctx.samplingPriority() assert.True(ok) - assert.Equal(test.priority, p) + assert.Equal(int(tc.out[1]), p) - assert.Equal(test.fullTraceID, sctx.trace.propagatingTags[w3cTraceIDTag]) - assert.Equal(test.propagatingTags, sctx.trace.propagatingTags) + assert.Equal(tc.propagatingTags, sctx.trace.propagatingTags) }) } } @@ -1047,12 +1083,12 @@ func TestEnvVars(t *testing.T) { }, } - for i, test := range tests { + for i, tc := range tests { t.Run(fmt.Sprintf("#%v extract/invalid with env=%q", i, testEnv), func(t *testing.T) { tracer := newTracer(WithHTTPClient(c), withStatsdClient(&statsd.NoOpClient{})) defer tracer.Stop() assert := assert.New(t) - ctx, err := tracer.Extract(test) + ctx, err := tracer.Extract(tc) assert.NotNil(err) assert.Nil(ctx) }) @@ -1071,55 +1107,61 @@ func TestEnvVars(t *testing.T) { t.Setenv(k, v) } var tests = []struct { - inHeaders TextMapCarrier - outHeaders TextMapCarrier - traceID uint64 - fullTraceID string - spanID uint64 - priority int - origin string + inHeaders TextMapCarrier + outHeaders TextMapCarrier + sid uint64 + tid traceID + priority int + traceID128 string + origin string }{ { inHeaders: TextMapCarrier{ - traceparentHeader: "00-12345678901234567890123456789012-1234567890123456-00", + traceparentHeader: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00", tracestateHeader: "foo=1,dd=s:-1", }, outHeaders: TextMapCarrier{ - traceparentHeader: "00-12345678901234567890123456789012-1234567890123456-00", - tracestateHeader: "dd=s:-1;o:synthetics,foo=1", + traceparentHeader: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00", + tracestateHeader: "dd=s:-1;o:synthetics;t.tid:4bf92f3577b34da6,foo=1", DefaultPriorityHeader: "-1", - DefaultTraceIDHeader: "12345678901234567890123456789012", - DefaultParentIDHeader: "1234567890123456", - }, - fullTraceID: "12345678901234567890123456789012", - traceID: 1229782938247303441, - spanID: 2459565876494606882, - priority: -1, - origin: "synthetics", + DefaultTraceIDHeader: "4bf92f3577b34da6a3ce929d0e0e4736", + DefaultParentIDHeader: "00f067aa0ba902b7", + }, + sid: 67667974448284343, + tid: traceIDFrom128Bits(5474458728733560230, 11803532876627986230), + priority: -1, + traceID128: "4bf92f3577b34da6", + origin: "synthetics", }, } - for i, test := range tests { + for i, tc := range tests { t.Run(fmt.Sprintf("#%v extract/valid with env=%q", i, testEnv), func(t *testing.T) { tracer := newTracer(WithHTTPClient(c), withStatsdClient(&statsd.NoOpClient{})) defer tracer.Stop() assert := assert.New(t) - ctx, err := tracer.Extract(test.inHeaders) + ctx, err := tracer.Extract(tc.inHeaders) if err != nil { t.Fatal(err) } root := tracer.StartSpan("web.request", ChildOf(ctx)).(*span) defer root.Finish() sctx, ok := ctx.(*spanContext) - sctx.origin = test.origin + sctx.origin = tc.origin + assert.True(ok) + + assert.Equal(tc.tid, sctx.traceID) + assert.Equal(tc.sid, sctx.spanID) + p, ok := sctx.samplingPriority() assert.True(ok) + assert.Equal(tc.priority, p) headers := TextMapCarrier(map[string]string{}) err = tracer.Inject(sctx, headers) assert.True(ok) assert.Nil(err) - assert.Equal(test.outHeaders[traceparentHeader], headers[traceparentHeader]) - assert.Equal(test.outHeaders[tracestateHeader], headers[tracestateHeader]) + checkSameElements(assert, tc.outHeaders[traceparentHeader], headers[traceparentHeader]) + checkSameElements(assert, tc.outHeaders[tracestateHeader], headers[tracestateHeader]) ddTag := strings.SplitN(headers[tracestateHeader], ",", 2)[0] assert.LessOrEqual(len(ddTag), 256) }) @@ -1140,9 +1182,9 @@ func TestEnvVars(t *testing.T) { t.Setenv(k, v) } var tests = []struct { + tid traceID + sid uint64 out TextMapCarrier - traceID uint64 - spanID uint64 priority int origin string propagatingTags map[string]string @@ -1152,8 +1194,8 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "00-00000000000000001111111111111111-2222222222222222-01", tracestateHeader: "dd=s:2;o:rum;t.usr.id: baz64 ~~,othervendor=t61rcWkgMzE", }, - traceID: 1229782938247303441, - spanID: 2459565876494606882, + tid: traceIDFrom64Bits(1229782938247303441), + sid: 2459565876494606882, priority: 2, origin: "rum", propagatingTags: map[string]string{ @@ -1166,8 +1208,8 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "00-00000000000000001111111111111111-2222222222222222-01", tracestateHeader: "dd=s:1;o:rum;t.usr.id:baz64~~", }, - traceID: 1229782938247303441, - spanID: 2459565876494606882, + tid: traceIDFrom64Bits(1229782938247303441), + sid: 2459565876494606882, priority: 1, origin: "rum", propagatingTags: map[string]string{ @@ -1177,16 +1219,15 @@ func TestEnvVars(t *testing.T) { { out: TextMapCarrier{ traceparentHeader: "00-12300000000000001111111111111111-2222222222222222-01", - tracestateHeader: "dd=s:2;o:rum:rum;t.usr.id:baz64~~,othervendor=t61rcWkgMzE", + tracestateHeader: "dd=s:2;o:rum:rum;t.tid:1230000000000000;t.usr.id:baz64~~,othervendor=t61rcWkgMzE", }, - traceID: 1229782938247303441, - spanID: 2459565876494606882, + tid: traceIDFrom128Bits(1310547491564814336, 1229782938247303441), + sid: 2459565876494606882, priority: 2, // tracestate priority takes precedence origin: "rum:rum", propagatingTags: map[string]string{ "_dd.p.usr.id": "baz64==", "tracestate": "dd=s:2;o:rum_rum;t.usr.id:baz64~~,othervendor=t61rcWkgMzE", - w3cTraceIDTag: "12300000000000001111111111111111", }, }, { @@ -1194,8 +1235,8 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "00-00000000000000001111111111111111-2222222222222222-01", tracestateHeader: "dd=s:1;o:rum:rum;t.usr.id:baz64~~,othervendor=t61rcWkgMzE", }, - traceID: 1229782938247303441, - spanID: 2459565876494606882, + tid: traceIDFrom64Bits(1229782938247303441), + sid: 2459565876494606882, priority: 1, // traceparent priority takes precedence origin: "rum:rum", propagatingTags: map[string]string{ @@ -1208,8 +1249,8 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "00-00000000000000001111111111111111-2222222222222222-00", tracestateHeader: "dd=s:-1;o:rum:rum;t.usr.id:baz:64~~,othervendor=t61rcWkgMzE", }, - traceID: 1229782938247303441, - spanID: 2459565876494606882, + tid: traceIDFrom64Bits(1229782938247303441), + sid: 2459565876494606882, priority: -1, // traceparent priority takes precedence origin: "rum:rum", propagatingTags: map[string]string{ @@ -1222,9 +1263,9 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "00-00000000000000001111111111111112-2222222222222222-00", tracestateHeader: "dd=s:0;o:old_tracestate;t.usr.id:baz:64~~ ,a0=a:1,a1=a:1,a2=a:1,a3=a:1,a4=a:1,a5=a:1,a6=a:1,a7=a:1,a8=a:1,a9=a:1,a10=a:1,a11=a:1,a12=a:1,a13=a:1,a14=a:1,a15=a:1,a16=a:1,a17=a:1,a18=a:1,a19=a:1,a20=a:1,a21=a:1,a22=a:1,a23=a:1,a24=a:1,a25=a:1,a26=a:1,a27=a:1,a28=a:1,a29=a:1,a30=a:1", }, - traceID: 1229782938247303442, - spanID: 2459565876494606882, - origin: "old_tracestate", + tid: traceIDFrom64Bits(1229782938247303442), + sid: 2459565876494606882, + origin: "old_tracestate", propagatingTags: map[string]string{ "_dd.p.usr.id": "baz:64== ", "tracestate": "dd=o:very_long_origin_tag,a0=a:1,a1=a:1,a2=a:1,a3=a:1,a4=a:1,a5=a:1,a6=a:1,a7=a:1,a8=a:1,a9=a:1,a10=a:1,a11=a:1,a12=a:1,a13=a:1,a14=a:1,a15=a:1,a16=a:1,a17=a:1,a18=a:1,a19=a:1,a20=a:1,a21=a:1,a22=a:1,a23=a:1,a24=a:1,a25=a:1,a26=a:1,a27=a:1,a28=a:1,a29=a:1,a30=a:1,a31=a:1,a32=a:1", @@ -1235,9 +1276,9 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "00-00000000000000001111111111111112-2222222222222222-00", tracestateHeader: "dd=s:0;o:old_tracestate;t.usr.id:baz:64~~,a0=a:1,a1=a:1,a2=a:1,a3=a:1,a4=a:1,a5=a:1,a6=a:1,a7=a:1,a8=a:1,a9=a:1,a10=a:1,a11=a:1,a12=a:1,a13=a:1,a14=a:1,a15=a:1,a16=a:1,a17=a:1,a18=a:1,a19=a:1,a20=a:1,a21=a:1,a22=a:1,a23=a:1,a24=a:1,a25=a:1,a26=a:1,a27=a:1,a28=a:1,a29=a:1,a30=a:1", }, - traceID: 1229782938247303442, - spanID: 2459565876494606882, - origin: "old_tracestate", + tid: traceIDFrom64Bits(1229782938247303442), + sid: 2459565876494606882, + origin: "old_tracestate", propagatingTags: map[string]string{ "_dd.p.usr.id": "baz:64==", "tracestate": "dd=o:very_long_origin_tag,a0=a:1,a1=a:1,a2=a:1,a3=a:1,a4=a:1,a5=a:1,a6=a:1,a7=a:1,a8=a:1,a9=a:1,a10=a:1,a11=a:1,a12=a:1,a13=a:1,a14=a:1,a15=a:1,a16=a:1,a17=a:1,a18=a:1,a19=a:1,a20=a:1,a21=a:1,a22=a:1,a23=a:1,a24=a:1,a25=a:1,a26=a:1,a27=a:1,a28=a:1,a29=a:1,a30=a:1,a31=a:1,a32=a:1", @@ -1248,9 +1289,9 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "00-00000000000000001111111111111112-2222222222222222-00", tracestateHeader: "dd=s:0;o:old_tracestate;t.usr.id:baz:64~~,foo=bar", }, - traceID: 1229782938247303442, - spanID: 2459565876494606882, - origin: "old_tracestate", + tid: traceIDFrom64Bits(1229782938247303442), + sid: 2459565876494606882, + origin: "old_tracestate", propagatingTags: map[string]string{ "_dd.p.usr.id": "baz:64==", "tracestate": "foo=bar ", @@ -1261,9 +1302,9 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "00-00000000000000001111111111111112-2222222222222222-00", tracestateHeader: "dd=s:0;o:old_tracestate;t.usr.id:baz:64__,foo=bar", }, - traceID: 1229782938247303442, - spanID: 2459565876494606882, - origin: "old_tracestate", + tid: traceIDFrom64Bits(1229782938247303442), + sid: 2459565876494606882, + origin: "old_tracestate", propagatingTags: map[string]string{ "_dd.p.usr.id": "baz:64~~", "tracestate": "\tfoo=bar\t", @@ -1274,34 +1315,40 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "00-00000000000000001111111111111112-2222222222222222-00", tracestateHeader: "dd=s:0;o:~~_;t.usr.id:baz:64__,foo=bar", }, - traceID: 1229782938247303442, - spanID: 2459565876494606882, - origin: "==~", + tid: traceIDFrom64Bits(1229782938247303442), + sid: 2459565876494606882, + origin: "==~", propagatingTags: map[string]string{ "_dd.p.usr.id": "baz:64~~", "tracestate": "\tfoo=bar\t", }, }, } - for i, test := range tests { + for i, tc := range tests { t.Run(fmt.Sprintf("#%d w3c inject with env=%q", i, testEnv), func(t *testing.T) { tracer := newTracer(WithHTTPClient(c), withStatsdClient(&statsd.NoOpClient{})) defer tracer.Stop() assert := assert.New(t) root := tracer.StartSpan("web.request").(*span) - root.SetTag(ext.SamplingPriority, test.priority) + root.SetTag(ext.SamplingPriority, tc.priority) ctx, ok := root.Context().(*spanContext) - ctx.origin = test.origin - ctx.traceID = test.traceID - ctx.spanID = test.spanID - ctx.trace.propagatingTags = test.propagatingTags + ctx.origin = tc.origin + ctx.traceID = tc.tid + ctx.spanID = tc.sid + ctx.trace.propagatingTags = tc.propagatingTags headers := TextMapCarrier(map[string]string{}) err := tracer.Inject(ctx, headers) assert.True(ok) assert.Nil(err) - assert.Equal(test.out[traceparentHeader], headers[traceparentHeader]) - assert.Equal(test.out[tracestateHeader], headers[tracestateHeader]) + checkSameElements(assert, tc.out[traceparentHeader], headers[traceparentHeader]) + if strings.HasSuffix(tc.out[tracestateHeader], ",othervendor=t61rcWkgMzE") { + assert.True(strings.HasSuffix(headers[tracestateHeader], ",othervendor=t61rcWkgMzE")) + // Remove the suffixes for the following check + headers[tracestateHeader] = strings.TrimSuffix(headers[tracestateHeader], ",othervendor=t61rcWkgMzE") + tc.out[tracestateHeader] = strings.TrimSuffix(tc.out[tracestateHeader], ",othervendor=t61rcWkgMzE") + } + checkSameElements(assert, tc.out[tracestateHeader], headers[tracestateHeader]) ddTag := strings.SplitN(headers[tracestateHeader], ",", 2)[0] assert.LessOrEqual(len(ddTag), 256) }) @@ -1314,7 +1361,7 @@ func TestEnvVars(t *testing.T) { root.SetTag(ext.SamplingPriority, ext.PriorityUserKeep) ctx, ok := root.Context().(*spanContext) ctx.origin = "old_tracestate" - ctx.traceID = 1229782938247303442 + ctx.traceID = traceIDFrom64Bits(1229782938247303442) ctx.spanID = 2459565876494606882 ctx.trace.propagatingTags = map[string]string{ "tracestate": "valid_vendor=a:1", @@ -1342,64 +1389,58 @@ func TestEnvVars(t *testing.T) { }) t.Run("datadog extract / w3c,datadog inject", func(t *testing.T) { - testEnvs = []map[string]string{ - {headerPropagationStyleInject: "tracecontext,datadog", headerPropagationStyleExtract: "datadog"}, - } - for _, testEnv := range testEnvs { - for k, v := range testEnv { - t.Setenv(k, v) - } - var tests = []struct { - outHeaders TextMapCarrier - inHeaders TextMapCarrier - }{ - { - outHeaders: TextMapCarrier{ - traceparentHeader: "00-000000000000000000000000075bcd15-000000003ade68b1-00", - tracestateHeader: "dd=s:-2;o:test.origin", - }, - inHeaders: TextMapCarrier{ - DefaultTraceIDHeader: "123456789", - DefaultParentIDHeader: "987654321", - DefaultPriorityHeader: "-2", - originHeader: "test.origin", - }, + t.Setenv(headerPropagationStyleInject, "tracecontext,datadog") + t.Setenv(headerPropagationStyleExtract, "datadog") + var tests = []struct { + outHeaders TextMapCarrier + inHeaders TextMapCarrier + }{ + { + outHeaders: TextMapCarrier{ + traceparentHeader: "00-000000000000000000000000075bcd15-000000003ade68b1-00", + tracestateHeader: "dd=s:-2;o:test.origin", }, - { - outHeaders: TextMapCarrier{ - traceparentHeader: "00-000000000000000000000000075bcd15-000000003ade68b1-00", - tracestateHeader: "dd=s:-2;o:synthetics___web", - }, - inHeaders: TextMapCarrier{ - DefaultTraceIDHeader: "123456789", - DefaultParentIDHeader: "987654321", - DefaultPriorityHeader: "-2", - originHeader: "synthetics;,~web", - }, + inHeaders: TextMapCarrier{ + DefaultTraceIDHeader: "123456789", + DefaultParentIDHeader: "987654321", + DefaultPriorityHeader: "-2", + originHeader: "test.origin", }, - } - for i, test := range tests { - t.Run(fmt.Sprintf("#%d with env=%q", i, testEnv), func(t *testing.T) { - tracer := newTracer(WithHTTPClient(c), withStatsdClient(&statsd.NoOpClient{})) - defer tracer.Stop() - assert := assert.New(t) - ctx, err := tracer.Extract(test.inHeaders) - assert.Nil(err) - - root := tracer.StartSpan("web.request", ChildOf(ctx)).(*span) - defer root.Finish() - sctx, ok := ctx.(*spanContext) - headers := TextMapCarrier(map[string]string{}) - err = tracer.Inject(sctx, headers) + }, + { + outHeaders: TextMapCarrier{ + traceparentHeader: "00-000000000000000000000000075bcd15-000000003ade68b1-00", + tracestateHeader: "dd=s:-2;o:synthetics___web", + }, + inHeaders: TextMapCarrier{ + DefaultTraceIDHeader: "123456789", + DefaultParentIDHeader: "987654321", + DefaultPriorityHeader: "-2", + originHeader: "synthetics;,~web", + }, + }, + } + for i, tc := range tests { + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + tracer := newTracer(WithHTTPClient(c), withStatsdClient(&statsd.NoOpClient{})) + defer tracer.Stop() + assert := assert.New(t) + ctx, err := tracer.Extract(tc.inHeaders) + assert.Nil(err) - assert.True(ok) - assert.Nil(err) - assert.Equal(test.outHeaders[traceparentHeader], headers[traceparentHeader]) - assert.Equal(test.outHeaders[tracestateHeader], headers[tracestateHeader]) - ddTag := strings.SplitN(headers[tracestateHeader], ",", 2)[0] - assert.LessOrEqual(len(ddTag), 256) - }) - } + root := tracer.StartSpan("web.request", ChildOf(ctx)).(*span) + defer root.Finish() + sctx, ok := ctx.(*spanContext) + headers := TextMapCarrier(map[string]string{}) + err = tracer.Inject(sctx, headers) + + assert.True(ok) + assert.Nil(err) + checkSameElements(assert, tc.outHeaders[traceparentHeader], headers[traceparentHeader]) + checkSameElements(assert, tc.outHeaders[tracestateHeader], headers[tracestateHeader]) + ddTag := strings.SplitN(headers[tracestateHeader], ",", 2)[0] + assert.LessOrEqual(len(ddTag), 256) + }) } }) @@ -1415,23 +1456,21 @@ func TestEnvVars(t *testing.T) { } var tests = []struct { in TextMapCarrier - out TextMapCarrier - traceID uint64 - spanID uint64 + outMap TextMapCarrier + out []uint64 // contains [, ] priority float64 origin string }{ { in: TextMapCarrier{ traceparentHeader: "00-12345678901234567890123456789012-1234567890123456-01", - tracestateHeader: "dd=s:2;o:rum;t.usr.id:baz64~~", + tracestateHeader: "dd=s:2;o:rum;t.tid:1234567890123456;t.usr.id:baz64~~", }, - out: TextMapCarrier{ + outMap: TextMapCarrier{ traceparentHeader: "00-12345678901234567890123456789012-1234567890123456-01", - tracestateHeader: "dd=s:2;o:rum;t.usr.id:baz64~~", + tracestateHeader: "dd=s:2;o:rum;t.tid:1234567890123456;t.usr.id:baz64~~", }, - traceID: 8687463697196027922, - spanID: 1311768467284833366, + out: []uint64{8687463697196027922, 1311768467284833366}, priority: 2, origin: "rum", }, @@ -1440,39 +1479,37 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "00-12345678901234567890123456789012-1234567890123456-01", tracestateHeader: "foo=1", }, - out: TextMapCarrier{ + outMap: TextMapCarrier{ traceparentHeader: "00-12345678901234567890123456789012-1234567890123456-01", - tracestateHeader: "dd=s:1,foo=1", + tracestateHeader: "dd=s:1;t.tid:1234567890123456,foo=1", }, - traceID: 8687463697196027922, - spanID: 1311768467284833366, + out: []uint64{8687463697196027922, 1311768467284833366}, priority: 1, }, } - for i, test := range tests { + for i, tc := range tests { t.Run(fmt.Sprintf("#%d w3c inject/extract with env=%q", i, testEnv), func(t *testing.T) { tracer := newTracer(WithHTTPClient(c), withStatsdClient(&statsd.NoOpClient{})) defer tracer.Stop() assert := assert.New(t) - ctx, err := tracer.Extract(test.in) + ctx, err := tracer.Extract(tc.in) if err != nil { t.FailNow() } sctx, ok := ctx.(*spanContext) assert.True(ok) - assert.Equal(test.traceID, sctx.traceID) - assert.Equal(test.spanID, sctx.spanID) - - assert.Equal(test.origin, sctx.origin) - assert.Equal(test.priority, *sctx.trace.priority) + assert.Equal(tc.out[0], sctx.traceID.Lower()) + assert.Equal(tc.out[1], sctx.spanID) + assert.Equal(tc.origin, sctx.origin) + assert.Equal(tc.priority, *sctx.trace.priority) headers := TextMapCarrier(map[string]string{}) err = tracer.Inject(ctx, headers) assert.Nil(err) - assert.Equal(test.out[traceparentHeader], headers[traceparentHeader]) - assert.Equal(test.out[tracestateHeader], headers[tracestateHeader]) + checkSameElements(assert, tc.outMap[traceparentHeader], headers[traceparentHeader]) + checkSameElements(assert, tc.outMap[tracestateHeader], headers[tracestateHeader]) ddTag := strings.SplitN(headers[tracestateHeader], ",", 2)[0] assert.LessOrEqual(len(ddTag), 256) }) @@ -1492,10 +1529,9 @@ func TestEnvVars(t *testing.T) { } var tests = []struct { in TextMapCarrier - out TextMapCarrier - traceID uint64 - spanID uint64 - parentID uint64 + outMap TextMapCarrier + out []uint64 // contains [, ] + tid traceID priority float64 origin string }{ @@ -1504,22 +1540,21 @@ func TestEnvVars(t *testing.T) { traceparentHeader: "00-12345678901234567890123456789012-1234567890123456-01", tracestateHeader: "dd=s:2;o:rum;t.usr.id:baz64~~", }, - out: TextMapCarrier{ + outMap: TextMapCarrier{ traceparentHeader: "00-12345678901234567890123456789012-0000000000000001-01", - tracestateHeader: "dd=s:1;o:rum;t.usr.id:baz64~~", + tracestateHeader: "dd=s:1;o:rum;t.usr.id:baz64~~;t.tid:1234567890123456", }, - traceID: 8687463697196027922, - spanID: 1, - parentID: 1311768467284833366, + out: []uint64{1311768467284833366, 1}, + tid: traceIDFrom128Bits(1311768467284833366, 8687463697196027922), priority: 1, }, } - for i, test := range tests { + for i, tc := range tests { t.Run(fmt.Sprintf("#%d w3c inject/extract with env=%q", i, testEnv), func(t *testing.T) { tracer := newTracer(WithHTTPClient(c), withStatsdClient(&statsd.NoOpClient{})) defer tracer.Stop() assert := assert.New(t) - pCtx, err := tracer.Extract(test.in) + pCtx, err := tracer.Extract(tc.in) if err != nil { t.FailNow() } @@ -1527,18 +1562,19 @@ func TestEnvVars(t *testing.T) { sctx, ok := s.Context().(*spanContext) assert.True(ok) // changing priority must set ctx.updated = true - if test.priority != 0 { - sctx.setSamplingPriority(int(test.priority), samplernames.Unknown) + if tc.priority != 0 { + sctx.setSamplingPriority(int(tc.priority), samplernames.Unknown) } assert.Equal(true, sctx.updated) headers := TextMapCarrier(map[string]string{}) err = tracer.Inject(s.Context(), headers) - assert.Equal(test.traceID, sctx.traceID) - assert.Equal(test.parentID, sctx.span.ParentID) - assert.Equal(test.spanID, sctx.spanID) - assert.Equal(test.out[traceparentHeader], headers[traceparentHeader]) - assert.Equal(test.out[tracestateHeader], headers[tracestateHeader]) + assert.NoError(err) + assert.Equal(tc.tid, sctx.traceID) + assert.Equal(tc.out[0], sctx.span.ParentID) + assert.Equal(tc.out[1], sctx.spanID) + checkSameElements(assert, tc.outMap[traceparentHeader], headers[traceparentHeader]) + checkSameElements(assert, tc.outMap[tracestateHeader], headers[tracestateHeader]) ddTag := strings.SplitN(headers[tracestateHeader], ",", 2)[0] assert.LessOrEqual(len(ddTag), 256) }) @@ -1547,6 +1583,12 @@ func TestEnvVars(t *testing.T) { }) } +func checkSameElements(assert *assert.Assertions, want, got string) { + gotInner, wantInner := strings.TrimPrefix(got, "dd="), strings.TrimPrefix(want, "dd=") + gotInnerList, wantInnerList := strings.Split(gotInner, ";"), strings.Split(wantInner, ";") + assert.ElementsMatch(gotInnerList, wantInnerList) +} + func TestW3CExtractsBaggage(t *testing.T) { tracer := newTracer() defer tracer.Stop() @@ -1577,7 +1619,7 @@ func TestNonePropagator(t *testing.T) { root.SetTag(ext.SamplingPriority, -1) root.SetBaggageItem("item", "x") ctx, ok := root.Context().(*spanContext) - ctx.traceID = 1 + ctx.traceID = traceIDFrom64Bits(1) ctx.spanID = 1 headers := TextMapCarrier(map[string]string{}) err := tracer.Inject(ctx, headers) @@ -1600,7 +1642,7 @@ func TestNonePropagator(t *testing.T) { root.SetTag(ext.SamplingPriority, -1) root.SetBaggageItem("item", "x") ctx, ok := root.Context().(*spanContext) - ctx.traceID = 1 + ctx.traceID = traceIDFrom64Bits(1) ctx.spanID = 1 headers := TextMapCarrier(map[string]string{}) err := tracer.Inject(ctx, headers) @@ -1639,7 +1681,7 @@ func TestNonePropagator(t *testing.T) { root.SetTag(ext.SamplingPriority, -1) root.SetBaggageItem("item", "x") ctx, ok := root.Context().(*spanContext) - ctx.traceID = 1 + ctx.traceID = traceIDFrom64Bits(1) ctx.spanID = 1 headers := TextMapCarrier(map[string]string{}) err := tracer.Inject(ctx, headers) @@ -1663,7 +1705,7 @@ func TestNonePropagator(t *testing.T) { root.SetTag(ext.SamplingPriority, -1) root.SetBaggageItem("item", "x") ctx, ok := root.Context().(*spanContext) - ctx.traceID = 1 + ctx.traceID = traceIDFrom64Bits(1) ctx.spanID = 1 headers := TextMapCarrier(map[string]string{}) err := tracer.Inject(ctx, headers) @@ -1850,8 +1892,6 @@ func FuzzParseTraceparent(f *testing.F) { if parseTraceparent(ctx, header) != nil { t.Skipf("Error parsing parent") } - parsedTraceID := ctx.trace.propagatingTags[w3cTraceIDTag] - parsedSpanID := ctx.spanID parsedSamplingPriority, ok := ctx.samplingPriority() if !ok { t.Skipf("Error retrieving sampling priority") @@ -1864,17 +1904,17 @@ func FuzzParseTraceparent(f *testing.F) { if err != nil { t.Skipf("Error parsing flag") } - if parsedTraceID != strings.ToLower(traceID) { + if gotTraceID := ctx.TraceID128(); gotTraceID != strings.ToLower(traceID) { t.Fatalf(`Inconsistent trace id parsing: - got: %s - wanted: %s - for header of: %s`, parsedTraceID, traceID, header) + got: %s + wanted: %s + for header of: %s`, gotTraceID, traceID, header) } - if parsedSpanID != expectedSpanID { + if ctx.spanID != expectedSpanID { t.Fatalf(`Inconsistent span id parsing: got: %d wanted: %d - for header of: %s`, parsedSpanID, expectedSpanID, header) + for header of: %s`, ctx.spanID, expectedSpanID, header) } if parsedSamplingPriority != int(expectedFlag)&0x1 { t.Fatalf(`Inconsistent flag parsing: @@ -1884,3 +1924,10 @@ func FuzzParseTraceparent(f *testing.F) { } }) } + +func FuzzExtractTraceID128(f *testing.F) { + f.Fuzz(func(t *testing.T, v string) { + ctx := new(spanContext) + extractTraceID128(ctx, v) // make sure it doesn't panic + }) +} diff --git a/ddtrace/tracer/tracer.go b/ddtrace/tracer/tracer.go index 3de6ec8a95..f8d986e0f6 100644 --- a/ddtrace/tracer/tracer.go +++ b/ddtrace/tracer/tracer.go @@ -452,7 +452,7 @@ func (t *tracer) StartSpan(operationName string, options ...ddtrace.StartSpanOpt } if context != nil { // this is a child span - span.TraceID = context.traceID + span.TraceID = context.traceID.Lower() span.ParentID = context.spanID if p, ok := context.samplingPriority(); ok { span.setMetric(keySamplingPriority, float64(p)) diff --git a/ddtrace/tracer/tracer_test.go b/ddtrace/tracer/tracer_test.go index 1dd1d962e4..45b11e16f8 100644 --- a/ddtrace/tracer/tracer_test.go +++ b/ddtrace/tracer/tracer_test.go @@ -8,6 +8,8 @@ package tracer import ( "context" "encoding/base64" + "encoding/binary" + "encoding/hex" "errors" "fmt" "io" @@ -49,6 +51,16 @@ func (t *tracer) newChildSpan(name string, parent *span) *span { return t.StartSpan(name, ChildOf(parent.Context())).(*span) } +func id128FromSpan(assert *assert.Assertions, ctx ddtrace.SpanContext) string { + var w3Cctx ddtrace.SpanContextW3C + var ok bool + w3Cctx, ok = ctx.(ddtrace.SpanContextW3C) + assert.True(ok) + id := w3Cctx.TraceID128() + assert.Len(id, 32) + return id +} + var ( // timeMultiplicator specifies by how long to extend waiting times. // It may be altered in some environments (like AppSec) where things @@ -659,6 +671,43 @@ func TestTracerStartSpanOptions(t *testing.T) { assert.Equal(1.0, span.Metrics[keyTopLevel]) } +func TestTracerStartSpanOptions128(t *testing.T) { + tracer := newTracer() + defer tracer.Stop() + assert := assert.New(t) + t.Run("64-bit-trace-id", func(t *testing.T) { + opts := []StartSpanOption{ + WithSpanID(987654), + } + s := tracer.StartSpan("web.request", opts...).(*span) + assert.Equal(uint64(987654), s.SpanID) + assert.Equal(uint64(987654), s.TraceID) + id := id128FromSpan(assert, s.Context()) + assert.Empty(s.Meta[keyTraceID128]) + idBytes, err := hex.DecodeString(id) + assert.NoError(err) + assert.Equal(uint64(0), binary.BigEndian.Uint64(idBytes[:8])) // high 64 bits should be 0 + assert.Equal(s.Context().TraceID(), binary.BigEndian.Uint64(idBytes[8:])) + }) + t.Run("128-bit-trace-id", func(t *testing.T) { + // Enable 128 bit trace ids + t.Setenv("DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED", "true") + opts128 := []StartSpanOption{ + WithSpanID(987654), + StartTime(time.Unix(123456, 0)), + } + s := tracer.StartSpan("web.request", opts128...).(*span) + assert.Equal(uint64(987654), s.SpanID) + assert.Equal(uint64(987654), s.TraceID) + id := id128FromSpan(assert, s.Context()) + // hex_encoded(<32-bit unix seconds> <32 bits of zero> <64 random bits>) + // 0001e240 (123456) + 00000000 (zeros) + 00000000000f1206 (987654) + assert.Equal("0001e2400000000000000000000f1206", id) + s.Finish() + assert.Equal(id[:16], s.Meta[keyTraceID128]) + }) +} + func TestTracerStartChildSpan(t *testing.T) { t.Run("own-service", func(t *testing.T) { assert := assert.New(t) @@ -806,7 +855,7 @@ func TestTracerSamplingPriorityEmptySpanCtx(t *testing.T) { defer stop() root := newBasicSpan("web.request") spanCtx := &spanContext{ - traceID: root.context.TraceID(), + traceID: traceIDFrom64Bits(root.context.TraceID()), spanID: root.context.SpanID(), trace: &trace{}, } @@ -821,7 +870,7 @@ func TestTracerDDUpstreamServicesManualKeep(t *testing.T) { defer tracer.Stop() root := newBasicSpan("web.request") spanCtx := &spanContext{ - traceID: root.context.TraceID(), + traceID: traceIDFrom64Bits(root.context.TraceID()), spanID: root.context.SpanID(), trace: &trace{}, } @@ -949,19 +998,38 @@ func TestNewSpan(t *testing.T) { } func TestNewSpanChild(t *testing.T) { - assert := assert.New(t) + testNewSpanChild(t, false) + testNewSpanChild(t, true) +} - // the tracer must create child spans - tracer := newTracer(withTransport(newDefaultTransport())) - defer tracer.Stop() - parent := tracer.newRootSpan("pylons.request", "pylons", "/") - child := tracer.newChildSpan("redis.command", parent) - // ids and services are inherited - assert.Equal(parent.SpanID, child.ParentID) - assert.Equal(parent.TraceID, child.TraceID) - assert.Equal(parent.Service, child.Service) - // the resource is not inherited and defaults to the name - assert.Equal("redis.command", child.Resource) +func testNewSpanChild(t *testing.T, is128 bool) { + t.Run(fmt.Sprintf("TestNewChildSpan(is128=%t)", is128), func(*testing.T) { + if is128 { + t.Setenv("DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED", "true") + } + assert := assert.New(t) + + // the tracer must create child spans + tracer := newTracer(withTransport(newDefaultTransport())) + defer tracer.Stop() + parent := tracer.newRootSpan("pylons.request", "pylons", "/") + child := tracer.newChildSpan("redis.command", parent) + // ids and services are inherited + assert.Equal(parent.SpanID, child.ParentID) + assert.Equal(parent.TraceID, child.TraceID) + id := id128FromSpan(assert, child.Context()) + assert.Equal(id128FromSpan(assert, parent.Context()), id) + assert.Equal(parent.Service, child.Service) + // the resource is not inherited and defaults to the name + assert.Equal("redis.command", child.Resource) + + child.Finish() // Meta[keyTraceID128] gets set upon Finish + if is128 { + assert.Equal(id[:16], child.Meta[keyTraceID128]) + } else { + assert.Empty(child.Meta[keyTraceID128]) + } + }) } func TestNewRootSpanHasPid(t *testing.T) {