Skip to content

Commit

Permalink
tracer: support 128-bit trace ids (#1833)
Browse files Browse the repository at this point in the history
  • Loading branch information
katiehockman authored and Eliott Bouhana committed Mar 31, 2023
1 parent 8d1e83e commit 5df1928
Show file tree
Hide file tree
Showing 11 changed files with 751 additions and 370 deletions.
18 changes: 16 additions & 2 deletions ddtrace/ddtrace.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions ddtrace/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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")

Expand Down
41 changes: 39 additions & 2 deletions ddtrace/tracer/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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()
Expand Down
16 changes: 13 additions & 3 deletions ddtrace/tracer/span.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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().
Expand Down
66 changes: 65 additions & 1 deletion ddtrace/tracer/span_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit 5df1928

Please sign in to comment.