Skip to content

Commit b02ca44

Browse files
authored
Merge branch 'main' into patch-1
2 parents 3bb6695 + f879913 commit b02ca44

7 files changed

Lines changed: 70 additions & 29 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6464
([#5055](https://github.com/open-telemetry/opentelemetry-python/pull/5055))
6565
- Add ability to selectively enable exporting of SDK internal metrics with the `OTEL_PYTHON_SDK_INTERNAL_METRICS_ENABLED` environment variable.
6666
([#5151](https://github.com/open-telemetry/opentelemetry-python/pull/5151))
67+
- `opentelemetry-api`, `opentelemetry-sdk`: add support for 'random-trace-id' flags in W3C traceparent header trace flags. Implementations of `IdGenerator` that do randomly generate the 56 least significant bits, should also implement a `is_trace_id_random` methods that returns `True`.
68+
([#4854](https://github.com/open-telemetry/opentelemetry-python/pull/4854))
6769

6870
## Version 1.41.0/0.62b0 (2026-04-09)
6971

opentelemetry-api/src/opentelemetry/trace/span.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,17 +200,21 @@ def __exit__(
200200
class TraceFlags(int):
201201
"""A bitmask that represents options specific to the trace.
202202
203-
The only supported option is the "sampled" flag (``0x01``). If set, this
204-
flag indicates that the trace may have been sampled upstream.
203+
Supported flags:
204+
- "sampled" (``0x01``): Indicates the trace may have been sampled upstream.
205+
- "random-trace-id" (``0x02``): Indicates the trace ID was generated
206+
randomly, with at least the 7 rightmost bytes (56 bits) selected with
207+
uniform distribution.
205208
206209
See the `W3C Trace Context - Traceparent`_ spec for details.
207210
208211
.. _W3C Trace Context - Traceparent:
209-
https://www.w3.org/TR/trace-context/#trace-flags
212+
https://www.w3.org/TR/trace-context-2/#trace-flags
210213
"""
211214

212215
DEFAULT = 0x00
213216
SAMPLED = 0x01
217+
RANDOM_TRACE_ID = 0x02
214218

215219
@classmethod
216220
def get_default(cls) -> "TraceFlags":
@@ -220,6 +224,10 @@ def get_default(cls) -> "TraceFlags":
220224
def sampled(self) -> bool:
221225
return bool(self & TraceFlags.SAMPLED)
222226

227+
@property
228+
def random_trace_id(self) -> bool:
229+
return bool(self & TraceFlags.RANDOM_TRACE_ID)
230+
223231

224232
DEFAULT_TRACE_OPTIONS = TraceFlags.get_default()
225233

opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,12 @@ def start_span( # pylint: disable=too-many-locals
12241224
if sampling_result.decision.is_sampled()
12251225
else trace_api.TraceFlags(trace_api.TraceFlags.DEFAULT)
12261226
)
1227+
1228+
if self.id_generator.is_trace_id_random():
1229+
trace_flags = trace_api.TraceFlags(
1230+
trace_flags | trace_api.TraceFlags.RANDOM_TRACE_ID
1231+
)
1232+
12271233
span_context = trace_api.SpanContext(
12281234
trace_id,
12291235
self.id_generator.generate_span_id(),

opentelemetry-sdk/src/opentelemetry/sdk/trace/id_generator.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,36 @@ def generate_span_id(self) -> int:
2020
def generate_trace_id(self) -> int:
2121
"""Get a new trace ID.
2222
23-
Implementations should at least make the 64 least significant bits
23+
Implementations should at least make the 56 least significant bits
2424
uniformly random. Samplers like the `TraceIdRatioBased` sampler rely on
2525
this randomness to make sampling decisions.
2626
27+
If the implementation does randomly generate the 56 least significant bits,
28+
it should also implement `is_trace_id_random` to return True.
29+
2730
See `the specification on TraceIdRatioBased <https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#traceidratiobased>`_.
2831
2932
Returns:
3033
A 128-bit int for use as a trace ID
3134
"""
3235

36+
# pylint: disable=no-self-use
37+
def is_trace_id_random(self) -> bool:
38+
"""Indicates whether generated trace IDs are random.
39+
40+
When True, the `trace-id` field will have the `random-trace-id` flag set
41+
in the W3C traceparent header. Per the W3C Trace Context specification,
42+
this indicates that at least the 7 rightmost bytes (56 bits) of the
43+
trace ID were generated randomly with uniform distribution.
44+
45+
See `the W3C Trace Context specification <https://www.w3.org/TR/trace-context-2/#considerations-for-trace-id-field-generation>`_.
46+
47+
Returns:
48+
True if this generator produces random IDs, False otherwise.
49+
"""
50+
# By default, return False for backwards compatibility.
51+
return False
52+
3353

3454
class RandomIdGenerator(IdGenerator):
3555
"""The default ID generator for TracerProvider which randomly generates all
@@ -47,3 +67,6 @@ def generate_trace_id(self) -> int:
4767
while trace_id == trace.INVALID_TRACE_ID:
4868
trace_id = random.getrandbits(128)
4969
return trace_id
70+
71+
def is_trace_id_random(self) -> bool:
72+
return True

opentelemetry-sdk/tests/test_configurator.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,9 @@ def generate_span_id(self):
327327
def generate_trace_id(self):
328328
pass
329329

330+
def is_trace_id_random(self):
331+
return False
332+
330333

331334
class TestTraceInit(TestCase):
332335
def setUp(self):

opentelemetry-sdk/tests/trace/test_trace.py

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -266,15 +266,8 @@ def test_default_sampler(self):
266266
self.assertIsInstance(root_span, trace.Span)
267267
child_span = tracer.start_span(name="child span", context=ctx)
268268
self.assertIsInstance(child_span, trace.Span)
269-
self.assertTrue(root_span.context.trace_flags.sampled)
270-
self.assertEqual(
271-
root_span.get_span_context().trace_flags,
272-
trace_api.TraceFlags.SAMPLED,
273-
)
274-
self.assertEqual(
275-
child_span.get_span_context().trace_flags,
276-
trace_api.TraceFlags.SAMPLED,
277-
)
269+
self.assertTrue(root_span.get_span_context().trace_flags.sampled)
270+
self.assertTrue(child_span.get_span_context().trace_flags.sampled)
278271

279272
def test_default_sampler_type(self):
280273
tracer_provider = trace.TracerProvider()
@@ -292,14 +285,8 @@ def test_sampler_no_sampling(self, _get_from_env_or_default):
292285
self.assertIsInstance(root_span, trace_api.NonRecordingSpan)
293286
child_span = tracer.start_span(name="child span", context=ctx)
294287
self.assertIsInstance(child_span, trace_api.NonRecordingSpan)
295-
self.assertEqual(
296-
root_span.get_span_context().trace_flags,
297-
trace_api.TraceFlags.DEFAULT,
298-
)
299-
self.assertEqual(
300-
child_span.get_span_context().trace_flags,
301-
trace_api.TraceFlags.DEFAULT,
302-
)
288+
self.assertFalse(root_span.get_span_context().trace_flags.sampled)
289+
self.assertFalse(child_span.get_span_context().trace_flags.sampled)
303290
self.assertFalse(_get_from_env_or_default.called)
304291

305292
@mock.patch.dict("os.environ", {OTEL_TRACES_SAMPLER: "always_off"})
@@ -519,9 +506,8 @@ def test_start_span_explicit(self):
519506
other_parent.get_span_context().trace_state,
520507
child_context.trace_state,
521508
)
522-
self.assertEqual(
523-
other_parent.get_span_context().trace_flags,
524-
child_context.trace_flags,
509+
self.assertTrue(
510+
other_parent.get_span_context().trace_flags.sampled
525511
)
526512

527513
# Verify start_span() did not set the current span.
@@ -945,10 +931,7 @@ def test_sampling_attributes(self):
945931
self.assertEqual(len(root.attributes), 2)
946932
self.assertEqual(root.attributes["sampler-attr"], "sample-val")
947933
self.assertEqual(root.attributes["attr-in-both"], "decision-attr")
948-
self.assertEqual(
949-
root.get_span_context().trace_flags,
950-
trace_api.TraceFlags.SAMPLED,
951-
)
934+
self.assertTrue(root.get_span_context().trace_flags.sampled)
952935

953936
def test_events(self):
954937
self.assertEqual(trace_api.get_current_span(), trace_api.INVALID_SPAN)
@@ -2185,6 +2168,9 @@ def test_constant_default(self):
21852168
def test_constant_sampled(self):
21862169
self.assertEqual(trace_api.TraceFlags.SAMPLED, 1)
21872170

2171+
def test_constant_random_trace_id(self):
2172+
self.assertEqual(trace_api.TraceFlags.RANDOM_TRACE_ID, 2)
2173+
21882174
def test_get_default(self):
21892175
self.assertEqual(
21902176
trace_api.TraceFlags.get_default(), trace_api.TraceFlags.DEFAULT
@@ -2196,6 +2182,14 @@ def test_sampled_true(self):
21962182
def test_sampled_false(self):
21972183
self.assertFalse(trace_api.TraceFlags(0xF0).sampled)
21982184

2185+
def test_random_trace_id_true(self):
2186+
self.assertTrue(trace_api.TraceFlags(0xF2).random_trace_id)
2187+
self.assertTrue(trace_api.TraceFlags(0xF3).random_trace_id)
2188+
2189+
def test_random_trace_id_false(self):
2190+
self.assertFalse(trace_api.TraceFlags(0xF0).random_trace_id)
2191+
self.assertFalse(trace_api.TraceFlags(0xF1).random_trace_id)
2192+
21992193
def test_constant_default_trace_options(self):
22002194
self.assertEqual(
22012195
trace_api.DEFAULT_TRACE_OPTIONS, trace_api.TraceFlags.DEFAULT
@@ -2485,3 +2479,7 @@ def test_generate_trace_id_avoids_invalid(self, mock_getrandbits):
24852479
self.assertNotEqual(trace_id, trace_api.INVALID_TRACE_ID)
24862480
mock_getrandbits.assert_any_call(128)
24872481
self.assertEqual(mock_getrandbits.call_count, 2)
2482+
2483+
def test_is_trace_id_random_returns_true(self):
2484+
generator = RandomIdGenerator()
2485+
self.assertTrue(generator.is_trace_id_random())

tests/opentelemetry-test-utils/src/opentelemetry/test/wsgitestutil.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ def assertTraceResponseHeaderMatchesSpan(self, headers, span): # pylint: disabl
3737

3838
trace_id = trace.format_trace_id(span.get_span_context().trace_id)
3939
span_id = trace.format_span_id(span.get_span_context().span_id)
40+
trace_flags = span.get_span_context().trace_flags
4041
self.assertEqual(
41-
f"00-{trace_id}-{span_id}-01",
42+
f"00-{trace_id}-{span_id}-{trace_flags:02x}",
4243
headers["traceresponse"],
4344
)

0 commit comments

Comments
 (0)