From a1d43e8d4851cd5ba394c4466232542e50fbcdf8 Mon Sep 17 00:00:00 2001 From: nikhilmantri0902 Date: Thu, 2 Oct 2025 13:24:57 +0530 Subject: [PATCH 1/3] chore: added full trace flags implementation and tests --- .../_internal/trace_encoder/__init__.py | 16 ++++-- .../tests/test_trace_encoder.py | 55 ++++++++++++++++++- .../tests/test_otlp_trace_exporter.py | 10 ++-- 3 files changed, 68 insertions(+), 13 deletions(-) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/trace_encoder/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/trace_encoder/__init__.py index 388d229bab6..3d54a83ac56 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/trace_encoder/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/trace_encoder/__init__.py @@ -105,10 +105,14 @@ def _encode_resource_spans( return pb2_resource_spans -def _span_flags(parent_span_context: Optional[SpanContext]) -> int: - flags = PB2SpanFlags.SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK - if parent_span_context and parent_span_context.is_remote: - flags |= PB2SpanFlags.SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK +def _span_flags(child_trace_flags: int, parent_span_context: Optional[SpanContext]) -> int: + # Lower 8 bits: W3C TraceFlags + flags = int(child_trace_flags) & int(PB2SpanFlags.SPAN_FLAGS_TRACE_FLAGS_MASK) + # Always indicate whether parent remote information is known + flags |= int(PB2SpanFlags.SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK) + # Set remote bit when applicable + if parent_span_context and getattr(parent_span_context, "is_remote", False): + flags |= int(PB2SpanFlags.SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) return flags @@ -130,7 +134,7 @@ def _encode_span(sdk_span: ReadableSpan) -> PB2SPan: dropped_attributes_count=sdk_span.dropped_attributes, dropped_events_count=sdk_span.dropped_events, dropped_links_count=sdk_span.dropped_links, - flags=_span_flags(sdk_span.parent), + flags=_span_flags(getattr(span_context, "trace_flags", 0), sdk_span.parent), ) @@ -161,7 +165,7 @@ def _encode_links(links: Sequence[Link]) -> Sequence[PB2SPan.Link]: span_id=_encode_span_id(link.context.span_id), attributes=_encode_attributes(link.attributes), dropped_attributes_count=link.dropped_attributes, - flags=_span_flags(link.context), + flags=_span_flags(getattr(link.context, "trace_flags", 0), link.context), ) pb2_links.append(encoded_link) return pb2_links diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_trace_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_trace_encoder.py index bf78526d7e4..7c145615675 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_trace_encoder.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_trace_encoder.py @@ -24,6 +24,8 @@ from opentelemetry.exporter.otlp.proto.common._internal.trace_encoder import ( _SPAN_KIND_MAP, _encode_status, + _encode_span, + _encode_links, ) from opentelemetry.exporter.otlp.proto.common.trace_encoder import encode_spans from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import ( @@ -43,6 +45,7 @@ from opentelemetry.proto.trace.v1.trace_pb2 import ScopeSpans as PB2ScopeSpans from opentelemetry.proto.trace.v1.trace_pb2 import Span as PB2SPan from opentelemetry.proto.trace.v1.trace_pb2 import Status as PB2Status +from opentelemetry.proto.trace.v1.trace_pb2 import SpanFlags as PB2SpanFlags from opentelemetry.sdk.trace import Event as SDKEvent from opentelemetry.sdk.trace import Resource as SDKResource from opentelemetry.sdk.trace import SpanContext as SDKSpanContext @@ -291,14 +294,14 @@ def get_exhaustive_test_spans( ), ), ], - flags=0x100, + flags=0x101, ) ], status=PB2Status( code=SDKStatusCode.ERROR.value, message="Example description", ), - flags=0x300, + flags=0x301, ) ], ), @@ -501,3 +504,51 @@ def test_encode_status_code_translations(self): code=SDKStatusCode.ERROR.value, ), ) + + +class TestSpanFlagsEncoding(unittest.TestCase): + def test_span_flags_root_unsampled(self): + sc = SDKSpanContext(0x1, 0x2, is_remote=False, trace_flags=0x00) + span = SDKSpan(name="root", context=sc, parent=None) + span.start(); span.end() + pb = _encode_span(span) + assert (pb.flags & PB2SpanFlags.SPAN_FLAGS_TRACE_FLAGS_MASK) == 0x00 + assert (pb.flags & PB2SpanFlags.SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK) != 0 + assert (pb.flags & PB2SpanFlags.SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) == 0 + assert (pb.flags & ~0x3FF) == 0 + + def test_span_flags_root_sampled(self): + sc = SDKSpanContext(0x1, 0x2, is_remote=False, trace_flags=0x01) + span = SDKSpan(name="root", context=sc, parent=None) + span.start(); span.end() + pb = _encode_span(span) + assert (pb.flags & PB2SpanFlags.SPAN_FLAGS_TRACE_FLAGS_MASK) == 0x01 + assert (pb.flags & PB2SpanFlags.SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK) != 0 + assert (pb.flags & PB2SpanFlags.SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) == 0 + assert (pb.flags & ~0x3FF) == 0 + + def test_span_flags_remote_parent_sampled(self): + parent = SDKSpanContext(0x1, 0x9, is_remote=True) + sc = SDKSpanContext(0x1, 0x2, is_remote=False, trace_flags=0x01) + span = SDKSpan(name="child", context=sc, parent=parent) + span.start(); span.end() + pb = _encode_span(span) + assert (pb.flags & PB2SpanFlags.SPAN_FLAGS_TRACE_FLAGS_MASK) == 0x01 + assert (pb.flags & PB2SpanFlags.SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK) != 0 + assert (pb.flags & PB2SpanFlags.SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) != 0 + assert (pb.flags & ~0x3FF) == 0 + + def test_link_flags_local_and_remote(self): + # local sampled link + l1 = SDKLink(SDKSpanContext(0x1, 0x2, is_remote=False, trace_flags=0x01)) + # remote sampled link + l2 = SDKLink(SDKSpanContext(0x1, 0x3, is_remote=True, trace_flags=0x01)) + pb_links = _encode_links([l1, l2]) + assert (pb_links[0].flags & PB2SpanFlags.SPAN_FLAGS_TRACE_FLAGS_MASK) == 0x01 + assert (pb_links[0].flags & PB2SpanFlags.SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK) != 0 + assert (pb_links[0].flags & PB2SpanFlags.SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) == 0 + assert (pb_links[0].flags & ~0x3FF) == 0 + assert (pb_links[1].flags & PB2SpanFlags.SPAN_FLAGS_TRACE_FLAGS_MASK) == 0x01 + assert (pb_links[1].flags & PB2SpanFlags.SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK) != 0 + assert (pb_links[1].flags & PB2SpanFlags.SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) != 0 + assert (pb_links[1].flags & ~0x3FF) == 0 diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_trace_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_trace_exporter.py index cbe6298df77..bcf8404f171 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_trace_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_trace_exporter.py @@ -466,7 +466,7 @@ def test_translate_spans(self): ), ), ], - flags=0x300, + flags=0x300, # updated below in more focused tests ) ], flags=0x300, @@ -570,10 +570,10 @@ def test_translate_spans_multi(self): ), ), ], - flags=0x300, + flags=0x301, ) ], - flags=0x300, + flags=0x301, ) ], ), @@ -603,7 +603,7 @@ def test_translate_spans_multi(self): OTLPSpan.SpanKind.SPAN_KIND_INTERNAL ), status=Status(code=0, message=""), - flags=0x300, + flags=0x301, ) ], ), @@ -645,7 +645,7 @@ def test_translate_spans_multi(self): OTLPSpan.SpanKind.SPAN_KIND_INTERNAL ), status=Status(code=0, message=""), - flags=0x300, + flags=0x301, ) ], ) From acc6f2e39cdc55738acf0e5acd6fdd4f80ced97a Mon Sep 17 00:00:00 2001 From: nikhilmantri0902 Date: Thu, 2 Oct 2025 13:30:32 +0530 Subject: [PATCH 2/3] chore: added changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90fcb56cb7e..b1961da8efb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#4755](https://github.com/open-telemetry/opentelemetry-python/pull/4755)) - logs: extend Logger.emit to accept separated keyword arguments ([#4737](https://github.com/open-telemetry/opentelemetry-python/pull/4737)) +- otlp exporters (trace): include W3C TraceFlags (bits 0–7) in OTLP `Span.flags` alongside parent isRemote bits (8–9) + ([#4761](https://github.com/open-telemetry/opentelemetry-python/pull/4761)) ## Version 1.37.0/0.58b0 (2025-09-11) From 27a12dcd9c1936c3d23f8aa96c05d7b7ea49016f Mon Sep 17 00:00:00 2001 From: nikhilmantri0902 Date: Fri, 3 Oct 2025 23:37:23 +0530 Subject: [PATCH 3/3] chore: removed getattr method and unnecessay int typecasts --- .../proto/common/_internal/trace_encoder/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/trace_encoder/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/trace_encoder/__init__.py index 3d54a83ac56..9c5cbfe3a2b 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/trace_encoder/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/trace_encoder/__init__.py @@ -107,12 +107,12 @@ def _encode_resource_spans( def _span_flags(child_trace_flags: int, parent_span_context: Optional[SpanContext]) -> int: # Lower 8 bits: W3C TraceFlags - flags = int(child_trace_flags) & int(PB2SpanFlags.SPAN_FLAGS_TRACE_FLAGS_MASK) + flags = child_trace_flags & PB2SpanFlags.SPAN_FLAGS_TRACE_FLAGS_MASK # Always indicate whether parent remote information is known - flags |= int(PB2SpanFlags.SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK) + flags |= PB2SpanFlags.SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK # Set remote bit when applicable - if parent_span_context and getattr(parent_span_context, "is_remote", False): - flags |= int(PB2SpanFlags.SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) + if parent_span_context and parent_span_context.is_remote: + flags |= PB2SpanFlags.SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK return flags @@ -134,7 +134,7 @@ def _encode_span(sdk_span: ReadableSpan) -> PB2SPan: dropped_attributes_count=sdk_span.dropped_attributes, dropped_events_count=sdk_span.dropped_events, dropped_links_count=sdk_span.dropped_links, - flags=_span_flags(getattr(span_context, "trace_flags", 0), sdk_span.parent), + flags=_span_flags(span_context.trace_flags, sdk_span.parent), ) @@ -165,7 +165,7 @@ def _encode_links(links: Sequence[Link]) -> Sequence[PB2SPan.Link]: span_id=_encode_span_id(link.context.span_id), attributes=_encode_attributes(link.attributes), dropped_attributes_count=link.dropped_attributes, - flags=_span_flags(getattr(link.context, "trace_flags", 0), link.context), + flags=_span_flags(link.context.trace_flags, link.context), ) pb2_links.append(encoded_link) return pb2_links