You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2026-06-03 18:35:08 +02:00
OTLP trace exporter include W3C trace flags (bits 0–7) in Span.Flags (#7438)
Closes #7436 Span.Flags should include: Bits 0–7: span’s W3C TraceFlags (e.g., sampled) Bits 8–9: “has parent isRemote” and “parent isRemote” per OTLP spec Update the trace exporter to include the span’s W3C trace flags in the lower 8 bits and keep the existing 8–9 isRemote logic. Conceptually: For spans: flags := uint32(sd.SpanContext().TraceFlags() & 0xff) Always set SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK If sd.Parent().IsRemote(), also set SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK Assign s.Flags = flags Apply the same for links using the link’s SpanContext.TraceFlags() for bits 0–7 and the link’s SpanContext.IsRemote() for bits 8–9. --------- Co-authored-by: Damien Mathieu <42@dmathieu.com>
This commit is contained in:
@@ -48,6 +48,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
Now, when translation would drop data (e.g., invalid label/value), the exporter emits a `NewInvalidMetric`, and Prometheus scrapes **fail with HTTP 500** by default.
|
||||
To preserve the prior behavior (scrapes succeed while errors are logged), configure your Prometheus HTTP handler with: `promhttp.HandlerOpts{ ErrorHandling: promhttp.ContinueOnError }`. (#7363)
|
||||
- The default `TranslationStrategy` in `go.opentelemetry.io/exporters/prometheus` is changed from `otlptranslator.NoUTF8EscapingWithSuffixes` to `otlptranslator.UnderscoreEscapingWithSuffixes`. (#7421)
|
||||
- Include W3C TraceFlags (bits 0–7) in the OTLP `Span.Flags` field in `go.opentelemetry.io/exporters/otlp/otlptrace/otlptracehttp` and `go.opentelemetry.io/exporters/otlp/otlptrace/otlptracegrpc`. (#7438)
|
||||
- The `ErrorType` function in `go.opentelemetry.io/otel/semconv/v1.37.0` now handles custom error types.
|
||||
If an error implements an `ErrorType() string` method, the return value of that method will be used as the error type. (#7442)
|
||||
- Improve performance of concurrent measurements in `go.opentelemetry.io/otel/sdk/metric`. (#7427)
|
||||
|
||||
@@ -113,7 +113,7 @@ func span(sd tracesdk.ReadOnlySpan) *tracepb.Span {
|
||||
if psid := sd.Parent().SpanID(); psid.IsValid() {
|
||||
s.ParentSpanId = psid[:]
|
||||
}
|
||||
s.Flags = buildSpanFlags(sd.Parent())
|
||||
s.Flags = buildSpanFlagsWith(sd.SpanContext().TraceFlags(), sd.Parent())
|
||||
|
||||
return s
|
||||
}
|
||||
@@ -159,7 +159,7 @@ func links(links []tracesdk.Link) []*tracepb.Span_Link {
|
||||
tid := otLink.SpanContext.TraceID()
|
||||
sid := otLink.SpanContext.SpanID()
|
||||
|
||||
flags := buildSpanFlags(otLink.SpanContext)
|
||||
flags := buildSpanFlagsWith(otLink.SpanContext.TraceFlags(), otLink.SpanContext)
|
||||
|
||||
sl = append(sl, &tracepb.Span_Link{
|
||||
TraceId: tid[:],
|
||||
@@ -172,13 +172,15 @@ func links(links []tracesdk.Link) []*tracepb.Span_Link {
|
||||
return sl
|
||||
}
|
||||
|
||||
func buildSpanFlags(sc trace.SpanContext) uint32 {
|
||||
flags := tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK
|
||||
if sc.IsRemote() {
|
||||
flags |= tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK
|
||||
func buildSpanFlagsWith(tf trace.TraceFlags, parent trace.SpanContext) uint32 {
|
||||
// Lower 8 bits are the W3C TraceFlags; always indicate that we know whether the parent is remote
|
||||
flags := uint32(tf) | uint32(tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK)
|
||||
// Set the parent-is-remote bit when applicable
|
||||
if parent.IsRemote() {
|
||||
flags |= uint32(tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK)
|
||||
}
|
||||
|
||||
return uint32(flags) // nolint:gosec // Flags is a bitmask and can't be negative
|
||||
return flags // nolint:gosec // Flags is a bitmask and can't be negative
|
||||
}
|
||||
|
||||
// spanEvents transforms span Events to an OTLP span events.
|
||||
|
||||
@@ -207,11 +207,66 @@ func TestBuildSpanFlags(t *testing.T) {
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.wantFlags, buildSpanFlags(tt.spanContext))
|
||||
assert.Equal(t, tt.wantFlags, buildSpanFlagsWith(tt.spanContext.TraceFlags(), tt.spanContext))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpanFlagsLower8BitsFromTraceFlags(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
traceFlags trace.TraceFlags
|
||||
parentRemote bool
|
||||
wantLow8 uint32
|
||||
wantMask uint32
|
||||
}{
|
||||
{name: "unsampled root", traceFlags: 0x00, parentRemote: false, wantLow8: 0x00, wantMask: 0x100},
|
||||
{name: "sampled root", traceFlags: 0x01, parentRemote: false, wantLow8: 0x01, wantMask: 0x100},
|
||||
{name: "custom bits root", traceFlags: 0x05, parentRemote: false, wantLow8: 0x05, wantMask: 0x100},
|
||||
{name: "unsampled remote parent", traceFlags: 0x00, parentRemote: true, wantLow8: 0x00, wantMask: 0x300},
|
||||
{name: "sampled remote parent", traceFlags: 0x01, parentRemote: true, wantLow8: 0x01, wantMask: 0x300},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
parent := trace.NewSpanContext(trace.SpanContextConfig{Remote: tc.parentRemote})
|
||||
got := buildSpanFlagsWith(tc.traceFlags, parent)
|
||||
assert.Equal(t, tc.wantLow8, got&0xff)
|
||||
assert.Equal(t, tc.wantMask, got&0x300)
|
||||
// Ensure higher bits are not set beyond 0-9
|
||||
assert.Equal(t, uint32(0), got&^uint32(0x3ff))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpanAndLinkExportLower8Bits(t *testing.T) {
|
||||
// Span: sampled child with local parent
|
||||
spanData := tracetest.SpanStub{
|
||||
SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
|
||||
TraceID: trace.TraceID{0x1},
|
||||
SpanID: trace.SpanID{0x2},
|
||||
TraceFlags: trace.TraceFlags(0x01),
|
||||
}),
|
||||
Parent: trace.NewSpanContext(trace.SpanContextConfig{}),
|
||||
Name: "flags-test",
|
||||
}
|
||||
rss := Spans(tracetest.SpanStubs{spanData}.Snapshots())
|
||||
require.Len(t, rss, 1)
|
||||
scopeSpans := rss[0].GetScopeSpans()
|
||||
require.Len(t, scopeSpans, 1)
|
||||
require.Len(t, scopeSpans[0].Spans, 1)
|
||||
s := scopeSpans[0].Spans[0]
|
||||
assert.Equal(t, uint32(0x01), s.Flags&0xff)
|
||||
assert.Equal(t, uint32(0x100), s.Flags&0x300)
|
||||
|
||||
// Link: sampled link local
|
||||
l := []tracesdk.Link{
|
||||
{SpanContext: trace.NewSpanContext(trace.SpanContextConfig{TraceFlags: 0x01})},
|
||||
}
|
||||
gotLinks := links(l)
|
||||
require.Len(t, gotLinks, 1)
|
||||
assert.Equal(t, uint32(0x01), gotLinks[0].Flags&0xff)
|
||||
assert.Equal(t, uint32(0x100), gotLinks[0].Flags&0x300)
|
||||
}
|
||||
|
||||
func TestNilSpan(t *testing.T) {
|
||||
assert.Nil(t, span(nil))
|
||||
}
|
||||
@@ -331,7 +386,7 @@ func TestSpanData(t *testing.T) {
|
||||
SpanId: []byte{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
|
||||
ParentSpanId: []byte{0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8},
|
||||
TraceState: "key1=val1,key2=val2",
|
||||
Flags: 0x300,
|
||||
Flags: 0x300, // lower 8 bits (trace flags) are 0x00 in this fixture; update in new tests below
|
||||
Name: spanData.Name,
|
||||
Kind: tracepb.Span_SPAN_KIND_SERVER,
|
||||
StartTimeUnixNano: uint64(startTime.UnixNano()),
|
||||
|
||||
Reference in New Issue
Block a user