1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2026-06-19 21:45:50 +02:00

trace: add Random Trace ID Flag (#8012)

## Summary

Adds API support for the W3C tracecontext random flag on `TraceFlags`
and `SpanContext` in `go.opentelemetry.io/otel/trace`:

- **`TraceFlags.IsRandom()`** — reports whether the random bit is set
- **`TraceFlags.WithRandom(bool)`** — sets or clears the random bit in a
copy of the flags
- **`SpanContext.IsRandom()`** — reports whether the random bit is set
in the span context's trace flags

These mirror the existing `IsSampled` / `WithSampled` pattern.

## Motivation

The W3C Trace Context spec defines a random bit in trace flags to
indicate probabilistic sampling. This is required for [TraceIdRatioBased
sampler](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#traceidratiobased)
support, where the random bit is set when a trace is probabilistically
sampled and is used for extrapolated span metrics.

## Related

Part of #7928 (Support TraceIdRatioBased Sampler). This PR adds the
trace API needed for that work; sampler implementation and tracestate
`th` handling will follow in separate PRs.

## Co-Author
Joshua MacDonald <jmacd@users.noreply.github.com>

---------

Co-authored-by: Joshua MacDonald <jmacd@users.noreply.github.com>
Co-authored-by: Robert Pająk <pellared@hotmail.com>
Co-authored-by: Damien Mathieu <42@dmathieu.com>
This commit is contained in:
Yuanyuan Zhao
2026-03-13 04:22:56 -04:00
committed by GitHub
parent 9c5a5df1a5
commit 2ffde5a428
3 changed files with 121 additions and 0 deletions
+1
View File
@@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added
- Add `IsRandom` and `WithRandom` on `TraceFlags`, and `IsRandom` on `SpanContext` in `go.opentelemetry.io/otel/trace` for [W3C Trace Context Level 2 Random Trace ID Flag](https://www.w3.org/TR/trace-context-2/#random-trace-id-flag) support. (#8012)
- Add service detection with `WithService` in `go.opentelemetry.io/otel/sdk/resource`. (#7642)
- Support attributes with empty value (`attribute.EMPTY`) in OTLP exporters (`go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`, `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`, `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`, `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`, `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`, `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`). (#8038)
- Support attributes with empty value (`attribute.EMPTY`) in `go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest`. (#8038)
+19
View File
@@ -196,6 +196,20 @@ func (tf TraceFlags) WithSampled(sampled bool) TraceFlags { // nolint:revive //
return tf &^ FlagsSampled
}
// IsRandom reports whether the random bit is set in the TraceFlags.
func (tf TraceFlags) IsRandom() bool {
return tf&FlagsRandom == FlagsRandom
}
// WithRandom sets the random bit in a new copy of the TraceFlags.
func (tf TraceFlags) WithRandom(random bool) TraceFlags { // nolint:revive // random is not a control flag.
if random {
return tf | FlagsRandom
}
return tf &^ FlagsRandom
}
// MarshalJSON implements a custom marshal function to encode TraceFlags
// as a hex string.
func (tf TraceFlags) MarshalJSON() ([]byte, error) {
@@ -322,6 +336,11 @@ func (sc SpanContext) IsSampled() bool {
return sc.traceFlags.IsSampled()
}
// IsRandom reports whether the random bit is set in the SpanContext's TraceFlags.
func (sc SpanContext) IsRandom() bool {
return sc.traceFlags.IsRandom()
}
// WithTraceFlags returns a new SpanContext with the TraceFlags replaced.
func (sc SpanContext) WithTraceFlags(flags TraceFlags) SpanContext {
return SpanContext{
+101
View File
@@ -107,6 +107,35 @@ func TestSpanContextIsSampled(t *testing.T) {
}
}
func TestSpanContextIsRandom(t *testing.T) {
for _, testcase := range []struct {
name string
tf TraceFlags
want bool
}{
{
name: "SpanContext.IsRandom() returns false if sc is not random",
want: false,
}, {
name: "SpanContext.IsRandom() returns true if sc is random",
tf: FlagsRandom,
want: true,
},
} {
t.Run(testcase.name, func(t *testing.T) {
sc := SpanContext{
traceFlags: testcase.tf,
}
have := sc.IsRandom()
if have != testcase.want {
t.Errorf("Want: %v, but have: %v", testcase.want, have)
}
})
}
}
func TestSpanContextIsRemote(t *testing.T) {
for _, testcase := range []struct {
name string
@@ -403,6 +432,78 @@ func TestTraceFlagsWithSampled(t *testing.T) {
}
}
func TestTraceFlagsIsRandom(t *testing.T) {
for _, testcase := range []struct {
name string
tf TraceFlags
want bool
}{
{
name: "random",
tf: FlagsRandom,
want: true,
}, {
name: "unused bits are ignored, still not random",
tf: ^FlagsRandom,
want: false,
}, {
name: "unused bits are ignored, still random",
tf: FlagsRandom | ^FlagsRandom,
want: true,
}, {
name: "not random/default",
want: false,
},
} {
t.Run(testcase.name, func(t *testing.T) {
have := testcase.tf.IsRandom()
if have != testcase.want {
t.Errorf("Want: %v, but have: %v", testcase.want, have)
}
})
}
}
func TestTraceFlagsWithRandom(t *testing.T) {
for _, testcase := range []struct {
name string
start TraceFlags
random bool
want TraceFlags
}{
{
name: "random unchanged",
start: FlagsRandom,
want: FlagsRandom,
random: true,
}, {
name: "become random",
want: FlagsRandom,
random: true,
}, {
name: "unused bits are ignored, still not random",
start: ^FlagsRandom,
want: ^FlagsRandom,
random: false,
}, {
name: "unused bits are ignored, becomes random",
start: ^FlagsRandom,
want: FlagsRandom | ^FlagsRandom,
random: true,
}, {
name: "not random/default",
random: false,
},
} {
t.Run(testcase.name, func(t *testing.T) {
have := testcase.start.WithRandom(testcase.random)
if have != testcase.want {
t.Errorf("Want: %v, but have: %v", testcase.want, have)
}
})
}
}
func TestStringTraceID(t *testing.T) {
for _, testcase := range []struct {
name string