You've already forked opentelemetry-go
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:
@@ -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)
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user