Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a1d43e8
chore: added full trace flags implementation and tests
nikhilmantri0902 Oct 2, 2025
acc6f2e
chore: added changelog entry
nikhilmantri0902 Oct 2, 2025
27a12dc
chore: removed getattr method and unnecessay int typecasts
nikhilmantri0902 Oct 3, 2025
f6e8ef1
Merge branch 'main' into feat/otlpspanexporter_traceflags_bits07
nikhilmantri0902 Oct 3, 2025
858e16b
chore: minor updates as per review
nikhilmantri0902 Oct 11, 2025
92a8960
Merge branch 'main' into feat/otlpspanexporter_traceflags_bits07
xrmx Oct 14, 2025
142ba3a
fix: tox lint errors
nikhilmantri0902 Oct 15, 2025
37312bf
fix: pylint error
nikhilmantri0902 Oct 15, 2025
55e7a3a
fix: added fixes for the failing test cases
nikhilmantri0902 Oct 19, 2025
ec02a22
fix:merged main
nikhilmantri0902 Oct 19, 2025
fb5aec8
fix: precommit formatting
nikhilmantri0902 Oct 22, 2025
0696540
Merge branch 'main' into feat/otlpspanexporter_traceflags_bits07
xrmx Oct 30, 2025
3be6a7b
Update CHANGELOG.md
xrmx Oct 30, 2025
9e581fb
Merge branch 'main' into feat/otlpspanexporter_traceflags_bits07
xrmx Oct 30, 2025
04be919
Merge branch 'main' into feat/otlpspanexporter_traceflags_bits07
xrmx Oct 31, 2025
c7a1222
fix: PR comments
nikhilmantri0902 Nov 2, 2025
01afe45
fix: failing workflow
nikhilmantri0902 Nov 3, 2025
f5eb8de
Merge branch 'main' into feat/otlpspanexporter_traceflags_bits07
xrmx Nov 5, 2025
37cc7bb
chore: merged main
nikhilmantri0902 Jan 21, 2026
630d80a
chore: Moved the # pylint: disable=no-member comment to the class level
nikhilmantri0902 Jan 22, 2026
c17bf89
chore: Remove test-specific handling from _span_flags and add trace_f…
nikhilmantri0902 Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ 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))
- 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))
- semantic-conventions: Bump to 1.38.0
([#4791](https://github.com/open-telemetry/opentelemetry-python/pull/4791))

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
# TraceFlags is an int subclass, but we handle Mock objects in tests
try:
flags = child_trace_flags & PB2SpanFlags.SPAN_FLAGS_TRACE_FLAGS_MASK
except TypeError:
# If bitwise operation fails (e.g., Mock object in tests), 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,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
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 # pylint: disable=no-member
assert (
pb.flags & PB2SpanFlags.SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK # pylint: disable=no-member
) != 0
assert (pb.flags & PB2SpanFlags.SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) == 0 # pylint: disable=no-member
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 # pylint: disable=no-member
assert (
pb.flags & PB2SpanFlags.SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK # pylint: disable=no-member
) != 0
assert (pb.flags & PB2SpanFlags.SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) == 0 # pylint: disable=no-member
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 # pylint: disable=no-member
assert (
pb.flags & PB2SpanFlags.SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK # pylint: disable=no-member
) != 0
assert (pb.flags & PB2SpanFlags.SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) != 0 # pylint: disable=no-member
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 # pylint: disable=no-member
) == 0x01
assert (
pb_links[0].flags
& PB2SpanFlags.SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK # pylint: disable=no-member
) != 0
assert (
pb_links[0].flags & PB2SpanFlags.SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK # pylint: disable=no-member
) == 0
assert (pb_links[0].flags & ~ALL_SPAN_FLAGS_MASK) == 0
assert (
pb_links[1].flags & PB2SpanFlags.SPAN_FLAGS_TRACE_FLAGS_MASK # pylint: disable=no-member
) == 0x01
assert (
pb_links[1].flags
& PB2SpanFlags.SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK # pylint: disable=no-member
) != 0
assert (
pb_links[1].flags & PB2SpanFlags.SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK # pylint: disable=no-member
) != 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