1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-10-31 00:07:40 +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:
Nikhil Mantri
2025-10-13 13:22:27 +05:30
committed by GitHub
parent 07a26be758
commit fa8e48ba88
3 changed files with 67 additions and 9 deletions

View File

@@ -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)

View File

@@ -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.

View File

@@ -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()),