Skip to content

Commit 3a7eb53

Browse files
authored
Make propagators conform to spec (#488)
* Make propagators conform to spec * do not modify / set an invalid span in the passed context in case a propagator did not manage to extract * in case no context is passed to propagator.extract assume the root context as default so that a new trace is started instead of continung the current active trace in case extraction fails * fix also ot-trace propagator which compared int with str trace/span ids when checking for validity in extract
1 parent 4a8b32b commit 3a7eb53

File tree

7 files changed

+173
-216
lines changed

7 files changed

+173
-216
lines changed

Diff for: CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
([#504](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/504))
1515
- `opentelemetry-instrumentation-asgi` Fix instrumentation default span name.
1616
([#418](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/418))
17+
- Propagators use the root context as default for `extract` and do not modify
18+
the context if extracting from carrier does not work.
19+
([#488](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/488))
1720

1821
### Added
1922
- `opentelemetry-instrumentation-botocore` now supports

Diff for: exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/propagator.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ def extract(
4242
context: typing.Optional[Context] = None,
4343
getter: Getter = default_getter,
4444
) -> Context:
45+
if context is None:
46+
context = Context()
47+
4548
trace_id = extract_first_element(
4649
getter.get(carrier, self.TRACE_ID_KEY)
4750
)
@@ -64,7 +67,7 @@ def extract(
6467
trace_flags = trace.TraceFlags(trace.TraceFlags.SAMPLED)
6568

6669
if trace_id is None or span_id is None:
67-
return set_span_in_context(trace.INVALID_SPAN, context)
70+
return context
6871

6972
trace_state = []
7073
if origin is not None:

Diff for: exporter/opentelemetry-exporter-datadog/tests/test_datadog_format.py

+41-24
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from unittest.mock import Mock, patch
1717

1818
from opentelemetry import trace as trace_api
19+
from opentelemetry.context import Context
1920
from opentelemetry.exporter.datadog import constants, propagator
2021
from opentelemetry.sdk import trace
2122
from opentelemetry.sdk.trace.id_generator import RandomIdGenerator
@@ -36,42 +37,58 @@ def setUpClass(cls):
3637
)
3738
cls.serialized_origin = "origin-service"
3839

39-
def test_malformed_headers(self):
40+
def test_extract_malformed_headers_to_explicit_ctx(self):
4041
"""Test with no Datadog headers"""
42+
orig_ctx = Context({"k1": "v1"})
4143
malformed_trace_id_key = FORMAT.TRACE_ID_KEY + "-x"
4244
malformed_parent_id_key = FORMAT.PARENT_ID_KEY + "-x"
43-
context = get_current_span(
44-
FORMAT.extract(
45-
{
46-
malformed_trace_id_key: self.serialized_trace_id,
47-
malformed_parent_id_key: self.serialized_parent_id,
48-
},
49-
)
50-
).get_span_context()
45+
context = FORMAT.extract(
46+
{
47+
malformed_trace_id_key: self.serialized_trace_id,
48+
malformed_parent_id_key: self.serialized_parent_id,
49+
},
50+
orig_ctx,
51+
)
52+
self.assertDictEqual(orig_ctx, context)
5153

52-
self.assertNotEqual(context.trace_id, int(self.serialized_trace_id))
53-
self.assertNotEqual(context.span_id, int(self.serialized_parent_id))
54-
self.assertFalse(context.is_remote)
54+
def test_extract_malformed_headers_to_implicit_ctx(self):
55+
malformed_trace_id_key = FORMAT.TRACE_ID_KEY + "-x"
56+
malformed_parent_id_key = FORMAT.PARENT_ID_KEY + "-x"
57+
context = FORMAT.extract(
58+
{
59+
malformed_trace_id_key: self.serialized_trace_id,
60+
malformed_parent_id_key: self.serialized_parent_id,
61+
}
62+
)
63+
self.assertDictEqual(Context(), context)
5564

56-
def test_missing_trace_id(self):
65+
def test_extract_missing_trace_id_to_explicit_ctx(self):
5766
"""If a trace id is missing, populate an invalid trace id."""
58-
carrier = {
59-
FORMAT.PARENT_ID_KEY: self.serialized_parent_id,
60-
}
67+
orig_ctx = Context({"k1": "v1"})
68+
carrier = {FORMAT.PARENT_ID_KEY: self.serialized_parent_id}
69+
70+
ctx = FORMAT.extract(carrier, orig_ctx)
71+
self.assertDictEqual(orig_ctx, ctx)
72+
73+
def test_extract_missing_trace_id_to_implicit_ctx(self):
74+
carrier = {FORMAT.PARENT_ID_KEY: self.serialized_parent_id}
6175

6276
ctx = FORMAT.extract(carrier)
63-
span_context = get_current_span(ctx).get_span_context()
64-
self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID)
77+
self.assertDictEqual(Context(), ctx)
6578

66-
def test_missing_parent_id(self):
79+
def test_extract_missing_parent_id_to_explicit_ctx(self):
6780
"""If a parent id is missing, populate an invalid trace id."""
68-
carrier = {
69-
FORMAT.TRACE_ID_KEY: self.serialized_trace_id,
70-
}
81+
orig_ctx = Context({"k1": "v1"})
82+
carrier = {FORMAT.TRACE_ID_KEY: self.serialized_trace_id}
83+
84+
ctx = FORMAT.extract(carrier, orig_ctx)
85+
self.assertDictEqual(orig_ctx, ctx)
86+
87+
def test_extract_missing_parent_id_to_implicit_ctx(self):
88+
carrier = {FORMAT.TRACE_ID_KEY: self.serialized_trace_id}
7189

7290
ctx = FORMAT.extract(carrier)
73-
span_context = get_current_span(ctx).get_span_context()
74-
self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID)
91+
self.assertDictEqual(Context(), ctx)
7592

7693
def test_context_propagation(self):
7794
"""Test the propagation of Datadog headers."""

Diff for: propagator/opentelemetry-propagator-ot-trace/src/opentelemetry/propagators/ot_trace/__init__.py

+26-12
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,19 @@ def extract(
5555
context: Optional[Context] = None,
5656
getter: Getter = default_getter,
5757
) -> Context:
58+
if context is None:
59+
context = Context()
5860

59-
traceid = _extract_first_element(
60-
getter.get(carrier, OT_TRACE_ID_HEADER), INVALID_TRACE_ID
61+
traceid = _extract_identifier(
62+
getter.get(carrier, OT_TRACE_ID_HEADER),
63+
_valid_extract_traceid,
64+
INVALID_TRACE_ID,
6165
)
6266

63-
spanid = _extract_first_element(
64-
getter.get(carrier, OT_SPAN_ID_HEADER), INVALID_SPAN_ID
67+
spanid = _extract_identifier(
68+
getter.get(carrier, OT_SPAN_ID_HEADER),
69+
_valid_extract_spanid,
70+
INVALID_SPAN_ID,
6571
)
6672

6773
sampled = _extract_first_element(
@@ -73,17 +79,12 @@ def extract(
7379
else:
7480
traceflags = TraceFlags.DEFAULT
7581

76-
if (
77-
traceid != INVALID_TRACE_ID
78-
and _valid_extract_traceid.fullmatch(traceid) is not None
79-
and spanid != INVALID_SPAN_ID
80-
and _valid_extract_spanid.fullmatch(spanid) is not None
81-
):
82+
if traceid != INVALID_TRACE_ID and spanid != INVALID_SPAN_ID:
8283
context = set_span_in_context(
8384
NonRecordingSpan(
8485
SpanContext(
85-
trace_id=int(traceid, 16),
86-
span_id=int(spanid, 16),
86+
trace_id=traceid,
87+
span_id=spanid,
8788
is_remote=True,
8889
trace_flags=TraceFlags(traceflags),
8990
)
@@ -172,3 +173,16 @@ def _extract_first_element(
172173
if items is None:
173174
return default
174175
return next(iter(items), None)
176+
177+
178+
def _extract_identifier(
179+
items: Iterable[CarrierT], validator_pattern, default: int
180+
) -> int:
181+
header = _extract_first_element(items)
182+
if header is None or validator_pattern.fullmatch(header) is None:
183+
return default
184+
185+
try:
186+
return int(header, 16)
187+
except ValueError:
188+
return default

Diff for: propagator/opentelemetry-propagator-ot-trace/tests/test_ot_trace_propagator.py

+47-67
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from unittest import TestCase
1616

1717
from opentelemetry.baggage import get_all, set_baggage
18+
from opentelemetry.context import Context
1819
from opentelemetry.propagators.ot_trace import (
1920
OT_BAGGAGE_PREFIX,
2021
OT_SAMPLED_HEADER,
@@ -24,8 +25,6 @@
2425
)
2526
from opentelemetry.sdk.trace import _Span
2627
from opentelemetry.trace import (
27-
INVALID_SPAN_CONTEXT,
28-
INVALID_SPAN_ID,
2928
INVALID_TRACE_ID,
3029
SpanContext,
3130
TraceFlags,
@@ -275,65 +274,44 @@ def test_extract_trace_id_span_id_sampled_false(self):
275274
get_current_span().get_span_context().trace_flags, TraceFlags
276275
)
277276

278-
def test_extract_malformed_trace_id(self):
279-
"""Test extraction with malformed trace_id"""
280-
281-
span_context = get_current_span(
282-
self.ot_trace_propagator.extract(
283-
{
284-
OT_TRACE_ID_HEADER: "abc123!",
285-
OT_SPAN_ID_HEADER: "e457b5a2e4d86bd1",
286-
OT_SAMPLED_HEADER: "false",
287-
},
288-
)
289-
).get_span_context()
290-
291-
self.assertEqual(span_context, INVALID_SPAN_CONTEXT)
292-
293-
def test_extract_malformed_span_id(self):
294-
"""Test extraction with malformed span_id"""
295-
296-
span_context = get_current_span(
297-
self.ot_trace_propagator.extract(
298-
{
299-
OT_TRACE_ID_HEADER: "64fe8b2a57d3eff7",
300-
OT_SPAN_ID_HEADER: "abc123!",
301-
OT_SAMPLED_HEADER: "false",
302-
},
303-
)
304-
).get_span_context()
305-
306-
self.assertEqual(span_context, INVALID_SPAN_CONTEXT)
307-
308-
def test_extract_invalid_trace_id(self):
309-
"""Test extraction with invalid trace_id"""
310-
311-
span_context = get_current_span(
312-
self.ot_trace_propagator.extract(
313-
{
314-
OT_TRACE_ID_HEADER: INVALID_TRACE_ID,
315-
OT_SPAN_ID_HEADER: "e457b5a2e4d86bd1",
316-
OT_SAMPLED_HEADER: "false",
317-
},
318-
)
319-
).get_span_context()
320-
321-
self.assertEqual(span_context, INVALID_SPAN_CONTEXT)
322-
323-
def test_extract_invalid_span_id(self):
324-
"""Test extraction with invalid span_id"""
325-
326-
span_context = get_current_span(
327-
self.ot_trace_propagator.extract(
328-
{
329-
OT_TRACE_ID_HEADER: "64fe8b2a57d3eff7",
330-
OT_SPAN_ID_HEADER: INVALID_SPAN_ID,
331-
OT_SAMPLED_HEADER: "false",
332-
},
333-
)
334-
).get_span_context()
335-
336-
self.assertEqual(span_context, INVALID_SPAN_CONTEXT)
277+
def test_extract_invalid_trace_header_to_explict_ctx(self):
278+
invalid_headers = [
279+
("abc123!", "e457b5a2e4d86bd1"), # malformed trace id
280+
("64fe8b2a57d3eff7", "abc123!"), # malformed span id
281+
("0" * 32, "e457b5a2e4d86bd1"), # invalid trace id
282+
("64fe8b2a57d3eff7", "0" * 16), # invalid span id
283+
]
284+
for trace_id, span_id in invalid_headers:
285+
with self.subTest(trace_id=trace_id, span_id=span_id):
286+
orig_ctx = Context({"k1": "v1"})
287+
288+
ctx = self.ot_trace_propagator.extract(
289+
{
290+
OT_TRACE_ID_HEADER: trace_id,
291+
OT_SPAN_ID_HEADER: span_id,
292+
OT_SAMPLED_HEADER: "false",
293+
},
294+
orig_ctx,
295+
)
296+
self.assertDictEqual(orig_ctx, ctx)
297+
298+
def test_extract_invalid_trace_header_to_implicit_ctx(self):
299+
invalid_headers = [
300+
("abc123!", "e457b5a2e4d86bd1"), # malformed trace id
301+
("64fe8b2a57d3eff7", "abc123!"), # malformed span id
302+
("0" * 32, "e457b5a2e4d86bd1"), # invalid trace id
303+
("64fe8b2a57d3eff7", "0" * 16), # invalid span id
304+
]
305+
for trace_id, span_id in invalid_headers:
306+
with self.subTest(trace_id=trace_id, span_id=span_id):
307+
ctx = self.ot_trace_propagator.extract(
308+
{
309+
OT_TRACE_ID_HEADER: trace_id,
310+
OT_SPAN_ID_HEADER: span_id,
311+
OT_SAMPLED_HEADER: "false",
312+
}
313+
)
314+
self.assertDictEqual(Context(), ctx)
337315

338316
def test_extract_baggage(self):
339317
"""Test baggage extraction"""
@@ -359,11 +337,13 @@ def test_extract_baggage(self):
359337
self.assertEqual(baggage["abc"], "abc")
360338
self.assertEqual(baggage["def"], "def")
361339

362-
def test_extract_empty(self):
363-
"Test extraction when no headers are present"
340+
def test_extract_empty_to_explicit_ctx(self):
341+
"""Test extraction when no headers are present"""
342+
orig_ctx = Context({"k1": "v1"})
343+
ctx = self.ot_trace_propagator.extract({}, orig_ctx)
364344

365-
span_context = get_current_span(
366-
self.ot_trace_propagator.extract({})
367-
).get_span_context()
345+
self.assertDictEqual(orig_ctx, ctx)
368346

369-
self.assertEqual(span_context, INVALID_SPAN_CONTEXT)
347+
def test_extract_empty_to_implicit_ctx(self):
348+
ctx = self.ot_trace_propagator.extract({})
349+
self.assertDictEqual(Context(), ctx)

Diff for: sdk-extension/opentelemetry-sdk-extension-aws/src/opentelemetry/sdk/extension/aws/trace/propagation/aws_xray_format.py

+7-12
Original file line numberDiff line numberDiff line change
@@ -106,19 +106,18 @@ def extract(
106106
context: typing.Optional[Context] = None,
107107
getter: Getter = default_getter,
108108
) -> Context:
109+
if context is None:
110+
context = Context()
111+
109112
trace_header_list = getter.get(carrier, TRACE_HEADER_KEY)
110113

111114
if not trace_header_list or len(trace_header_list) != 1:
112-
return trace.set_span_in_context(
113-
trace.INVALID_SPAN, context=context
114-
)
115+
return context
115116

116117
trace_header = trace_header_list[0]
117118

118119
if not trace_header:
119-
return trace.set_span_in_context(
120-
trace.INVALID_SPAN, context=context
121-
)
120+
return context
122121

123122
try:
124123
(
@@ -128,9 +127,7 @@ def extract(
128127
) = AwsXRayFormat._extract_span_properties(trace_header)
129128
except AwsParseTraceHeaderError as err:
130129
_logger.debug(err.message)
131-
return trace.set_span_in_context(
132-
trace.INVALID_SPAN, context=context
133-
)
130+
return context
134131

135132
options = 0
136133
if sampled:
@@ -148,9 +145,7 @@ def extract(
148145
_logger.debug(
149146
"Invalid Span Extracted. Insertting INVALID span into provided context."
150147
)
151-
return trace.set_span_in_context(
152-
trace.INVALID_SPAN, context=context
153-
)
148+
return context
154149

155150
return trace.set_span_in_context(
156151
trace.NonRecordingSpan(span_context), context=context

0 commit comments

Comments
 (0)