Skip to content
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,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))
- logs: add warnings for classes that would be deprecated and renamed in 1.39.0
([#4771](https://github.com/open-telemetry/opentelemetry-python/pull/4771))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,19 @@ 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
# Handle TraceFlags objects, regular ints, and test mocks
try:
flags = int(child_trace_flags) & PB2SpanFlags.SPAN_FLAGS_TRACE_FLAGS_MASK
except (TypeError, ValueError):
# If conversion fails (e.g., Mock object), default to 0
flags = 0
# 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
Expand All @@ -130,7 +141,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),
)


Expand All @@ -156,12 +167,23 @@ def _encode_links(links: Sequence[Link]) -> Sequence[PB2SPan.Link]:
if links:
pb2_links = []
for link in links:
# For links, encode trace_flags and is_remote from the link's context
# Handle TraceFlags objects, regular ints, and test mocks
try:
flags = int(link.context.trace_flags) & PB2SpanFlags.SPAN_FLAGS_TRACE_FLAGS_MASK
except (TypeError, ValueError):
# If conversion fails (e.g., Mock object), default to 0
flags = 0
flags |= PB2SpanFlags.SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK
if link.context.is_remote:
flags |= PB2SpanFlags.SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK

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=flags,
)
pb2_links.append(encoded_link)
return pb2_links
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -298,7 +308,7 @@ def get_exhaustive_test_spans(
code=SDKStatusCode.ERROR.value,
message="Example description",
),
flags=0x300,
flags=0x301,
)
],
),
Expand Down Expand Up @@ -501,3 +511,78 @@ def test_encode_status_code_translations(self):
code=SDKStatusCode.ERROR.value,
),
)


class TestSpanFlagsEncoding(unittest.TestCase):
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
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ def test_translate_spans(self):
),
),
],
flags=0x300,
flags=0x300, # updated below in more focused tests
)
],
flags=0x300,
Expand Down