diff --git a/CHANGELOG.md b/CHANGELOG.md index c58d2d9a88b..69fc5b1f53b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Fix missing `request.GetBody` in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` to correctly handle HTTP2 GOAWAY frame. (#7931) - Fix semconv v1.39.0 generated metric helpers skipping required attributes when extra attributes were empty. (#7964) +- Preserve W3C TraceFlags bitmask (including the random Trace ID flag) during trace context extraction and injection in `go.opentelemetry.io/otel/propagation`. (#7834) ### Removed diff --git a/propagation/trace_context.go b/propagation/trace_context.go index 271ab71f1ae..11f404deb73 100644 --- a/propagation/trace_context.go +++ b/propagation/trace_context.go @@ -46,8 +46,8 @@ func (TraceContext) Inject(ctx context.Context, carrier TextMapCarrier) { carrier.Set(tracestateHeader, ts) } - // Clear all flags other than the trace-context supported sampling bit. - flags := sc.TraceFlags() & trace.FlagsSampled + // Preserve only the spec-defined flags: sampled (0x01) and random (0x02). + flags := sc.TraceFlags() & (trace.FlagsSampled | trace.FlagsRandom) var sb strings.Builder sb.Grow(2 + 32 + 16 + 2 + 3) @@ -104,14 +104,13 @@ func (TraceContext) extract(carrier TextMapCarrier) trace.SpanContext { if !extractPart(opts[:], &h, 2) { return trace.SpanContext{} } - if version == 0 && (h != "" || opts[0] > 2) { - // version 0 not allow extra - // version 0 not allow other flag + if version == 0 && (h != "" || opts[0] > 3) { + // version 0 does not allow extra fields or reserved flag bits. return trace.SpanContext{} } - // Clear all flags other than the trace-context supported sampling bit. - scc.TraceFlags = trace.TraceFlags(opts[0]) & trace.FlagsSampled // nolint:gosec // slice size already checked. + scc.TraceFlags = trace.TraceFlags(opts[0]) & //nolint:gosec // slice size already checked. + (trace.FlagsSampled | trace.FlagsRandom) // Ignore the error returned here. Failure to parse tracestate MUST NOT // affect the parsing of traceparent according to the W3C tracecontext diff --git a/propagation/trace_context_test.go b/propagation/trace_context_test.go index c9c9ac8607c..41557348cb3 100644 --- a/propagation/trace_context_test.go +++ b/propagation/trace_context_test.go @@ -56,6 +56,30 @@ func TestExtractValidTraceContext(t *testing.T) { Remote: true, }), }, + { + name: "random", + header: http.Header{ + traceparent: []string{"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-02"}, + }, + sc: trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.TraceFlags(0x02), + Remote: true, + }), + }, + { + name: "sampled and random", + header: http.Header{ + traceparent: []string{"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-03"}, + }, + sc: trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.TraceFlags(0x03), + Remote: true, + }), + }, { name: "valid tracestate", header: http.Header{ @@ -105,7 +129,7 @@ func TestExtractValidTraceContext(t *testing.T) { }), }, { - name: "future version sample bit set", + name: "future version sample bit set reserved bits zeroed", header: http.Header{ traceparent: []string{"02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-09"}, }, @@ -117,7 +141,7 @@ func TestExtractValidTraceContext(t *testing.T) { }), }, { - name: "future version sample bit not set", + name: "future version reserved bits zeroed", header: http.Header{ traceparent: []string{"02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-08"}, }, @@ -228,10 +252,6 @@ func TestExtractInvalidTraceContextFromHTTPReq(t *testing.T) { name: "zero trace ID and span ID", header: "00-00000000000000000000000000000000-0000000000000000-01", }, - { - name: "trace-flag unused bits set", - header: "00-ab000000000000000000000000000000-cd00000000000000-09", - }, { name: "missing options", header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7", @@ -240,6 +260,14 @@ func TestExtractInvalidTraceContextFromHTTPReq(t *testing.T) { name: "empty options", header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-", }, + { + name: "version 0 reserved trace flag bits set", + header: "00-ab000000000000000000000000000000-cd00000000000000-09", + }, + { + name: "version 0 with extra content", + header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01-extra", + }, } empty := trace.SpanContext{} @@ -287,14 +315,38 @@ func TestInjectValidTraceContext(t *testing.T) { }), }, { - name: "unsupported trace flag bits dropped", + name: "reserved trace flag bits dropped on inject", header: http.Header{ - traceparent: []string{"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"}, + traceparent: []string{"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-03"}, + }, + sc: trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.TraceFlags(0xff), + Remote: true, + }), + }, + { + name: "random", + header: http.Header{ + traceparent: []string{"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-02"}, + }, + sc: trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.TraceFlags(0x02), + Remote: true, + }), + }, + { + name: "sampled and random", + header: http.Header{ + traceparent: []string{"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-03"}, }, sc: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, - TraceFlags: 0xff, + TraceFlags: trace.TraceFlags(0x03), Remote: true, }), }, diff --git a/trace/trace.go b/trace/trace.go index ee6f4bcb2aa..96c06ec321b 100644 --- a/trace/trace.go +++ b/trace/trace.go @@ -12,6 +12,11 @@ const ( // with the sampling bit set means the span is sampled. FlagsSampled = TraceFlags(0x01) + // FlagsRandom is a bitmask with the random trace ID flag set. When + // set, it signals that the trace ID was generated randomly with at + // least 56 bits of randomness (W3C Trace Context Level 2). + FlagsRandom = TraceFlags(0x02) + errInvalidHexID errorConst = "trace-id and span-id can only contain [0-9a-f] characters, all lowercase" errInvalidTraceIDLength errorConst = "hex encoded trace-id must have length equals to 32"