diff --git a/CHANGELOG.md b/CHANGELOG.md index a83e9bfa2f2..5081cc94c86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- 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)) - `opentelemetry-exporter-prometheus`: Fix duplicate HELP/TYPE declarations for metrics with different label sets ([#4868](https://github.com/open-telemetry/opentelemetry-python/issues/4868)) - Allow loading all resource detectors by setting `OTEL_EXPERIMENTAL_RESOURCE_DETECTORS` to `*` @@ -39,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#4734](https://github.com/open-telemetry/opentelemetry-python/pull/4734)) - build: bump ruff to 0.14.1 ([#4782](https://github.com/open-telemetry/opentelemetry-python/pull/4782)) + - Add `opentelemetry-exporter-credential-provider-gcp` as an optional dependency to `opentelemetry-exporter-otlp-proto-grpc` and `opentelemetry-exporter-otlp-proto-http` ([#4760](https://github.com/open-telemetry/opentelemetry-python/pull/4760)) 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..8a9dd424b54 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,8 +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 +def _span_flags( + child_trace_flags: int, parent_span_context: Optional[SpanContext] +) -> int: + # Lower 8 bits: W3C TraceFlags + flags = child_trace_flags & PB2SpanFlags.SPAN_FLAGS_TRACE_FLAGS_MASK + # Always indicate whether parent remote information is known + flags |= PB2SpanFlags.SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK + # Set remote bit when applicable if parent_span_context and parent_span_context.is_remote: flags |= PB2SpanFlags.SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK return flags @@ -130,7 +136,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(span_context.trace_flags, sdk_span.parent), ) @@ -156,12 +162,14 @@ def _encode_links(links: Sequence[Link]) -> Sequence[PB2SPan.Link]: if links: pb2_links = [] for link in links: + # For links, we encode the link's own context (not treating it as parent-child) + # The link context's is_remote indicates if the linked span is from a remote process encoded_link = PB2SPan.Link( trace_id=_encode_trace_id(link.context.trace_id), 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(link.context.trace_flags, 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..d067a31ff0a 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 @@ -23,6 +23,8 @@ ) from opentelemetry.exporter.otlp.proto.common._internal.trace_encoder import ( _SPAN_KIND_MAP, + _encode_links, + _encode_span, _encode_status, ) from opentelemetry.exporter.otlp.proto.common.trace_encoder import encode_spans @@ -42,6 +44,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 SpanFlags as PB2SpanFlags from opentelemetry.proto.trace.v1.trace_pb2 import Status as PB2Status from opentelemetry.sdk.trace import Event as SDKEvent from opentelemetry.sdk.trace import Resource as SDKResource @@ -56,6 +59,13 @@ from opentelemetry.trace.status import Status as SDKStatus from opentelemetry.trace.status import StatusCode as SDKStatusCode +# Mask for all currently-defined span flag bits (0-9): lower 8 trace flags + has/is remote bits +ALL_SPAN_FLAGS_MASK = ( # pylint: disable=no-member + PB2SpanFlags.SPAN_FLAGS_TRACE_FLAGS_MASK + | PB2SpanFlags.SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK + | PB2SpanFlags.SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK +) + class TestOTLPTraceEncoder(unittest.TestCase): def test_encode_spans(self): @@ -298,7 +308,7 @@ def get_exhaustive_test_spans( code=SDKStatusCode.ERROR.value, message="Example description", ), - flags=0x300, + flags=0x301, ) ], ), @@ -501,3 +511,78 @@ def test_encode_status_code_translations(self): code=SDKStatusCode.ERROR.value, ), ) + + +class TestSpanFlagsEncoding(unittest.TestCase): # pylint: disable=no-member + def test_span_flags_root_unsampled(self): + span_context = SDKSpanContext( + 0x1, 0x2, is_remote=False, trace_flags=0x00 + ) + span = SDKSpan(name="root", context=span_context, parent=None) + 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 & ~ALL_SPAN_FLAGS_MASK) == 0 + + def test_span_flags_root_sampled(self): + span_context = SDKSpanContext( + 0x1, 0x2, is_remote=False, trace_flags=0x01 + ) + span = SDKSpan(name="root", context=span_context, parent=None) + 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 & ~ALL_SPAN_FLAGS_MASK) == 0 + + def test_span_flags_remote_parent_sampled(self): + parent = SDKSpanContext(0x1, 0x9, is_remote=True) + span_context = SDKSpanContext( + 0x1, 0x2, is_remote=False, trace_flags=0x01 + ) + span = SDKSpan(name="child", context=span_context, parent=parent) + 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 & ~ALL_SPAN_FLAGS_MASK) == 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 & ~ALL_SPAN_FLAGS_MASK) == 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 & ~ALL_SPAN_FLAGS_MASK) == 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..63025a91b90 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 @@ -101,6 +101,7 @@ def setUp(self): "trace_state": {"a": "b", "c": "d"}, "span_id": 10217189687419569865, "trace_id": 67545097771067222548457157018666467027, + "trace_flags": 0x00, } ), resource=SDKResource({"a": 1, "b": False}), @@ -110,8 +111,13 @@ def setUp(self): links=[ Mock( **{ - "context.trace_id": 1, - "context.span_id": 2, + "context": Mock( + **{ + "trace_id": 1, + "span_id": 2, + "trace_flags": 0x00, + } + ), "attributes": BoundedAttributes( attributes={"a": 1, "b": False} ), @@ -132,6 +138,7 @@ def setUp(self): "trace_state": {"a": "b", "c": "d"}, "span_id": 10217189687419569865, "trace_id": 67545097771067222548457157018666467027, + "trace_flags": 0x00, } ), resource=SDKResource({"a": 2, "b": False}), @@ -148,6 +155,7 @@ def setUp(self): "trace_state": {"a": "b", "c": "d"}, "span_id": 10217189687419569865, "trace_id": 67545097771067222548457157018666467027, + "trace_flags": 0x00, } ), resource=SDKResource({"a": 1, "b": False}), @@ -466,7 +474,7 @@ def test_translate_spans(self): ), ), ], - flags=0x300, + flags=0x300, # updated below in more focused tests ) ], flags=0x300, @@ -788,6 +796,7 @@ def _create_span_with_status(status: SDKStatus): "trace_state": {"a": "b", "c": "d"}, "span_id": 10217189687419569865, "trace_id": 67545097771067222548457157018666467027, + "trace_flags": 0x00, } ), parent=Mock(**{"span_id": 12345}), diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_span_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_span_exporter.py index 5f61344bbf1..22031722441 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_span_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_span_exporter.py @@ -66,6 +66,7 @@ "trace_state": {"a": "b", "c": "d"}, "span_id": 10217189687419569865, "trace_id": 67545097771067222548457157018666467027, + "trace_flags": 0x00, } ), )