You've already forked opentelemetry-go
							
							
				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:
		| @@ -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