diff --git a/CHANGELOG.md b/CHANGELOG.md index c8e680eef7..d27c5fce0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#504](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/504)) - `opentelemetry-instrumentation-asgi` Fix instrumentation default span name. ([#418](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/418)) +- Propagators use the root context as default for `extract` and do not modify + the context if extracting from carrier does not work. + ([#488](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/488)) ### Added - `opentelemetry-instrumentation-botocore` now supports diff --git a/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/propagator.py b/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/propagator.py index e9b71ef7d9..db008c903a 100644 --- a/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/propagator.py +++ b/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/propagator.py @@ -42,6 +42,9 @@ def extract( context: typing.Optional[Context] = None, getter: Getter = default_getter, ) -> Context: + if context is None: + context = Context() + trace_id = extract_first_element( getter.get(carrier, self.TRACE_ID_KEY) ) @@ -64,7 +67,7 @@ def extract( trace_flags = trace.TraceFlags(trace.TraceFlags.SAMPLED) if trace_id is None or span_id is None: - return set_span_in_context(trace.INVALID_SPAN, context) + return context trace_state = [] if origin is not None: diff --git a/exporter/opentelemetry-exporter-datadog/tests/test_datadog_format.py b/exporter/opentelemetry-exporter-datadog/tests/test_datadog_format.py index c724849028..e7205d6508 100644 --- a/exporter/opentelemetry-exporter-datadog/tests/test_datadog_format.py +++ b/exporter/opentelemetry-exporter-datadog/tests/test_datadog_format.py @@ -16,6 +16,7 @@ from unittest.mock import Mock, patch from opentelemetry import trace as trace_api +from opentelemetry.context import Context from opentelemetry.exporter.datadog import constants, propagator from opentelemetry.sdk import trace from opentelemetry.sdk.trace.id_generator import RandomIdGenerator @@ -36,42 +37,58 @@ def setUpClass(cls): ) cls.serialized_origin = "origin-service" - def test_malformed_headers(self): + def test_extract_malformed_headers_to_explicit_ctx(self): """Test with no Datadog headers""" + orig_ctx = Context({"k1": "v1"}) malformed_trace_id_key = FORMAT.TRACE_ID_KEY + "-x" malformed_parent_id_key = FORMAT.PARENT_ID_KEY + "-x" - context = get_current_span( - FORMAT.extract( - { - malformed_trace_id_key: self.serialized_trace_id, - malformed_parent_id_key: self.serialized_parent_id, - }, - ) - ).get_span_context() + context = FORMAT.extract( + { + malformed_trace_id_key: self.serialized_trace_id, + malformed_parent_id_key: self.serialized_parent_id, + }, + orig_ctx, + ) + self.assertDictEqual(orig_ctx, context) - self.assertNotEqual(context.trace_id, int(self.serialized_trace_id)) - self.assertNotEqual(context.span_id, int(self.serialized_parent_id)) - self.assertFalse(context.is_remote) + def test_extract_malformed_headers_to_implicit_ctx(self): + malformed_trace_id_key = FORMAT.TRACE_ID_KEY + "-x" + malformed_parent_id_key = FORMAT.PARENT_ID_KEY + "-x" + context = FORMAT.extract( + { + malformed_trace_id_key: self.serialized_trace_id, + malformed_parent_id_key: self.serialized_parent_id, + } + ) + self.assertDictEqual(Context(), context) - def test_missing_trace_id(self): + def test_extract_missing_trace_id_to_explicit_ctx(self): """If a trace id is missing, populate an invalid trace id.""" - carrier = { - FORMAT.PARENT_ID_KEY: self.serialized_parent_id, - } + orig_ctx = Context({"k1": "v1"}) + carrier = {FORMAT.PARENT_ID_KEY: self.serialized_parent_id} + + ctx = FORMAT.extract(carrier, orig_ctx) + self.assertDictEqual(orig_ctx, ctx) + + def test_extract_missing_trace_id_to_implicit_ctx(self): + carrier = {FORMAT.PARENT_ID_KEY: self.serialized_parent_id} ctx = FORMAT.extract(carrier) - span_context = get_current_span(ctx).get_span_context() - self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID) + self.assertDictEqual(Context(), ctx) - def test_missing_parent_id(self): + def test_extract_missing_parent_id_to_explicit_ctx(self): """If a parent id is missing, populate an invalid trace id.""" - carrier = { - FORMAT.TRACE_ID_KEY: self.serialized_trace_id, - } + orig_ctx = Context({"k1": "v1"}) + carrier = {FORMAT.TRACE_ID_KEY: self.serialized_trace_id} + + ctx = FORMAT.extract(carrier, orig_ctx) + self.assertDictEqual(orig_ctx, ctx) + + def test_extract_missing_parent_id_to_implicit_ctx(self): + carrier = {FORMAT.TRACE_ID_KEY: self.serialized_trace_id} ctx = FORMAT.extract(carrier) - span_context = get_current_span(ctx).get_span_context() - self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID) + self.assertDictEqual(Context(), ctx) def test_context_propagation(self): """Test the propagation of Datadog headers.""" diff --git a/propagator/opentelemetry-propagator-ot-trace/src/opentelemetry/propagators/ot_trace/__init__.py b/propagator/opentelemetry-propagator-ot-trace/src/opentelemetry/propagators/ot_trace/__init__.py index 0097a1bcfb..6324cbf77e 100644 --- a/propagator/opentelemetry-propagator-ot-trace/src/opentelemetry/propagators/ot_trace/__init__.py +++ b/propagator/opentelemetry-propagator-ot-trace/src/opentelemetry/propagators/ot_trace/__init__.py @@ -55,13 +55,19 @@ def extract( context: Optional[Context] = None, getter: Getter = default_getter, ) -> Context: + if context is None: + context = Context() - traceid = _extract_first_element( - getter.get(carrier, OT_TRACE_ID_HEADER), INVALID_TRACE_ID + traceid = _extract_identifier( + getter.get(carrier, OT_TRACE_ID_HEADER), + _valid_extract_traceid, + INVALID_TRACE_ID, ) - spanid = _extract_first_element( - getter.get(carrier, OT_SPAN_ID_HEADER), INVALID_SPAN_ID + spanid = _extract_identifier( + getter.get(carrier, OT_SPAN_ID_HEADER), + _valid_extract_spanid, + INVALID_SPAN_ID, ) sampled = _extract_first_element( @@ -73,17 +79,12 @@ def extract( else: traceflags = TraceFlags.DEFAULT - if ( - traceid != INVALID_TRACE_ID - and _valid_extract_traceid.fullmatch(traceid) is not None - and spanid != INVALID_SPAN_ID - and _valid_extract_spanid.fullmatch(spanid) is not None - ): + if traceid != INVALID_TRACE_ID and spanid != INVALID_SPAN_ID: context = set_span_in_context( NonRecordingSpan( SpanContext( - trace_id=int(traceid, 16), - span_id=int(spanid, 16), + trace_id=traceid, + span_id=spanid, is_remote=True, trace_flags=TraceFlags(traceflags), ) @@ -172,3 +173,16 @@ def _extract_first_element( if items is None: return default return next(iter(items), None) + + +def _extract_identifier( + items: Iterable[CarrierT], validator_pattern, default: int +) -> int: + header = _extract_first_element(items) + if header is None or validator_pattern.fullmatch(header) is None: + return default + + try: + return int(header, 16) + except ValueError: + return default diff --git a/propagator/opentelemetry-propagator-ot-trace/tests/test_ot_trace_propagator.py b/propagator/opentelemetry-propagator-ot-trace/tests/test_ot_trace_propagator.py index 0fbcb42db9..58bd4fa0ab 100644 --- a/propagator/opentelemetry-propagator-ot-trace/tests/test_ot_trace_propagator.py +++ b/propagator/opentelemetry-propagator-ot-trace/tests/test_ot_trace_propagator.py @@ -15,6 +15,7 @@ from unittest import TestCase from opentelemetry.baggage import get_all, set_baggage +from opentelemetry.context import Context from opentelemetry.propagators.ot_trace import ( OT_BAGGAGE_PREFIX, OT_SAMPLED_HEADER, @@ -24,8 +25,6 @@ ) from opentelemetry.sdk.trace import _Span from opentelemetry.trace import ( - INVALID_SPAN_CONTEXT, - INVALID_SPAN_ID, INVALID_TRACE_ID, SpanContext, TraceFlags, @@ -275,65 +274,44 @@ def test_extract_trace_id_span_id_sampled_false(self): get_current_span().get_span_context().trace_flags, TraceFlags ) - def test_extract_malformed_trace_id(self): - """Test extraction with malformed trace_id""" - - span_context = get_current_span( - self.ot_trace_propagator.extract( - { - OT_TRACE_ID_HEADER: "abc123!", - OT_SPAN_ID_HEADER: "e457b5a2e4d86bd1", - OT_SAMPLED_HEADER: "false", - }, - ) - ).get_span_context() - - self.assertEqual(span_context, INVALID_SPAN_CONTEXT) - - def test_extract_malformed_span_id(self): - """Test extraction with malformed span_id""" - - span_context = get_current_span( - self.ot_trace_propagator.extract( - { - OT_TRACE_ID_HEADER: "64fe8b2a57d3eff7", - OT_SPAN_ID_HEADER: "abc123!", - OT_SAMPLED_HEADER: "false", - }, - ) - ).get_span_context() - - self.assertEqual(span_context, INVALID_SPAN_CONTEXT) - - def test_extract_invalid_trace_id(self): - """Test extraction with invalid trace_id""" - - span_context = get_current_span( - self.ot_trace_propagator.extract( - { - OT_TRACE_ID_HEADER: INVALID_TRACE_ID, - OT_SPAN_ID_HEADER: "e457b5a2e4d86bd1", - OT_SAMPLED_HEADER: "false", - }, - ) - ).get_span_context() - - self.assertEqual(span_context, INVALID_SPAN_CONTEXT) - - def test_extract_invalid_span_id(self): - """Test extraction with invalid span_id""" - - span_context = get_current_span( - self.ot_trace_propagator.extract( - { - OT_TRACE_ID_HEADER: "64fe8b2a57d3eff7", - OT_SPAN_ID_HEADER: INVALID_SPAN_ID, - OT_SAMPLED_HEADER: "false", - }, - ) - ).get_span_context() - - self.assertEqual(span_context, INVALID_SPAN_CONTEXT) + def test_extract_invalid_trace_header_to_explict_ctx(self): + invalid_headers = [ + ("abc123!", "e457b5a2e4d86bd1"), # malformed trace id + ("64fe8b2a57d3eff7", "abc123!"), # malformed span id + ("0" * 32, "e457b5a2e4d86bd1"), # invalid trace id + ("64fe8b2a57d3eff7", "0" * 16), # invalid span id + ] + for trace_id, span_id in invalid_headers: + with self.subTest(trace_id=trace_id, span_id=span_id): + orig_ctx = Context({"k1": "v1"}) + + ctx = self.ot_trace_propagator.extract( + { + OT_TRACE_ID_HEADER: trace_id, + OT_SPAN_ID_HEADER: span_id, + OT_SAMPLED_HEADER: "false", + }, + orig_ctx, + ) + self.assertDictEqual(orig_ctx, ctx) + + def test_extract_invalid_trace_header_to_implicit_ctx(self): + invalid_headers = [ + ("abc123!", "e457b5a2e4d86bd1"), # malformed trace id + ("64fe8b2a57d3eff7", "abc123!"), # malformed span id + ("0" * 32, "e457b5a2e4d86bd1"), # invalid trace id + ("64fe8b2a57d3eff7", "0" * 16), # invalid span id + ] + for trace_id, span_id in invalid_headers: + with self.subTest(trace_id=trace_id, span_id=span_id): + ctx = self.ot_trace_propagator.extract( + { + OT_TRACE_ID_HEADER: trace_id, + OT_SPAN_ID_HEADER: span_id, + OT_SAMPLED_HEADER: "false", + } + ) + self.assertDictEqual(Context(), ctx) def test_extract_baggage(self): """Test baggage extraction""" @@ -359,11 +337,13 @@ def test_extract_baggage(self): self.assertEqual(baggage["abc"], "abc") self.assertEqual(baggage["def"], "def") - def test_extract_empty(self): - "Test extraction when no headers are present" + def test_extract_empty_to_explicit_ctx(self): + """Test extraction when no headers are present""" + orig_ctx = Context({"k1": "v1"}) + ctx = self.ot_trace_propagator.extract({}, orig_ctx) - span_context = get_current_span( - self.ot_trace_propagator.extract({}) - ).get_span_context() + self.assertDictEqual(orig_ctx, ctx) - self.assertEqual(span_context, INVALID_SPAN_CONTEXT) + def test_extract_empty_to_implicit_ctx(self): + ctx = self.ot_trace_propagator.extract({}) + self.assertDictEqual(Context(), ctx) diff --git a/sdk-extension/opentelemetry-sdk-extension-aws/src/opentelemetry/sdk/extension/aws/trace/propagation/aws_xray_format.py b/sdk-extension/opentelemetry-sdk-extension-aws/src/opentelemetry/sdk/extension/aws/trace/propagation/aws_xray_format.py index 2ede9e9ae2..2cf481a373 100644 --- a/sdk-extension/opentelemetry-sdk-extension-aws/src/opentelemetry/sdk/extension/aws/trace/propagation/aws_xray_format.py +++ b/sdk-extension/opentelemetry-sdk-extension-aws/src/opentelemetry/sdk/extension/aws/trace/propagation/aws_xray_format.py @@ -106,19 +106,18 @@ def extract( context: typing.Optional[Context] = None, getter: Getter = default_getter, ) -> Context: + if context is None: + context = Context() + trace_header_list = getter.get(carrier, TRACE_HEADER_KEY) if not trace_header_list or len(trace_header_list) != 1: - return trace.set_span_in_context( - trace.INVALID_SPAN, context=context - ) + return context trace_header = trace_header_list[0] if not trace_header: - return trace.set_span_in_context( - trace.INVALID_SPAN, context=context - ) + return context try: ( @@ -128,9 +127,7 @@ def extract( ) = AwsXRayFormat._extract_span_properties(trace_header) except AwsParseTraceHeaderError as err: _logger.debug(err.message) - return trace.set_span_in_context( - trace.INVALID_SPAN, context=context - ) + return context options = 0 if sampled: @@ -148,9 +145,7 @@ def extract( _logger.debug( "Invalid Span Extracted. Insertting INVALID span into provided context." ) - return trace.set_span_in_context( - trace.INVALID_SPAN, context=context - ) + return context return trace.set_span_in_context( trace.NonRecordingSpan(span_context), context=context diff --git a/sdk-extension/opentelemetry-sdk-extension-aws/tests/trace/propagation/test_aws_xray_format.py b/sdk-extension/opentelemetry-sdk-extension-aws/tests/trace/propagation/test_aws_xray_format.py index cd066cc154..683dc79825 100644 --- a/sdk-extension/opentelemetry-sdk-extension-aws/tests/trace/propagation/test_aws_xray_format.py +++ b/sdk-extension/opentelemetry-sdk-extension-aws/tests/trace/propagation/test_aws_xray_format.py @@ -18,6 +18,7 @@ from requests.structures import CaseInsensitiveDict import opentelemetry.trace as trace_api +from opentelemetry.context import Context from opentelemetry.sdk.extension.aws.trace.propagation.aws_xray_format import ( TRACE_HEADER_KEY, AwsXRayFormat, @@ -164,15 +165,18 @@ def test_inject_reported_fields_matches_carrier_fields(self): # Extract Tests - def test_extract_empty_carrier_from_invalid_context(self): + def test_extract_empty_carrier_to_explicit_ctx(self): + orig_ctx = Context({"k1": "v1"}) context_with_extracted = AwsXRayPropagatorTest.XRAY_PROPAGATOR.extract( - CaseInsensitiveDict() + CaseInsensitiveDict(), orig_ctx ) + self.assertDictEqual(orig_ctx, context_with_extracted) - self.assertEqual( - get_nested_span_context(context_with_extracted), - INVALID_SPAN_CONTEXT, + def test_extract_empty_carrier_to_implicit_ctx(self): + context_with_extracted = AwsXRayPropagatorTest.XRAY_PROPAGATOR.extract( + CaseInsensitiveDict() ) + self.assertDictEqual(Context(), context_with_extracted) def test_extract_not_sampled_context(self): context_with_extracted = AwsXRayPropagatorTest.XRAY_PROPAGATOR.extract( @@ -256,103 +260,44 @@ def test_extract_invalid_xray_trace_header(self): INVALID_SPAN_CONTEXT, ) - def test_extract_invalid_trace_id(self): - context_with_extracted = AwsXRayPropagatorTest.XRAY_PROPAGATOR.extract( - CaseInsensitiveDict( - { - TRACE_HEADER_KEY: "Root=1-12345678-abcdefghijklmnopqrstuvwx;Parent=53995c3f42cd8ad8;Sampled=0" - } - ), - ) - - self.assertEqual( - get_nested_span_context(context_with_extracted), - INVALID_SPAN_CONTEXT, - ) - - def test_extract_invalid_trace_id_size(self): - context_with_extracted = AwsXRayPropagatorTest.XRAY_PROPAGATOR.extract( - CaseInsensitiveDict( - { - TRACE_HEADER_KEY: "Root=1-8a3c60f7-d188f8fa79d48a391a778fa600;Parent=53995c3f42cd8ad8;Sampled=0=" - } - ), - ) - - self.assertEqual( - get_nested_span_context(context_with_extracted), - INVALID_SPAN_CONTEXT, - ) - - def test_extract_invalid_span_id(self): - context_with_extracted = AwsXRayPropagatorTest.XRAY_PROPAGATOR.extract( - CaseInsensitiveDict( - { - TRACE_HEADER_KEY: "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=abcdefghijklmnop;Sampled=0" - } - ), - ) - - self.assertEqual( - get_nested_span_context(context_with_extracted), - INVALID_SPAN_CONTEXT, - ) - - def test_extract_invalid_span_id_size(self): - context_with_extracted = AwsXRayPropagatorTest.XRAY_PROPAGATOR.extract( - CaseInsensitiveDict( - { - TRACE_HEADER_KEY: "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad800;Sampled=0" - } - ), - ) - - self.assertEqual( - get_nested_span_context(context_with_extracted), - INVALID_SPAN_CONTEXT, - ) - - def test_extract_invalid_empty_sampled_flag(self): - context_with_extracted = AwsXRayPropagatorTest.XRAY_PROPAGATOR.extract( - CaseInsensitiveDict( - { - TRACE_HEADER_KEY: "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=" - } - ), - ) - - self.assertEqual( - get_nested_span_context(context_with_extracted), - INVALID_SPAN_CONTEXT, - ) - - def test_extract_invalid_sampled_flag_size(self): - context_with_extracted = AwsXRayPropagatorTest.XRAY_PROPAGATOR.extract( - CaseInsensitiveDict( - { - TRACE_HEADER_KEY: "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=011" - } - ), - ) - - self.assertEqual( - get_nested_span_context(context_with_extracted), - INVALID_SPAN_CONTEXT, - ) + def test_extract_invalid_to_explicit_ctx(self): + trace_headers = [ + "Root=1-12345678-abcdefghijklmnopqrstuvwx;Parent=53995c3f42cd8ad8;Sampled=0", # invalid trace id + "Root=1-8a3c60f7-d188f8fa79d48a391a778fa600;Parent=53995c3f42cd8ad8;Sampled=0", # invalid size trace id + "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=abcdefghijklmnop;Sampled=0", # invalid span id + "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad800;Sampled=0" # invalid size span id + "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=", # no sampled flag + "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=011", # invalid size sampled + "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=a", # non numeric sampled flag + ] + for trace_header in trace_headers: + with self.subTest(trace_header=trace_header): + orig_ctx = Context({"k1": "v1"}) + + ctx = AwsXRayPropagatorTest.XRAY_PROPAGATOR.extract( + CaseInsensitiveDict({TRACE_HEADER_KEY: trace_header}), + orig_ctx, + ) - def test_extract_invalid_non_numeric_sampled_flag(self): - context_with_extracted = AwsXRayPropagatorTest.XRAY_PROPAGATOR.extract( - CaseInsensitiveDict( - { - TRACE_HEADER_KEY: "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=a" - } - ), - ) + self.assertDictEqual(orig_ctx, ctx) + + def test_extract_invalid_to_implicit_ctx(self): + trace_headers = [ + "Root=1-12345678-abcdefghijklmnopqrstuvwx;Parent=53995c3f42cd8ad8;Sampled=0", # invalid trace id + "Root=1-8a3c60f7-d188f8fa79d48a391a778fa600;Parent=53995c3f42cd8ad8;Sampled=0", # invalid size trace id + "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=abcdefghijklmnop;Sampled=0", # invalid span id + "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad800;Sampled=0" # invalid size span id + "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=", # no sampled flag + "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=011", # invalid size sampled + "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=a", # non numeric sampled flag + ] + for trace_header in trace_headers: + with self.subTest(trace_header=trace_header): + ctx = AwsXRayPropagatorTest.XRAY_PROPAGATOR.extract( + CaseInsensitiveDict({TRACE_HEADER_KEY: trace_header}), + ) - self.assertEqual( - get_nested_span_context(context_with_extracted), - INVALID_SPAN_CONTEXT, - ) + self.assertDictEqual(Context(), ctx) @patch( "opentelemetry.sdk.extension.aws.trace."