Skip to content

Commit fa8e48b

Browse files
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 <[email protected]>
1 parent 07a26be commit fa8e48b

File tree

3 files changed

+67
-9
lines changed

3 files changed

+67
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
4848
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.
4949
To preserve the prior behavior (scrapes succeed while errors are logged), configure your Prometheus HTTP handler with: `promhttp.HandlerOpts{ ErrorHandling: promhttp.ContinueOnError }`. (#7363)
5050
- The default `TranslationStrategy` in `go.opentelemetry.io/exporters/prometheus` is changed from `otlptranslator.NoUTF8EscapingWithSuffixes` to `otlptranslator.UnderscoreEscapingWithSuffixes`. (#7421)
51+
- 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)
5152
- The `ErrorType` function in `go.opentelemetry.io/otel/semconv/v1.37.0` now handles custom error types.
5253
If an error implements an `ErrorType() string` method, the return value of that method will be used as the error type. (#7442)
5354
- Improve performance of concurrent measurements in `go.opentelemetry.io/otel/sdk/metric`. (#7427)

exporters/otlp/otlptrace/internal/tracetransform/span.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func span(sd tracesdk.ReadOnlySpan) *tracepb.Span {
113113
if psid := sd.Parent().SpanID(); psid.IsValid() {
114114
s.ParentSpanId = psid[:]
115115
}
116-
s.Flags = buildSpanFlags(sd.Parent())
116+
s.Flags = buildSpanFlagsWith(sd.SpanContext().TraceFlags(), sd.Parent())
117117

118118
return s
119119
}
@@ -159,7 +159,7 @@ func links(links []tracesdk.Link) []*tracepb.Span_Link {
159159
tid := otLink.SpanContext.TraceID()
160160
sid := otLink.SpanContext.SpanID()
161161

162-
flags := buildSpanFlags(otLink.SpanContext)
162+
flags := buildSpanFlagsWith(otLink.SpanContext.TraceFlags(), otLink.SpanContext)
163163

164164
sl = append(sl, &tracepb.Span_Link{
165165
TraceId: tid[:],
@@ -172,13 +172,15 @@ func links(links []tracesdk.Link) []*tracepb.Span_Link {
172172
return sl
173173
}
174174

175-
func buildSpanFlags(sc trace.SpanContext) uint32 {
176-
flags := tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK
177-
if sc.IsRemote() {
178-
flags |= tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK
175+
func buildSpanFlagsWith(tf trace.TraceFlags, parent trace.SpanContext) uint32 {
176+
// Lower 8 bits are the W3C TraceFlags; always indicate that we know whether the parent is remote
177+
flags := uint32(tf) | uint32(tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK)
178+
// Set the parent-is-remote bit when applicable
179+
if parent.IsRemote() {
180+
flags |= uint32(tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK)
179181
}
180182

181-
return uint32(flags) // nolint:gosec // Flags is a bitmask and can't be negative
183+
return flags // nolint:gosec // Flags is a bitmask and can't be negative
182184
}
183185

184186
// spanEvents transforms span Events to an OTLP span events.

exporters/otlp/otlptrace/internal/tracetransform/span_test.go

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,11 +207,66 @@ func TestBuildSpanFlags(t *testing.T) {
207207
},
208208
} {
209209
t.Run(tt.name, func(t *testing.T) {
210-
assert.Equal(t, tt.wantFlags, buildSpanFlags(tt.spanContext))
210+
assert.Equal(t, tt.wantFlags, buildSpanFlagsWith(tt.spanContext.TraceFlags(), tt.spanContext))
211211
})
212212
}
213213
}
214214

215+
func TestSpanFlagsLower8BitsFromTraceFlags(t *testing.T) {
216+
for _, tc := range []struct {
217+
name string
218+
traceFlags trace.TraceFlags
219+
parentRemote bool
220+
wantLow8 uint32
221+
wantMask uint32
222+
}{
223+
{name: "unsampled root", traceFlags: 0x00, parentRemote: false, wantLow8: 0x00, wantMask: 0x100},
224+
{name: "sampled root", traceFlags: 0x01, parentRemote: false, wantLow8: 0x01, wantMask: 0x100},
225+
{name: "custom bits root", traceFlags: 0x05, parentRemote: false, wantLow8: 0x05, wantMask: 0x100},
226+
{name: "unsampled remote parent", traceFlags: 0x00, parentRemote: true, wantLow8: 0x00, wantMask: 0x300},
227+
{name: "sampled remote parent", traceFlags: 0x01, parentRemote: true, wantLow8: 0x01, wantMask: 0x300},
228+
} {
229+
t.Run(tc.name, func(t *testing.T) {
230+
parent := trace.NewSpanContext(trace.SpanContextConfig{Remote: tc.parentRemote})
231+
got := buildSpanFlagsWith(tc.traceFlags, parent)
232+
assert.Equal(t, tc.wantLow8, got&0xff)
233+
assert.Equal(t, tc.wantMask, got&0x300)
234+
// Ensure higher bits are not set beyond 0-9
235+
assert.Equal(t, uint32(0), got&^uint32(0x3ff))
236+
})
237+
}
238+
}
239+
240+
func TestSpanAndLinkExportLower8Bits(t *testing.T) {
241+
// Span: sampled child with local parent
242+
spanData := tracetest.SpanStub{
243+
SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
244+
TraceID: trace.TraceID{0x1},
245+
SpanID: trace.SpanID{0x2},
246+
TraceFlags: trace.TraceFlags(0x01),
247+
}),
248+
Parent: trace.NewSpanContext(trace.SpanContextConfig{}),
249+
Name: "flags-test",
250+
}
251+
rss := Spans(tracetest.SpanStubs{spanData}.Snapshots())
252+
require.Len(t, rss, 1)
253+
scopeSpans := rss[0].GetScopeSpans()
254+
require.Len(t, scopeSpans, 1)
255+
require.Len(t, scopeSpans[0].Spans, 1)
256+
s := scopeSpans[0].Spans[0]
257+
assert.Equal(t, uint32(0x01), s.Flags&0xff)
258+
assert.Equal(t, uint32(0x100), s.Flags&0x300)
259+
260+
// Link: sampled link local
261+
l := []tracesdk.Link{
262+
{SpanContext: trace.NewSpanContext(trace.SpanContextConfig{TraceFlags: 0x01})},
263+
}
264+
gotLinks := links(l)
265+
require.Len(t, gotLinks, 1)
266+
assert.Equal(t, uint32(0x01), gotLinks[0].Flags&0xff)
267+
assert.Equal(t, uint32(0x100), gotLinks[0].Flags&0x300)
268+
}
269+
215270
func TestNilSpan(t *testing.T) {
216271
assert.Nil(t, span(nil))
217272
}
@@ -331,7 +386,7 @@ func TestSpanData(t *testing.T) {
331386
SpanId: []byte{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
332387
ParentSpanId: []byte{0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8},
333388
TraceState: "key1=val1,key2=val2",
334-
Flags: 0x300,
389+
Flags: 0x300, // lower 8 bits (trace flags) are 0x00 in this fixture; update in new tests below
335390
Name: spanData.Name,
336391
Kind: tracepb.Span_SPAN_KIND_SERVER,
337392
StartTimeUnixNano: uint64(startTime.UnixNano()),

0 commit comments

Comments
 (0)