From 0f3b8c191f1b3608637c21459598eed890ae6ac0 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 18 Feb 2020 14:18:05 -0800 Subject: [PATCH 01/15] Implementing Propagators API to use Context Signed-off-by: Alex Boten --- .../src/opentelemetry/ext/flask/__init__.py | 6 +- .../ext/http_requests/__init__.py | 4 +- .../tests/test_requests_integration.py | 1 + .../ext/opentracing_shim/__init__.py | 14 +- .../tests/test_shim.py | 39 ++++- .../src/opentelemetry/ext/wsgi/__init__.py | 4 +- .../context/propagation/__init__.py | 5 - .../context/propagation/binaryformat.py | 60 ------- .../propagation/tracecontexthttptextformat.py | 42 +++-- .../propagation/__init__.py | 18 --- .../propagation/binaryformat.py | 62 ------- .../propagation/httptextformat.py | 114 ------------- .../src/opentelemetry/propagators/__init__.py | 46 +++++- .../trace/propagation/__init__.py | 19 ++- .../propagation/httptextformat.py | 16 +- .../test_tracecontexthttptextformat.py | 152 ++++++++++-------- .../sdk/context/propagation/b3_format.py | 47 ++++-- .../context/propagation/test_b3_format.py | 21 ++- 18 files changed, 278 insertions(+), 392 deletions(-) delete mode 100644 opentelemetry-api/src/opentelemetry/context/propagation/binaryformat.py delete mode 100644 opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py delete mode 100644 opentelemetry-api/src/opentelemetry/distributedcontext/propagation/binaryformat.py delete mode 100644 opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py rename opentelemetry-api/src/opentelemetry/{context => trace}/propagation/httptextformat.py (91%) diff --git a/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py b/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py index aa9217c00ea..88d42068546 100644 --- a/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py +++ b/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py @@ -8,6 +8,7 @@ import opentelemetry.ext.wsgi as otel_wsgi from opentelemetry import propagators, trace from opentelemetry.ext.flask.version import __version__ +from opentelemetry.trace.propagation import get_span_from_context from opentelemetry.util import time_ns logger = logging.getLogger(__name__) @@ -57,9 +58,8 @@ def _before_flask_request(): span_name = flask_request.endpoint or otel_wsgi.get_default_span_name( environ ) - parent_span = propagators.extract( - otel_wsgi.get_header_from_environ, environ - ) + context = propagators.extract(otel_wsgi.get_header_from_environ, environ) + parent_span = get_span_from_context(context).get_context() tracer = trace.get_tracer(__name__, __version__) diff --git a/ext/opentelemetry-ext-http-requests/src/opentelemetry/ext/http_requests/__init__.py b/ext/opentelemetry-ext-http-requests/src/opentelemetry/ext/http_requests/__init__.py index a557e6fc453..f6e18a1a0d6 100644 --- a/ext/opentelemetry-ext-http-requests/src/opentelemetry/ext/http_requests/__init__.py +++ b/ext/opentelemetry-ext-http-requests/src/opentelemetry/ext/http_requests/__init__.py @@ -25,6 +25,7 @@ from opentelemetry import context, propagators from opentelemetry.ext.http_requests.version import __version__ from opentelemetry.trace import SpanKind +from opentelemetry.trace.propagation import set_span_in_context # NOTE: Currently we force passing a tracer. But in turn, this forces the user @@ -76,7 +77,8 @@ def instrumented_request(self, method, url, *args, **kwargs): # to access propagators. headers = kwargs.setdefault("headers", {}) - propagators.inject(tracer, type(headers).__setitem__, headers) + ctx = set_span_in_context(span) + propagators.inject(type(headers).__setitem__, headers, ctx) result = wrapped(self, method, url, *args, **kwargs) # *** PROCEED span.set_attribute("http.status_code", result.status_code) diff --git a/ext/opentelemetry-ext-http-requests/tests/test_requests_integration.py b/ext/opentelemetry-ext-http-requests/tests/test_requests_integration.py index de659f20e18..08251f85c65 100644 --- a/ext/opentelemetry-ext-http-requests/tests/test_requests_integration.py +++ b/ext/opentelemetry-ext-http-requests/tests/test_requests_integration.py @@ -41,6 +41,7 @@ def setUp(self): self.get_tracer = self.get_tracer_patcher.start() self.span_context_manager = mock.MagicMock() self.span = mock.create_autospec(trace.Span, spec_set=True) + self.span.get_context.return_value = trace.INVALID_SPAN_CONTEXT self.span_context_manager.__enter__.return_value = self.span def setspanattr(key, value): diff --git a/ext/opentelemetry-ext-opentracing-shim/src/opentelemetry/ext/opentracing_shim/__init__.py b/ext/opentelemetry-ext-opentracing-shim/src/opentelemetry/ext/opentracing_shim/__init__.py index 11ef52ec796..9cbd7200e65 100644 --- a/ext/opentelemetry-ext-opentracing-shim/src/opentelemetry/ext/opentracing_shim/__init__.py +++ b/ext/opentelemetry-ext-opentracing-shim/src/opentelemetry/ext/opentracing_shim/__init__.py @@ -93,6 +93,10 @@ from opentelemetry.ext.opentracing_shim import util from opentelemetry.ext.opentracing_shim.version import __version__ from opentelemetry.trace import DefaultSpan +from opentelemetry.trace.propagation import ( + get_span_from_context, + set_span_in_context, +) logger = logging.getLogger(__name__) @@ -677,11 +681,8 @@ def inject(self, span_context, format, carrier): propagator = propagators.get_global_httptextformat() - propagator.inject( - DefaultSpan(span_context.unwrap()), - type(carrier).__setitem__, - carrier, - ) + ctx = set_span_in_context(DefaultSpan(span_context.unwrap())) + propagator.inject(type(carrier).__setitem__, carrier, context=ctx) def extract(self, format, carrier): """Implements the ``extract`` method from the base class.""" @@ -700,6 +701,7 @@ def get_as_list(dict_object, key): return [value] if value is not None else [] propagator = propagators.get_global_httptextformat() - otel_context = propagator.extract(get_as_list, carrier) + ctx = propagator.extract(get_as_list, carrier) + otel_context = get_span_from_context(ctx).get_context() return SpanContextShim(otel_context) diff --git a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py index eacfc639b37..d7c4d5cda4c 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -16,15 +16,26 @@ # pylint:disable=no-member import time +import typing from unittest import TestCase import opentracing import opentelemetry.ext.opentracing_shim as opentracingshim from opentelemetry import propagators, trace -from opentelemetry.context.propagation.httptextformat import HTTPTextFormat +from opentelemetry.context import Context from opentelemetry.ext.opentracing_shim import util from opentelemetry.sdk.trace import TracerSource +from opentelemetry.trace.propagation import ( + get_span_from_context, + set_span_in_context, +) +from opentelemetry.trace.propagation.httptextformat import ( + _T, + Getter, + HTTPTextFormat, + Setter, +) class TestShim(TestCase): @@ -542,19 +553,35 @@ class MockHTTPTextFormat(HTTPTextFormat): SPAN_ID_KEY = "mock-spanid" @classmethod - def extract(cls, get_from_carrier, carrier): + def extract( + cls, + get_from_carrier: Getter[_T], + carrier: _T, + context: typing.Optional[Context] = None, + ) -> Context: trace_id_list = get_from_carrier(carrier, cls.TRACE_ID_KEY) span_id_list = get_from_carrier(carrier, cls.SPAN_ID_KEY) if not trace_id_list or not span_id_list: - return trace.INVALID_SPAN_CONTEXT + return set_span_in_context(trace.INVALID_SPAN) - return trace.SpanContext( - trace_id=int(trace_id_list[0]), span_id=int(span_id_list[0]) + return set_span_in_context( + trace.DefaultSpan( + trace.SpanContext( + trace_id=int(trace_id_list[0]), + span_id=int(span_id_list[0]), + ) + ) ) @classmethod - def inject(cls, span, set_in_carrier, carrier): + def inject( + cls, + set_in_carrier: Setter[_T], + carrier: _T, + context: typing.Optional[Context] = None, + ) -> None: + span = get_span_from_context(context) set_in_carrier( carrier, cls.TRACE_ID_KEY, str(span.get_context().trace_id) ) diff --git a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py index 37a3a0e9e0d..52eb004a360 100644 --- a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py +++ b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py @@ -24,6 +24,7 @@ from opentelemetry import propagators, trace from opentelemetry.ext.wsgi.version import __version__ +from opentelemetry.trace.propagation import get_span_from_context from opentelemetry.trace.status import Status, StatusCanonicalCode _HTTP_VERSION_PREFIX = "HTTP/" @@ -181,7 +182,8 @@ def __call__(self, environ, start_response): start_response: The WSGI start_response callable. """ - parent_span = propagators.extract(get_header_from_environ, environ) + context = propagators.extract(get_header_from_environ, environ) + parent_span = get_span_from_context(context) span_name = get_default_span_name(environ) span = self.tracer.start_span( diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py index c8706281ad7..d853a7bcf65 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py @@ -11,8 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - -from .binaryformat import BinaryFormat -from .httptextformat import HTTPTextFormat - -__all__ = ["BinaryFormat", "HTTPTextFormat"] diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/binaryformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/binaryformat.py deleted file mode 100644 index 7f1a65882f3..00000000000 --- a/opentelemetry-api/src/opentelemetry/context/propagation/binaryformat.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright 2019, OpenTelemetry Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import abc -import typing - -from opentelemetry.trace import SpanContext - - -class BinaryFormat(abc.ABC): - """API for serialization of span context into binary formats. - - This class provides an interface that enables converting span contexts - to and from a binary format. - """ - - @staticmethod - @abc.abstractmethod - def to_bytes(context: SpanContext) -> bytes: - """Creates a byte representation of a SpanContext. - - to_bytes should read values from a SpanContext and return a data - format to represent it, in bytes. - - Args: - context: the SpanContext to serialize - - Returns: - A bytes representation of the SpanContext. - - """ - - @staticmethod - @abc.abstractmethod - def from_bytes(byte_representation: bytes) -> typing.Optional[SpanContext]: - """Return a SpanContext that was represented by bytes. - - from_bytes should return back a SpanContext that was constructed from - the data serialized in the byte_representation passed. If it is not - possible to read in a proper SpanContext, return None. - - Args: - byte_representation: the bytes to deserialize - - Returns: - A bytes representation of the SpanContext if it is valid. - Otherwise return None. - - """ diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py index 6f50f008394..1a96a067484 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py @@ -16,7 +16,12 @@ import typing import opentelemetry.trace as trace -from opentelemetry.context.propagation import httptextformat +from opentelemetry.context.context import Context +from opentelemetry.trace.propagation import ( + get_span_from_context, + httptextformat, + set_span_in_context, +) _T = typing.TypeVar("_T") @@ -61,18 +66,21 @@ class TraceContextHTTPTextFormat(httptextformat.HTTPTextFormat): @classmethod def extract( - cls, get_from_carrier: httptextformat.Getter[_T], carrier: _T - ) -> trace.SpanContext: + cls, + get_from_carrier: httptextformat.Getter[_T], + carrier: _T, + context: typing.Optional[Context] = None, + ) -> Context: """Extracts a valid SpanContext from the carrier. """ header = get_from_carrier(carrier, cls._TRACEPARENT_HEADER_NAME) if not header: - return trace.INVALID_SPAN_CONTEXT + return set_span_in_context(trace.INVALID_SPAN, context) match = re.search(cls._TRACEPARENT_HEADER_FORMAT_RE, header[0]) if not match: - return trace.INVALID_SPAN_CONTEXT + return set_span_in_context(trace.INVALID_SPAN, context) version = match.group(1) trace_id = match.group(2) @@ -80,13 +88,13 @@ def extract( trace_options = match.group(4) if trace_id == "0" * 32 or span_id == "0" * 16: - return trace.INVALID_SPAN_CONTEXT + return set_span_in_context(trace.INVALID_SPAN, context) if version == "00": if match.group(5): - return trace.INVALID_SPAN_CONTEXT + return set_span_in_context(trace.INVALID_SPAN, context) if version == "ff": - return trace.INVALID_SPAN_CONTEXT + return set_span_in_context(trace.INVALID_SPAN, context) tracestate_headers = get_from_carrier( carrier, cls._TRACESTATE_HEADER_NAME @@ -99,29 +107,29 @@ def extract( trace_options=trace.TraceOptions(trace_options), trace_state=tracestate, ) - - return span_context + return set_span_in_context(trace.DefaultSpan(span_context), context) @classmethod def inject( cls, - span: trace.Span, set_in_carrier: httptextformat.Setter[_T], carrier: _T, + context: typing.Optional[Context] = None, ) -> None: + span_context = get_span_from_context(context).get_context() - context = span.get_context() - - if context == trace.INVALID_SPAN_CONTEXT: + if span_context == trace.INVALID_SPAN_CONTEXT: return traceparent_string = "00-{:032x}-{:016x}-{:02x}".format( - context.trace_id, context.span_id, context.trace_options + span_context.trace_id, + span_context.span_id, + span_context.trace_options, ) set_in_carrier( carrier, cls._TRACEPARENT_HEADER_NAME, traceparent_string ) - if context.trace_state: - tracestate_string = _format_tracestate(context.trace_state) + if span_context.trace_state: + tracestate_string = _format_tracestate(span_context.trace_state) set_in_carrier( carrier, cls._TRACESTATE_HEADER_NAME, tracestate_string ) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py deleted file mode 100644 index c8706281ad7..00000000000 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2019, OpenTelemetry Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from .binaryformat import BinaryFormat -from .httptextformat import HTTPTextFormat - -__all__ = ["BinaryFormat", "HTTPTextFormat"] diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/binaryformat.py b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/binaryformat.py deleted file mode 100644 index d6d083c0dae..00000000000 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/binaryformat.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2019, OpenTelemetry Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import abc -import typing - -from opentelemetry.distributedcontext import DistributedContext - - -class BinaryFormat(abc.ABC): - """API for serialization of span context into binary formats. - - This class provides an interface that enables converting span contexts - to and from a binary format. - """ - - @staticmethod - @abc.abstractmethod - def to_bytes(context: DistributedContext) -> bytes: - """Creates a byte representation of a DistributedContext. - - to_bytes should read values from a DistributedContext and return a data - format to represent it, in bytes. - - Args: - context: the DistributedContext to serialize - - Returns: - A bytes representation of the DistributedContext. - - """ - - @staticmethod - @abc.abstractmethod - def from_bytes( - byte_representation: bytes, - ) -> typing.Optional[DistributedContext]: - """Return a DistributedContext that was represented by bytes. - - from_bytes should return back a DistributedContext that was constructed - from the data serialized in the byte_representation passed. If it is - not possible to read in a proper DistributedContext, return None. - - Args: - byte_representation: the bytes to deserialize - - Returns: - A bytes representation of the DistributedContext if it is valid. - Otherwise return None. - - """ diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py deleted file mode 100644 index 3e2c186283c..00000000000 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py +++ /dev/null @@ -1,114 +0,0 @@ -# Copyright 2019, OpenTelemetry Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import abc -import typing - -from opentelemetry.distributedcontext import DistributedContext - -Setter = typing.Callable[[object, str, str], None] -Getter = typing.Callable[[object, str], typing.List[str]] - - -class HTTPTextFormat(abc.ABC): - """API for propagation of span context via headers. - - This class provides an interface that enables extracting and injecting - span context into headers of HTTP requests. HTTP frameworks and clients - can integrate with HTTPTextFormat by providing the object containing the - headers, and a getter and setter function for the extraction and - injection of values, respectively. - - Example:: - - import flask - import requests - from opentelemetry.context.propagation import HTTPTextFormat - - PROPAGATOR = HTTPTextFormat() - - def get_header_from_flask_request(request, key): - return request.headers.get_all(key) - - def set_header_into_requests_request(request: requests.Request, - key: str, value: str): - request.headers[key] = value - - def example_route(): - distributed_context = PROPAGATOR.extract( - get_header_from_flask_request, - flask.request - ) - request_to_downstream = requests.Request( - "GET", "http://httpbin.org/get" - ) - PROPAGATOR.inject( - distributed_context, - set_header_into_requests_request, - request_to_downstream - ) - session = requests.Session() - session.send(request_to_downstream.prepare()) - - - .. _Propagation API Specification: - https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/api-propagators.md - """ - - @abc.abstractmethod - def extract( - self, get_from_carrier: Getter, carrier: object - ) -> DistributedContext: - """Create a DistributedContext from values in the carrier. - - The extract function should retrieve values from the carrier - object using get_from_carrier, and use values to populate a - DistributedContext value and return it. - - Args: - get_from_carrier: a function that can retrieve zero - or more values from the carrier. In the case that - the value does not exist, return an empty list. - carrier: and object which contains values that are - used to construct a DistributedContext. This object - must be paired with an appropriate get_from_carrier - which understands how to extract a value from it. - Returns: - A DistributedContext with configuration found in the carrier. - - """ - - @abc.abstractmethod - def inject( - self, - context: DistributedContext, - set_in_carrier: Setter, - carrier: object, - ) -> None: - """Inject values from a DistributedContext into a carrier. - - inject enables the propagation of values into HTTP clients or - other objects which perform an HTTP request. Implementations - should use the set_in_carrier method to set values on the - carrier. - - Args: - context: The DistributedContext to read values from. - set_in_carrier: A setter function that can set values - on the carrier. - carrier: An object that a place to define HTTP headers. - Should be paired with set_in_carrier, which should - know how to set header values on the carrier. - - """ diff --git a/opentelemetry-api/src/opentelemetry/propagators/__init__.py b/opentelemetry-api/src/opentelemetry/propagators/__init__.py index 3974a4cb03a..d706ddb8960 100644 --- a/opentelemetry-api/src/opentelemetry/propagators/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagators/__init__.py @@ -14,18 +14,48 @@ import typing -import opentelemetry.context.propagation.httptextformat as httptextformat import opentelemetry.trace as trace +from opentelemetry.context import get_current +from opentelemetry.context.context import Context from opentelemetry.context.propagation.tracecontexthttptextformat import ( TraceContextHTTPTextFormat, ) +from opentelemetry.trace.propagation import httptextformat _T = typing.TypeVar("_T") +class DefaultPropagator(httptextformat.HTTPTextFormat): + """The default Propagator used when no implementation is configured. + All operations are no-ops. + """ + + @classmethod + def extract( + cls, + get_from_carrier: httptextformat.Getter[_T], + carrier: _T, + context: typing.Optional[Context] = None, + ) -> Context: + if context: + return context + return get_current() + + @classmethod + def inject( + cls, + set_in_carrier: httptextformat.Setter[_T], + carrier: _T, + context: typing.Optional[Context] = None, + ) -> None: + pass + + def extract( - get_from_carrier: httptextformat.Getter[_T], carrier: _T -) -> trace.SpanContext: + get_from_carrier: httptextformat.Getter[_T], + carrier: _T, + context: typing.Optional[Context] = None, +) -> Context: """Load the parent SpanContext from values in the carrier. Using the specified HTTPTextFormatter, the propagator will @@ -41,13 +71,15 @@ def extract( must be paired with an appropriate get_from_carrier which understands how to extract a value from it. """ - return get_global_httptextformat().extract(get_from_carrier, carrier) + return get_global_httptextformat().extract( + get_from_carrier, carrier, context + ) def inject( - tracer: trace.Tracer, set_in_carrier: httptextformat.Setter[_T], carrier: _T, + context: typing.Optional[Context] = None, ) -> None: """Inject values from the current context into the carrier. @@ -63,9 +95,7 @@ def inject( headers. Should be paired with set_in_carrier, which should know how to set header values on the carrier. """ - get_global_httptextformat().inject( - tracer.get_current_span(), set_in_carrier, carrier - ) + get_global_httptextformat().inject(set_in_carrier, carrier, context) _HTTP_TEXT_FORMAT = ( diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py index 881a74287a2..df80a1a4edb 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py @@ -13,7 +13,22 @@ # limitations under the License. from typing import Optional -from opentelemetry.trace import INVALID_SPAN_CONTEXT, Span, SpanContext +from opentelemetry import trace as trace_api +from opentelemetry.context import get_value, set_current, set_value +from opentelemetry.context.context import Context -_SPAN_CONTEXT_KEY = "extracted-span-context" SPAN_KEY = "current-span" + + +def set_span_in_context( + span: trace_api.Span, context: Optional[Context] = None +) -> Context: + ctx = set_value(SPAN_KEY, span, context=context) + return ctx + + +def get_span_from_context(context: Optional[Context] = None) -> trace_api.Span: + span = get_value(SPAN_KEY, context=context) + if not isinstance(span, trace_api.Span): + return trace_api.INVALID_SPAN + return span diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/trace/propagation/httptextformat.py similarity index 91% rename from opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py rename to opentelemetry-api/src/opentelemetry/trace/propagation/httptextformat.py index b64a298c410..3277b6ecc44 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/httptextformat.py @@ -15,7 +15,7 @@ import abc import typing -from opentelemetry.trace import Span, SpanContext +from opentelemetry.context.context import Context _T = typing.TypeVar("_T") @@ -70,10 +70,14 @@ def example_route(): https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/api-propagators.md """ + @classmethod @abc.abstractmethod def extract( - self, get_from_carrier: Getter[_T], carrier: _T - ) -> SpanContext: + cls, + get_from_carrier: Getter[_T], + carrier: _T, + context: typing.Optional[Context] = None, + ) -> Context: """Create a SpanContext from values in the carrier. The extract function should retrieve values from the carrier @@ -93,9 +97,13 @@ def extract( """ + @classmethod @abc.abstractmethod def inject( - self, span: Span, set_in_carrier: Setter[_T], carrier: _T + cls, + set_in_carrier: Setter[_T], + carrier: _T, + context: typing.Optional[Context] = None, ) -> None: """Inject values from a Span into a carrier. diff --git a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py index 8f283ef8819..46bd6658012 100644 --- a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py @@ -14,10 +14,13 @@ import typing import unittest -from unittest.mock import Mock from opentelemetry import trace from opentelemetry.context.propagation import tracecontexthttptextformat +from opentelemetry.trace.propagation import ( + get_span_from_context, + set_span_in_context, +) FORMAT = tracecontexthttptextformat.TraceContextHTTPTextFormat() @@ -43,8 +46,8 @@ def test_no_traceparent_header(self): trace-id and parent-id that represents the current request. """ output = {} # type:typing.Dict[str, typing.List[str]] - span_context = FORMAT.extract(get_as_list, output) - self.assertTrue(isinstance(span_context, trace.SpanContext)) + span = get_span_from_context(FORMAT.extract(get_as_list, output)) + self.assertTrue(isinstance(span.get_context(), trace.SpanContext)) def test_headers_with_tracestate(self): """When there is a traceparent and tracestate header, data from @@ -55,23 +58,25 @@ def test_headers_with_tracestate(self): span_id=format(self.SPAN_ID, "016x"), ) tracestate_value = "foo=1,bar=2,baz=3" - span_context = FORMAT.extract( - get_as_list, - { - "traceparent": [traceparent_value], - "tracestate": [tracestate_value], - }, - ) + span_context = get_span_from_context( + FORMAT.extract( + get_as_list, + { + "traceparent": [traceparent_value], + "tracestate": [tracestate_value], + }, + ) + ).get_context() self.assertEqual(span_context.trace_id, self.TRACE_ID) self.assertEqual(span_context.span_id, self.SPAN_ID) self.assertEqual( span_context.trace_state, {"foo": "1", "bar": "2", "baz": "3"} ) - - mock_span = Mock() - mock_span.configure_mock(**{"get_context.return_value": span_context}) output = {} # type:typing.Dict[str, str] - FORMAT.inject(mock_span, dict.__setitem__, output) + span = trace.DefaultSpan(span_context) + + ctx = set_span_in_context(span) + FORMAT.inject(dict.__setitem__, output, ctx) self.assertEqual(output["traceparent"], traceparent_value) for pair in ["foo=1", "bar=2", "baz=3"]: self.assertIn(pair, output["tracestate"]) @@ -96,16 +101,18 @@ def test_invalid_trace_id(self): Note that the opposite is not true: failure to parse tracestate MUST NOT affect the parsing of traceparent. """ - span_context = FORMAT.extract( - get_as_list, - { - "traceparent": [ - "00-00000000000000000000000000000000-1234567890123456-00" - ], - "tracestate": ["foo=1,bar=2,foo=3"], - }, + span = get_span_from_context( + FORMAT.extract( + get_as_list, + { + "traceparent": [ + "00-00000000000000000000000000000000-1234567890123456-00" + ], + "tracestate": ["foo=1,bar=2,foo=3"], + }, + ) ) - self.assertEqual(span_context, trace.INVALID_SPAN_CONTEXT) + self.assertEqual(span.get_context(), trace.INVALID_SPAN_CONTEXT) def test_invalid_parent_id(self): """If the parent id is invalid, we must ignore the full traceparent @@ -125,16 +132,18 @@ def test_invalid_parent_id(self): Note that the opposite is not true: failure to parse tracestate MUST NOT affect the parsing of traceparent. """ - span_context = FORMAT.extract( - get_as_list, - { - "traceparent": [ - "00-00000000000000000000000000000000-0000000000000000-00" - ], - "tracestate": ["foo=1,bar=2,foo=3"], - }, + span = get_span_from_context( + FORMAT.extract( + get_as_list, + { + "traceparent": [ + "00-00000000000000000000000000000000-0000000000000000-00" + ], + "tracestate": ["foo=1,bar=2,foo=3"], + }, + ) ) - self.assertEqual(span_context, trace.INVALID_SPAN_CONTEXT) + self.assertEqual(span.get_context(), trace.INVALID_SPAN_CONTEXT) def test_no_send_empty_tracestate(self): """If the tracestate is empty, do not set the header. @@ -145,15 +154,11 @@ def test_no_send_empty_tracestate(self): empty tracestate headers but SHOULD avoid sending them. """ output = {} # type:typing.Dict[str, str] - mock_span = Mock() - mock_span.configure_mock( - **{ - "get_context.return_value": trace.SpanContext( - self.TRACE_ID, self.SPAN_ID - ) - } + span = trace.DefaultSpan( + trace.SpanContext(self.TRACE_ID, self.SPAN_ID) ) - FORMAT.inject(mock_span, dict.__setitem__, output) + ctx = set_span_in_context(span) + FORMAT.inject(dict.__setitem__, output, ctx) self.assertTrue("traceparent" in output) self.assertFalse("tracestate" in output) @@ -165,48 +170,57 @@ def test_format_not_supported(self): If the version cannot be parsed, return an invalid trace header. """ - span_context = FORMAT.extract( - get_as_list, - { - "traceparent": [ - "00-12345678901234567890123456789012-" - "1234567890123456-00-residue" - ], - "tracestate": ["foo=1,bar=2,foo=3"], - }, + span = get_span_from_context( + FORMAT.extract( + get_as_list, + { + "traceparent": [ + "00-12345678901234567890123456789012-" + "1234567890123456-00-residue" + ], + "tracestate": ["foo=1,bar=2,foo=3"], + }, + ) ) - self.assertEqual(span_context, trace.INVALID_SPAN_CONTEXT) + self.assertEqual(span.get_context(), trace.INVALID_SPAN_CONTEXT) def test_propagate_invalid_context(self): """Do not propagate invalid trace context.""" output = {} # type:typing.Dict[str, str] - FORMAT.inject(trace.INVALID_SPAN, dict.__setitem__, output) + ctx = set_span_in_context( + trace.DefaultSpan(trace.INVALID_SPAN_CONTEXT) + ) + FORMAT.inject(dict.__setitem__, output, context=ctx) self.assertFalse("traceparent" in output) def test_tracestate_empty_header(self): """Test tracestate with an additional empty header (should be ignored) """ - span_context = FORMAT.extract( - get_as_list, - { - "traceparent": [ - "00-12345678901234567890123456789012-1234567890123456-00" - ], - "tracestate": ["foo=1", ""], - }, + span = get_span_from_context( + FORMAT.extract( + get_as_list, + { + "traceparent": [ + "00-12345678901234567890123456789012-1234567890123456-00" + ], + "tracestate": ["foo=1", ""], + }, + ) ) - self.assertEqual(span_context.trace_state["foo"], "1") + self.assertEqual(span.get_context().trace_state["foo"], "1") def test_tracestate_header_with_trailing_comma(self): """Do not propagate invalid trace context. """ - span_context = FORMAT.extract( - get_as_list, - { - "traceparent": [ - "00-12345678901234567890123456789012-1234567890123456-00" - ], - "tracestate": ["foo=1,"], - }, + span = get_span_from_context( + FORMAT.extract( + get_as_list, + { + "traceparent": [ + "00-12345678901234567890123456789012-1234567890123456-00" + ], + "tracestate": ["foo=1,"], + }, + ) ) - self.assertEqual(span_context.trace_state["foo"], "1") + self.assertEqual(span.get_context().trace_state["foo"], "1") diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py index 4c9214dbccb..3cb47f83795 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py @@ -15,7 +15,17 @@ import typing import opentelemetry.trace as trace -from opentelemetry.context.propagation.httptextformat import HTTPTextFormat +from opentelemetry.context import Context +from opentelemetry.trace.propagation import ( + get_span_from_context, + set_span_in_context, +) +from opentelemetry.trace.propagation.httptextformat import ( + _T, + Getter, + HTTPTextFormat, + Setter, +) class B3Format(HTTPTextFormat): @@ -33,7 +43,12 @@ class B3Format(HTTPTextFormat): _SAMPLE_PROPAGATE_VALUES = set(["1", "True", "true", "d"]) @classmethod - def extract(cls, get_from_carrier, carrier): + def extract( + cls, + get_from_carrier: Getter[_T], + carrier: _T, + context: typing.Optional[Context] = None, + ) -> Context: trace_id = format_trace_id(trace.INVALID_TRACE_ID) span_id = format_span_id(trace.INVALID_SPAN_ID) sampled = "0" @@ -58,7 +73,7 @@ def extract(cls, get_from_carrier, carrier): elif len(fields) == 4: trace_id, span_id, sampled, _ = fields else: - return trace.INVALID_SPAN_CONTEXT + return set_span_in_context(trace.INVALID_SPAN) else: trace_id = ( _extract_first_element( @@ -92,21 +107,31 @@ def extract(cls, get_from_carrier, carrier): # header is set to allow. if sampled in cls._SAMPLE_PROPAGATE_VALUES or flags == "1": options |= trace.TraceOptions.SAMPLED - return trace.SpanContext( - # trace an span ids are encoded in hex, so must be converted - trace_id=int(trace_id, 16), - span_id=int(span_id, 16), - trace_options=trace.TraceOptions(options), - trace_state=trace.TraceState(), + return set_span_in_context( + trace.DefaultSpan( + trace.SpanContext( + # trace an span ids are encoded in hex, so must be converted + trace_id=int(trace_id, 16), + span_id=int(span_id, 16), + trace_options=trace.TraceOptions(options), + trace_state=trace.TraceState(), + ) + ) ) @classmethod - def inject(cls, span, set_in_carrier, carrier): + def inject( + cls, + set_in_carrier: Setter[_T], + carrier: _T, + context: typing.Optional[Context] = None, + ) -> None: + span = get_span_from_context(context=context) sampled = ( trace.TraceOptions.SAMPLED & span.context.trace_options ) != 0 set_in_carrier( - carrier, cls.TRACE_ID_KEY, format_trace_id(span.context.trace_id) + carrier, cls.TRACE_ID_KEY, format_trace_id(span.context.trace_id), ) set_in_carrier( carrier, cls.SPAN_ID_KEY, format_span_id(span.context.span_id) diff --git a/opentelemetry-sdk/tests/context/propagation/test_b3_format.py b/opentelemetry-sdk/tests/context/propagation/test_b3_format.py index 17f7fdf7cae..cc64929164b 100644 --- a/opentelemetry-sdk/tests/context/propagation/test_b3_format.py +++ b/opentelemetry-sdk/tests/context/propagation/test_b3_format.py @@ -17,6 +17,10 @@ import opentelemetry.sdk.context.propagation.b3_format as b3_format import opentelemetry.sdk.trace as trace import opentelemetry.trace as trace_api +from opentelemetry.trace.propagation import ( + get_span_from_context, + set_span_in_context, +) FORMAT = b3_format.B3Format() @@ -28,7 +32,8 @@ def get_as_list(dict_object, key): def get_child_parent_new_carrier(old_carrier): - parent_context = FORMAT.extract(get_as_list, old_carrier) + ctx = FORMAT.extract(get_as_list, old_carrier) + parent_context = get_span_from_context(ctx).get_context() parent = trace.Span("parent", parent_context) child = trace.Span( @@ -43,7 +48,8 @@ def get_child_parent_new_carrier(old_carrier): ) new_carrier = {} - FORMAT.inject(child, dict.__setitem__, new_carrier) + ctx = set_span_in_context(child) + FORMAT.inject(dict.__setitem__, new_carrier, context=ctx) return child, parent, new_carrier @@ -222,7 +228,8 @@ def test_invalid_single_header(self): invalid SpanContext. """ carrier = {FORMAT.SINGLE_HEADER_KEY: "0-1-2-3-4-5-6-7"} - span_context = FORMAT.extract(get_as_list, carrier) + ctx = FORMAT.extract(get_as_list, carrier) + span_context = get_span_from_context(ctx).get_context() self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID) self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID) @@ -232,7 +239,9 @@ def test_missing_trace_id(self): FORMAT.SPAN_ID_KEY: self.serialized_span_id, FORMAT.FLAGS_KEY: "1", } - span_context = FORMAT.extract(get_as_list, carrier) + + ctx = FORMAT.extract(get_as_list, carrier) + span_context = get_span_from_context(ctx).get_context() self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID) def test_missing_span_id(self): @@ -241,5 +250,7 @@ def test_missing_span_id(self): FORMAT.TRACE_ID_KEY: self.serialized_trace_id, FORMAT.FLAGS_KEY: "1", } - span_context = FORMAT.extract(get_as_list, carrier) + + ctx = FORMAT.extract(get_as_list, carrier) + span_context = get_span_from_context(ctx).get_context() self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID) From e74972c85832c27962954643e0014bc53d6c1129 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Fri, 21 Feb 2020 16:56:28 -0800 Subject: [PATCH 02/15] minor cleanups Signed-off-by: Alex Boten --- .../tests/test_shim.py | 10 +++--- .../propagation/tracecontexthttptextformat.py | 12 +++---- .../src/opentelemetry/propagators/__init__.py | 36 +++---------------- .../trace/propagation/httptextformat.py | 14 ++++---- .../sdk/context/propagation/b3_format.py | 17 +++++---- 5 files changed, 30 insertions(+), 59 deletions(-) diff --git a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py index 9da5e8f8998..066b2981013 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -31,9 +31,9 @@ set_span_in_context, ) from opentelemetry.trace.propagation.httptextformat import ( - _T, Getter, HTTPTextFormat, + HTTPTextFormatT, Setter, ) @@ -555,8 +555,8 @@ class MockHTTPTextFormat(HTTPTextFormat): @classmethod def extract( cls, - get_from_carrier: Getter[_T], - carrier: _T, + get_from_carrier: Getter[HTTPTextFormatT], + carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> Context: trace_id_list = get_from_carrier(carrier, cls.TRACE_ID_KEY) @@ -577,8 +577,8 @@ def extract( @classmethod def inject( cls, - set_in_carrier: Setter[_T], - carrier: _T, + set_in_carrier: Setter[HTTPTextFormatT], + carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: span = get_span_from_context(context) diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py index 1a96a067484..67984924e5a 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py @@ -23,8 +23,6 @@ set_span_in_context, ) -_T = typing.TypeVar("_T") - # Keys and values are strings of up to 256 printable US-ASCII characters. # Implementations should conform to the `W3C Trace Context - Tracestate`_ # spec, which describes additional restrictions on valid field values. @@ -67,8 +65,10 @@ class TraceContextHTTPTextFormat(httptextformat.HTTPTextFormat): @classmethod def extract( cls, - get_from_carrier: httptextformat.Getter[_T], - carrier: _T, + get_from_carrier: httptextformat.Getter[ + httptextformat.HTTPTextFormatT + ], + carrier: httptextformat.HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> Context: """Extracts a valid SpanContext from the carrier. @@ -112,8 +112,8 @@ def extract( @classmethod def inject( cls, - set_in_carrier: httptextformat.Setter[_T], - carrier: _T, + set_in_carrier: httptextformat.Setter[httptextformat.HTTPTextFormatT], + carrier: httptextformat.HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: span_context = get_span_from_context(context).get_context() diff --git a/opentelemetry-api/src/opentelemetry/propagators/__init__.py b/opentelemetry-api/src/opentelemetry/propagators/__init__.py index d706ddb8960..690272a824d 100644 --- a/opentelemetry-api/src/opentelemetry/propagators/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagators/__init__.py @@ -22,38 +22,10 @@ ) from opentelemetry.trace.propagation import httptextformat -_T = typing.TypeVar("_T") - - -class DefaultPropagator(httptextformat.HTTPTextFormat): - """The default Propagator used when no implementation is configured. - All operations are no-ops. - """ - - @classmethod - def extract( - cls, - get_from_carrier: httptextformat.Getter[_T], - carrier: _T, - context: typing.Optional[Context] = None, - ) -> Context: - if context: - return context - return get_current() - - @classmethod - def inject( - cls, - set_in_carrier: httptextformat.Setter[_T], - carrier: _T, - context: typing.Optional[Context] = None, - ) -> None: - pass - def extract( - get_from_carrier: httptextformat.Getter[_T], - carrier: _T, + get_from_carrier: httptextformat.Getter[httptextformat.HTTPTextFormatT], + carrier: httptextformat.HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> Context: """Load the parent SpanContext from values in the carrier. @@ -77,8 +49,8 @@ def extract( def inject( - set_in_carrier: httptextformat.Setter[_T], - carrier: _T, + set_in_carrier: httptextformat.Setter[httptextformat.HTTPTextFormatT], + carrier: httptextformat.HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: """Inject values from the current context into the carrier. diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/trace/propagation/httptextformat.py index 3277b6ecc44..cad824a66e1 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/httptextformat.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/httptextformat.py @@ -17,10 +17,10 @@ from opentelemetry.context.context import Context -_T = typing.TypeVar("_T") +HTTPTextFormatT = typing.TypeVar("HTTPTextFormatT") -Setter = typing.Callable[[_T, str, str], None] -Getter = typing.Callable[[_T, str], typing.List[str]] +Setter = typing.Callable[[HTTPTextFormatT, str, str], None] +Getter = typing.Callable[[HTTPTextFormatT, str], typing.List[str]] class HTTPTextFormat(abc.ABC): @@ -74,8 +74,8 @@ def example_route(): @abc.abstractmethod def extract( cls, - get_from_carrier: Getter[_T], - carrier: _T, + get_from_carrier: Getter[HTTPTextFormatT], + carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> Context: """Create a SpanContext from values in the carrier. @@ -101,8 +101,8 @@ def extract( @abc.abstractmethod def inject( cls, - set_in_carrier: Setter[_T], - carrier: _T, + set_in_carrier: Setter[HTTPTextFormatT], + carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: """Inject values from a Span into a carrier. diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py index 3cb47f83795..6bf4429b395 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py @@ -21,9 +21,9 @@ set_span_in_context, ) from opentelemetry.trace.propagation.httptextformat import ( - _T, Getter, HTTPTextFormat, + HTTPTextFormatT, Setter, ) @@ -45,8 +45,8 @@ class B3Format(HTTPTextFormat): @classmethod def extract( cls, - get_from_carrier: Getter[_T], - carrier: _T, + get_from_carrier: Getter[HTTPTextFormatT], + carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> Context: trace_id = format_trace_id(trace.INVALID_TRACE_ID) @@ -122,8 +122,8 @@ def extract( @classmethod def inject( cls, - set_in_carrier: Setter[_T], - carrier: _T, + set_in_carrier: Setter[HTTPTextFormatT], + carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: span = get_span_from_context(context=context) @@ -155,10 +155,9 @@ def format_span_id(span_id: int) -> str: return format(span_id, "016x") -_T = typing.TypeVar("_T") - - -def _extract_first_element(items: typing.Iterable[_T]) -> typing.Optional[_T]: +def _extract_first_element( + items: typing.Iterable[HTTPTextFormatT], +) -> typing.Optional[HTTPTextFormatT]: if items is None: return None return next(iter(items), None) From fcabf003940198c0da66d7db2dae2c3265cee0d8 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 2 Mar 2020 10:11:26 -0800 Subject: [PATCH 03/15] moving tracecontexthttptextformat to trace/propagation Signed-off-by: Alex Boten --- .../opentelemetry/context/propagation/__init__.py | 13 ------------- .../src/opentelemetry/propagators/__init__.py | 2 +- .../propagation/tracecontexthttptextformat.py | 0 .../propagation/test_tracecontexthttptextformat.py | 2 +- 4 files changed, 2 insertions(+), 15 deletions(-) delete mode 100644 opentelemetry-api/src/opentelemetry/context/propagation/__init__.py rename opentelemetry-api/src/opentelemetry/{context => trace}/propagation/tracecontexthttptextformat.py (100%) diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py deleted file mode 100644 index d853a7bcf65..00000000000 --- a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019, OpenTelemetry Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/opentelemetry-api/src/opentelemetry/propagators/__init__.py b/opentelemetry-api/src/opentelemetry/propagators/__init__.py index 690272a824d..ba313e8c5ea 100644 --- a/opentelemetry-api/src/opentelemetry/propagators/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagators/__init__.py @@ -17,7 +17,7 @@ import opentelemetry.trace as trace from opentelemetry.context import get_current from opentelemetry.context.context import Context -from opentelemetry.context.propagation.tracecontexthttptextformat import ( +from opentelemetry.trace.propagation.tracecontexthttptextformat import ( TraceContextHTTPTextFormat, ) from opentelemetry.trace.propagation import httptextformat diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py b/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py similarity index 100% rename from opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py rename to opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py diff --git a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py index 46bd6658012..e4a15d34b35 100644 --- a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py @@ -16,7 +16,7 @@ import unittest from opentelemetry import trace -from opentelemetry.context.propagation import tracecontexthttptextformat +from opentelemetry.trace.propagation import tracecontexthttptextformat from opentelemetry.trace.propagation import ( get_span_from_context, set_span_in_context, From fe6bf4b52c7c43544220f80096a4b3a40286e610 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 2 Mar 2020 10:18:58 -0800 Subject: [PATCH 04/15] moving test to match src change Signed-off-by: Alex Boten --- .../propagation/test_tracecontexthttptextformat.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename opentelemetry-api/tests/{context => trace}/propagation/test_tracecontexthttptextformat.py (100%) diff --git a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py similarity index 100% rename from opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py rename to opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py From 914ad6b3acdc835c79b91aa50504564192d63763 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 4 Mar 2020 13:19:58 -0800 Subject: [PATCH 05/15] attach/detach for wsgi and flask extensions --- .../src/opentelemetry/ext/flask/__init__.py | 11 +++++++---- .../src/opentelemetry/ext/wsgi/__init__.py | 16 ++++++++++------ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py b/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py index 88d42068546..b30b42d3fd2 100644 --- a/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py +++ b/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py @@ -6,7 +6,7 @@ from flask import request as flask_request import opentelemetry.ext.wsgi as otel_wsgi -from opentelemetry import propagators, trace +from opentelemetry import context, propagators, trace from opentelemetry.ext.flask.version import __version__ from opentelemetry.trace.propagation import get_span_from_context from opentelemetry.util import time_ns @@ -16,6 +16,7 @@ _ENVIRON_STARTTIME_KEY = "opentelemetry-flask.starttime_key" _ENVIRON_SPAN_KEY = "opentelemetry-flask.span_key" _ENVIRON_ACTIVATION_KEY = "opentelemetry-flask.activation_key" +_ENVIRON_TOKEN = "opentelemetry-flask.token" def instrument_app(flask): @@ -58,8 +59,9 @@ def _before_flask_request(): span_name = flask_request.endpoint or otel_wsgi.get_default_span_name( environ ) - context = propagators.extract(otel_wsgi.get_header_from_environ, environ) - parent_span = get_span_from_context(context).get_context() + token = context.attach( + propagators.extract(otel_wsgi.get_header_from_environ, environ) + ) tracer = trace.get_tracer(__name__, __version__) @@ -69,7 +71,6 @@ def _before_flask_request(): attributes["http.route"] = flask_request.url_rule.rule span = tracer.start_span( span_name, - parent_span, kind=trace.SpanKind.SERVER, attributes=attributes, start_time=environ.get(_ENVIRON_STARTTIME_KEY), @@ -78,6 +79,7 @@ def _before_flask_request(): activation.__enter__() environ[_ENVIRON_ACTIVATION_KEY] = activation environ[_ENVIRON_SPAN_KEY] = span + environ[_ENVIRON_TOKEN] = token def _teardown_flask_request(exc): @@ -95,3 +97,4 @@ def _teardown_flask_request(exc): activation.__exit__( type(exc), exc, getattr(exc, "__traceback__", None) ) + context.detach(flask_request.environ.get(_ENVIRON_TOKEN)) diff --git a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py index 52eb004a360..b96fc057d14 100644 --- a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py +++ b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py @@ -22,7 +22,7 @@ import typing import wsgiref.util as wsgiref_util -from opentelemetry import propagators, trace +from opentelemetry import context, propagators, trace from opentelemetry.ext.wsgi.version import __version__ from opentelemetry.trace.propagation import get_span_from_context from opentelemetry.trace.status import Status, StatusCanonicalCode @@ -182,13 +182,13 @@ def __call__(self, environ, start_response): start_response: The WSGI start_response callable. """ - context = propagators.extract(get_header_from_environ, environ) - parent_span = get_span_from_context(context) + token = context.attach( + propagators.extract(get_header_from_environ, environ) + ) span_name = get_default_span_name(environ) span = self.tracer.start_span( span_name, - parent_span, kind=trace.SpanKind.SERVER, attributes=collect_request_attributes(environ), ) @@ -199,17 +199,20 @@ def __call__(self, environ, start_response): span, start_response ) iterable = self.wsgi(environ, start_response) - return _end_span_after_iterating(iterable, span, self.tracer) + return _end_span_after_iterating( + iterable, span, self.tracer, token + ) except: # noqa # TODO Set span status (cf. https://github.com/open-telemetry/opentelemetry-python/issues/292) span.end() + context.detach(token) raise # Put this in a subfunction to not delay the call to the wrapped # WSGI application (instrumentation should change the application # behavior as little as possible). -def _end_span_after_iterating(iterable, span, tracer): +def _end_span_after_iterating(iterable, span, tracer, token): try: with tracer.use_span(span): for yielded in iterable: @@ -219,3 +222,4 @@ def _end_span_after_iterating(iterable, span, tracer): if close: close() span.end() + context.detach(token) From 8d2293434d456d91ce98978818774533d3e4a3e9 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 4 Mar 2020 13:33:45 -0800 Subject: [PATCH 06/15] lint fixes --- opentelemetry-api/src/opentelemetry/propagators/__init__.py | 2 +- .../tests/trace/propagation/test_tracecontexthttptextformat.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/propagators/__init__.py b/opentelemetry-api/src/opentelemetry/propagators/__init__.py index ba313e8c5ea..1b1e1ec27a9 100644 --- a/opentelemetry-api/src/opentelemetry/propagators/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagators/__init__.py @@ -17,10 +17,10 @@ import opentelemetry.trace as trace from opentelemetry.context import get_current from opentelemetry.context.context import Context +from opentelemetry.trace.propagation import httptextformat from opentelemetry.trace.propagation.tracecontexthttptextformat import ( TraceContextHTTPTextFormat, ) -from opentelemetry.trace.propagation import httptextformat def extract( diff --git a/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py index e4a15d34b35..cb10eba46d0 100644 --- a/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py @@ -16,10 +16,10 @@ import unittest from opentelemetry import trace -from opentelemetry.trace.propagation import tracecontexthttptextformat from opentelemetry.trace.propagation import ( get_span_from_context, set_span_in_context, + tracecontexthttptextformat, ) FORMAT = tracecontexthttptextformat.TraceContextHTTPTextFormat() From 03ea072ad965f966794e6e64844c72c518c869f0 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 4 Mar 2020 17:20:11 -0800 Subject: [PATCH 07/15] adding composite propagator --- .../opentelemetry/propagators/composite.py | 75 +++++++++++ .../tests/propagators/test_composite.py | 119 ++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 opentelemetry-api/src/opentelemetry/propagators/composite.py create mode 100644 opentelemetry-api/tests/propagators/test_composite.py diff --git a/opentelemetry-api/src/opentelemetry/propagators/composite.py b/opentelemetry-api/src/opentelemetry/propagators/composite.py new file mode 100644 index 00000000000..3ac61be4f13 --- /dev/null +++ b/opentelemetry-api/src/opentelemetry/propagators/composite.py @@ -0,0 +1,75 @@ +# Copyright 2020, OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import logging +import typing + +from opentelemetry.context.context import Context +from opentelemetry.trace.propagation import httptextformat + +logger = logging.getLogger(__name__) + + +class CompositePropagator(httptextformat.HTTPTextFormat): + """ CompositePropagator provides a mechanism for combining multiple + propagators into a single one. + """ + + propagators = [] # type: typing.List[httptextformat.HTTPTextFormat] + + @classmethod + def extract( + cls, + get_from_carrier: httptextformat.Getter[ + httptextformat.HTTPTextFormatT + ], + carrier: httptextformat.HTTPTextFormatT, + context: typing.Optional[Context] = None, + ) -> Context: + """ Run each of the configured propagators with the given context and carrier. + Propagators are run in the order they are configured, if multiple + propagators write the same context key, the propagator later in the list + will override previous propagators. + + See `opentelemetry.trace.propagation.httptextformat.HTTPTextFormat.extract` + """ + for propagator in cls.propagators: + try: + context = propagator.extract( + get_from_carrier, carrier, context + ) + # pylint: disable=broad-except + except Exception: + logging.exception("Exception during extract") + return context # type: ignore + + @classmethod + def inject( + cls, + set_in_carrier: httptextformat.Setter[httptextformat.HTTPTextFormatT], + carrier: httptextformat.HTTPTextFormatT, + context: typing.Optional[Context] = None, + ) -> None: + """ Run each of the configured propagators with the given context and carrier. + Propagators are run in the order they are configured, if multiple + propagators write the same carrier key, the propagator later in the list + will override previous propagators. + + See `opentelemetry.trace.propagation.httptextformat.HTTPTextFormat.inject` + """ + for propagator in cls.propagators: + try: + propagator.inject(set_in_carrier, carrier, context) + # pylint: disable=broad-except + except Exception: + logging.exception("Exception during inject") diff --git a/opentelemetry-api/tests/propagators/test_composite.py b/opentelemetry-api/tests/propagators/test_composite.py new file mode 100644 index 00000000000..84f079ebe9f --- /dev/null +++ b/opentelemetry-api/tests/propagators/test_composite.py @@ -0,0 +1,119 @@ +# Copyright 2020, OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest +from logging import ERROR +from unittest.mock import Mock + +from opentelemetry.propagators.composite import CompositePropagator + + +def get_as_list(dict_object, key): + value = dict_object.get(key) + return [value] if value is not None else [] + + +def mock_inject(name): + def wrapped(setter, carrier=None, context=None): + carrier[name] = "data" + + return wrapped + + +def mock_extract(name): + def wrapped(getter, carrier=None, context=None): + context[name] = "context" + return context + + return wrapped + + +class TestCompositePropagator(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.mock_propagator_0 = Mock( + inject=mock_inject("mock-0"), extract=mock_extract("mock-0") + ) + cls.mock_propagator_1 = Mock( + inject=mock_inject("mock-1"), extract=mock_extract("mock-1") + ) + + def mock_broken_extract(getter, carrier=None, context=None): + raise Exception("failed to extract") + + def mock_broken_inject(setter, carrier=None, context=None): + raise Exception("failed to inject") + + cls.mock_broken_extract = Mock( + inject=mock_inject("mock-broken-extract"), + extract=mock_broken_extract, + ) + cls.mock_broken_inject = Mock( + inject=mock_broken_inject, + extract=mock_extract("mock-broken-inject"), + ) + + def test_no_propagators(self): + CompositePropagator.propagators = [] + new_carrier = {} + CompositePropagator.inject(dict.__setitem__, carrier=new_carrier) + self.assertEqual(new_carrier, {}) + CompositePropagator.extract(get_as_list, carrier=new_carrier) + self.assertEqual(new_carrier, {}) + + def test_single_propagator(self): + new_carrier = {} + CompositePropagator.propagators = [self.mock_propagator_0] + CompositePropagator.inject(dict.__setitem__, carrier=new_carrier) + self.assertEqual(new_carrier, {"mock-0": "data"}) + + context = {} + CompositePropagator.extract( + get_as_list, carrier=new_carrier, context=context + ) + self.assertEqual(context, {"mock-0": "context"}) + + def test_multiple_propagators(self): + CompositePropagator.propagators = [ + self.mock_propagator_0, + self.mock_propagator_1, + ] + + new_carrier = {} + CompositePropagator.inject(dict.__setitem__, carrier=new_carrier) + self.assertEqual(new_carrier, {"mock-0": "data", "mock-1": "data"}) + + context = {} + CompositePropagator.extract( + get_as_list, carrier=new_carrier, context=context + ) + self.assertEqual(context, {"mock-0": "context", "mock-1": "context"}) + + def test_broken_propagator(self): + CompositePropagator.propagators = [ + self.mock_broken_extract, + self.mock_broken_inject, + ] + + new_carrier = {} + with self.assertLogs(level=ERROR): + CompositePropagator.inject(dict.__setitem__, carrier=new_carrier) + self.assertEqual(new_carrier, {"mock-broken-extract": "data"}) + + context = {} + with self.assertLogs(level=ERROR): + CompositePropagator.extract( + get_as_list, carrier=new_carrier, context=context + ) + self.assertEqual(context, {"mock-broken-inject": "context"}) From 5bc4f602508b74da8b1ac92b19a79325b827844a Mon Sep 17 00:00:00 2001 From: alrex Date: Thu, 5 Mar 2020 09:16:18 -0800 Subject: [PATCH 08/15] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Mauricio Vásquez --- .../trace/propagation/test_tracecontexthttptextformat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py index cb10eba46d0..234601f26a9 100644 --- a/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py @@ -47,7 +47,7 @@ def test_no_traceparent_header(self): """ output = {} # type:typing.Dict[str, typing.List[str]] span = get_span_from_context(FORMAT.extract(get_as_list, output)) - self.assertTrue(isinstance(span.get_context(), trace.SpanContext)) + self.assertIsInstance(span.get_context(), trace.SpanContext) def test_headers_with_tracestate(self): """When there is a traceparent and tracestate header, data from @@ -188,7 +188,7 @@ def test_propagate_invalid_context(self): """Do not propagate invalid trace context.""" output = {} # type:typing.Dict[str, str] ctx = set_span_in_context( - trace.DefaultSpan(trace.INVALID_SPAN_CONTEXT) + trace.INVALID_SPAN ) FORMAT.inject(dict.__setitem__, output, context=ctx) self.assertFalse("traceparent" in output) From 80c5c0703c9c2deb7e184d29c3fd16addeebc0ce Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Thu, 5 Mar 2020 09:27:51 -0800 Subject: [PATCH 09/15] rename CompositePropagator to CompositeHTTPPropagator, remove try/except --- .../opentelemetry/propagators/composite.py | 18 +++------- .../tests/propagators/test_composite.py | 36 +++++++++---------- .../test_tracecontexthttptextformat.py | 4 +-- 3 files changed, 22 insertions(+), 36 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/propagators/composite.py b/opentelemetry-api/src/opentelemetry/propagators/composite.py index 3ac61be4f13..5ec73cd0b13 100644 --- a/opentelemetry-api/src/opentelemetry/propagators/composite.py +++ b/opentelemetry-api/src/opentelemetry/propagators/composite.py @@ -20,8 +20,8 @@ logger = logging.getLogger(__name__) -class CompositePropagator(httptextformat.HTTPTextFormat): - """ CompositePropagator provides a mechanism for combining multiple +class CompositeHTTPPropagator(httptextformat.HTTPTextFormat): + """ CompositeHTTPPropagator provides a mechanism for combining multiple propagators into a single one. """ @@ -44,13 +44,7 @@ def extract( See `opentelemetry.trace.propagation.httptextformat.HTTPTextFormat.extract` """ for propagator in cls.propagators: - try: - context = propagator.extract( - get_from_carrier, carrier, context - ) - # pylint: disable=broad-except - except Exception: - logging.exception("Exception during extract") + context = propagator.extract(get_from_carrier, carrier, context) return context # type: ignore @classmethod @@ -68,8 +62,4 @@ def inject( See `opentelemetry.trace.propagation.httptextformat.HTTPTextFormat.inject` """ for propagator in cls.propagators: - try: - propagator.inject(set_in_carrier, carrier, context) - # pylint: disable=broad-except - except Exception: - logging.exception("Exception during inject") + propagator.inject(set_in_carrier, carrier, context) diff --git a/opentelemetry-api/tests/propagators/test_composite.py b/opentelemetry-api/tests/propagators/test_composite.py index 84f079ebe9f..25a5fc10055 100644 --- a/opentelemetry-api/tests/propagators/test_composite.py +++ b/opentelemetry-api/tests/propagators/test_composite.py @@ -16,7 +16,7 @@ from logging import ERROR from unittest.mock import Mock -from opentelemetry.propagators.composite import CompositePropagator +from opentelemetry.propagators.composite import CompositeHTTPPropagator def get_as_list(dict_object, key): @@ -50,10 +50,10 @@ def setUpClass(cls): ) def mock_broken_extract(getter, carrier=None, context=None): - raise Exception("failed to extract") + return context def mock_broken_inject(setter, carrier=None, context=None): - raise Exception("failed to inject") + return cls.mock_broken_extract = Mock( inject=mock_inject("mock-broken-extract"), @@ -65,55 +65,53 @@ def mock_broken_inject(setter, carrier=None, context=None): ) def test_no_propagators(self): - CompositePropagator.propagators = [] + CompositeHTTPPropagator.propagators = [] new_carrier = {} - CompositePropagator.inject(dict.__setitem__, carrier=new_carrier) + CompositeHTTPPropagator.inject(dict.__setitem__, carrier=new_carrier) self.assertEqual(new_carrier, {}) - CompositePropagator.extract(get_as_list, carrier=new_carrier) + CompositeHTTPPropagator.extract(get_as_list, carrier=new_carrier) self.assertEqual(new_carrier, {}) def test_single_propagator(self): new_carrier = {} - CompositePropagator.propagators = [self.mock_propagator_0] - CompositePropagator.inject(dict.__setitem__, carrier=new_carrier) + CompositeHTTPPropagator.propagators = [self.mock_propagator_0] + CompositeHTTPPropagator.inject(dict.__setitem__, carrier=new_carrier) self.assertEqual(new_carrier, {"mock-0": "data"}) context = {} - CompositePropagator.extract( + CompositeHTTPPropagator.extract( get_as_list, carrier=new_carrier, context=context ) self.assertEqual(context, {"mock-0": "context"}) def test_multiple_propagators(self): - CompositePropagator.propagators = [ + CompositeHTTPPropagator.propagators = [ self.mock_propagator_0, self.mock_propagator_1, ] new_carrier = {} - CompositePropagator.inject(dict.__setitem__, carrier=new_carrier) + CompositeHTTPPropagator.inject(dict.__setitem__, carrier=new_carrier) self.assertEqual(new_carrier, {"mock-0": "data", "mock-1": "data"}) context = {} - CompositePropagator.extract( + CompositeHTTPPropagator.extract( get_as_list, carrier=new_carrier, context=context ) self.assertEqual(context, {"mock-0": "context", "mock-1": "context"}) def test_broken_propagator(self): - CompositePropagator.propagators = [ + CompositeHTTPPropagator.propagators = [ self.mock_broken_extract, self.mock_broken_inject, ] new_carrier = {} - with self.assertLogs(level=ERROR): - CompositePropagator.inject(dict.__setitem__, carrier=new_carrier) + CompositeHTTPPropagator.inject(dict.__setitem__, carrier=new_carrier) self.assertEqual(new_carrier, {"mock-broken-extract": "data"}) context = {} - with self.assertLogs(level=ERROR): - CompositePropagator.extract( - get_as_list, carrier=new_carrier, context=context - ) + CompositeHTTPPropagator.extract( + get_as_list, carrier=new_carrier, context=context + ) self.assertEqual(context, {"mock-broken-inject": "context"}) diff --git a/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py index 234601f26a9..6ee4a957d29 100644 --- a/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py @@ -187,9 +187,7 @@ def test_format_not_supported(self): def test_propagate_invalid_context(self): """Do not propagate invalid trace context.""" output = {} # type:typing.Dict[str, str] - ctx = set_span_in_context( - trace.INVALID_SPAN - ) + ctx = set_span_in_context(trace.INVALID_SPAN) FORMAT.inject(dict.__setitem__, output, context=ctx) self.assertFalse("traceparent" in output) From 6c21258605f68027d4a5932e78d119bc36990266 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Thu, 5 Mar 2020 09:43:25 -0800 Subject: [PATCH 10/15] change extract/inject from class methods --- .../tests/test_shim.py | 16 ++++---- .../opentelemetry/propagators/composite.py | 15 +++---- .../trace/propagation/httptextformat.py | 6 +-- .../propagation/tracecontexthttptextformat.py | 16 ++++---- .../tests/propagators/test_composite.py | 41 ++++++++----------- .../sdk/context/propagation/b3_format.py | 26 ++++++------ 6 files changed, 53 insertions(+), 67 deletions(-) diff --git a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py index 066b2981013..2a3fe819c9b 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -60,7 +60,7 @@ def setUpClass(cls): cls._previous_propagator = propagators.get_global_httptextformat() # Set mock propagator for testing. - propagators.set_global_httptextformat(MockHTTPTextFormat) + propagators.set_global_httptextformat(MockHTTPTextFormat()) @classmethod def tearDownClass(cls): @@ -552,15 +552,14 @@ class MockHTTPTextFormat(HTTPTextFormat): TRACE_ID_KEY = "mock-traceid" SPAN_ID_KEY = "mock-spanid" - @classmethod def extract( - cls, + self, get_from_carrier: Getter[HTTPTextFormatT], carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> Context: - trace_id_list = get_from_carrier(carrier, cls.TRACE_ID_KEY) - span_id_list = get_from_carrier(carrier, cls.SPAN_ID_KEY) + trace_id_list = get_from_carrier(carrier, self.TRACE_ID_KEY) + span_id_list = get_from_carrier(carrier, self.SPAN_ID_KEY) if not trace_id_list or not span_id_list: return set_span_in_context(trace.INVALID_SPAN) @@ -574,17 +573,16 @@ def extract( ) ) - @classmethod def inject( - cls, + self, set_in_carrier: Setter[HTTPTextFormatT], carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: span = get_span_from_context(context) set_in_carrier( - carrier, cls.TRACE_ID_KEY, str(span.get_context().trace_id) + carrier, self.TRACE_ID_KEY, str(span.get_context().trace_id) ) set_in_carrier( - carrier, cls.SPAN_ID_KEY, str(span.get_context().span_id) + carrier, self.SPAN_ID_KEY, str(span.get_context().span_id) ) diff --git a/opentelemetry-api/src/opentelemetry/propagators/composite.py b/opentelemetry-api/src/opentelemetry/propagators/composite.py index 5ec73cd0b13..30efc4716de 100644 --- a/opentelemetry-api/src/opentelemetry/propagators/composite.py +++ b/opentelemetry-api/src/opentelemetry/propagators/composite.py @@ -25,11 +25,13 @@ class CompositeHTTPPropagator(httptextformat.HTTPTextFormat): propagators into a single one. """ - propagators = [] # type: typing.List[httptextformat.HTTPTextFormat] + def __init__( + self, propagators: typing.List[httptextformat.HTTPTextFormat] + ) -> None: + self._propagators = propagators - @classmethod def extract( - cls, + self, get_from_carrier: httptextformat.Getter[ httptextformat.HTTPTextFormatT ], @@ -43,13 +45,12 @@ def extract( See `opentelemetry.trace.propagation.httptextformat.HTTPTextFormat.extract` """ - for propagator in cls.propagators: + for propagator in self._propagators: context = propagator.extract(get_from_carrier, carrier, context) return context # type: ignore - @classmethod def inject( - cls, + self, set_in_carrier: httptextformat.Setter[httptextformat.HTTPTextFormatT], carrier: httptextformat.HTTPTextFormatT, context: typing.Optional[Context] = None, @@ -61,5 +62,5 @@ def inject( See `opentelemetry.trace.propagation.httptextformat.HTTPTextFormat.inject` """ - for propagator in cls.propagators: + for propagator in self._propagators: propagator.inject(set_in_carrier, carrier, context) diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/trace/propagation/httptextformat.py index cad824a66e1..1b279c83089 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/httptextformat.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/httptextformat.py @@ -70,10 +70,9 @@ def example_route(): https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/api-propagators.md """ - @classmethod @abc.abstractmethod def extract( - cls, + self, get_from_carrier: Getter[HTTPTextFormatT], carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, @@ -97,10 +96,9 @@ def extract( """ - @classmethod @abc.abstractmethod def inject( - cls, + self, set_in_carrier: Setter[HTTPTextFormatT], carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py b/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py index 3ea0e52d999..e1ab087eb6e 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py @@ -62,9 +62,8 @@ class TraceContextHTTPTextFormat(httptextformat.HTTPTextFormat): ) _TRACEPARENT_HEADER_FORMAT_RE = re.compile(_TRACEPARENT_HEADER_FORMAT) - @classmethod def extract( - cls, + self, get_from_carrier: httptextformat.Getter[ httptextformat.HTTPTextFormatT ], @@ -73,12 +72,12 @@ def extract( ) -> Context: """Extracts a valid SpanContext from the carrier. """ - header = get_from_carrier(carrier, cls._TRACEPARENT_HEADER_NAME) + header = get_from_carrier(carrier, self._TRACEPARENT_HEADER_NAME) if not header: return set_span_in_context(trace.INVALID_SPAN, context) - match = re.search(cls._TRACEPARENT_HEADER_FORMAT_RE, header[0]) + match = re.search(self._TRACEPARENT_HEADER_FORMAT_RE, header[0]) if not match: return set_span_in_context(trace.INVALID_SPAN, context) @@ -97,7 +96,7 @@ def extract( return set_span_in_context(trace.INVALID_SPAN, context) tracestate_headers = get_from_carrier( - carrier, cls._TRACESTATE_HEADER_NAME + carrier, self._TRACESTATE_HEADER_NAME ) tracestate = _parse_tracestate(tracestate_headers) @@ -109,9 +108,8 @@ def extract( ) return set_span_in_context(trace.DefaultSpan(span_context), context) - @classmethod def inject( - cls, + self, set_in_carrier: httptextformat.Setter[httptextformat.HTTPTextFormatT], carrier: httptextformat.HTTPTextFormatT, context: typing.Optional[Context] = None, @@ -126,12 +124,12 @@ def inject( span_context.trace_flags, ) set_in_carrier( - carrier, cls._TRACEPARENT_HEADER_NAME, traceparent_string + carrier, self._TRACEPARENT_HEADER_NAME, traceparent_string ) if span_context.trace_state: tracestate_string = _format_tracestate(span_context.trace_state) set_in_carrier( - carrier, cls._TRACESTATE_HEADER_NAME, tracestate_string + carrier, self._TRACESTATE_HEADER_NAME, tracestate_string ) diff --git a/opentelemetry-api/tests/propagators/test_composite.py b/opentelemetry-api/tests/propagators/test_composite.py index 25a5fc10055..ee5e4aa5eda 100644 --- a/opentelemetry-api/tests/propagators/test_composite.py +++ b/opentelemetry-api/tests/propagators/test_composite.py @@ -65,53 +65,46 @@ def mock_broken_inject(setter, carrier=None, context=None): ) def test_no_propagators(self): - CompositeHTTPPropagator.propagators = [] + propagator = CompositeHTTPPropagator([]) new_carrier = {} - CompositeHTTPPropagator.inject(dict.__setitem__, carrier=new_carrier) + propagator.inject(dict.__setitem__, carrier=new_carrier) self.assertEqual(new_carrier, {}) - CompositeHTTPPropagator.extract(get_as_list, carrier=new_carrier) + propagator.extract(get_as_list, carrier=new_carrier) self.assertEqual(new_carrier, {}) def test_single_propagator(self): + propagator = CompositeHTTPPropagator([self.mock_propagator_0]) + new_carrier = {} - CompositeHTTPPropagator.propagators = [self.mock_propagator_0] - CompositeHTTPPropagator.inject(dict.__setitem__, carrier=new_carrier) + propagator.inject(dict.__setitem__, carrier=new_carrier) self.assertEqual(new_carrier, {"mock-0": "data"}) context = {} - CompositeHTTPPropagator.extract( - get_as_list, carrier=new_carrier, context=context - ) + propagator.extract(get_as_list, carrier=new_carrier, context=context) self.assertEqual(context, {"mock-0": "context"}) def test_multiple_propagators(self): - CompositeHTTPPropagator.propagators = [ - self.mock_propagator_0, - self.mock_propagator_1, - ] + propagator = CompositeHTTPPropagator( + [self.mock_propagator_0, self.mock_propagator_1] + ) new_carrier = {} - CompositeHTTPPropagator.inject(dict.__setitem__, carrier=new_carrier) + propagator.inject(dict.__setitem__, carrier=new_carrier) self.assertEqual(new_carrier, {"mock-0": "data", "mock-1": "data"}) context = {} - CompositeHTTPPropagator.extract( - get_as_list, carrier=new_carrier, context=context - ) + propagator.extract(get_as_list, carrier=new_carrier, context=context) self.assertEqual(context, {"mock-0": "context", "mock-1": "context"}) def test_broken_propagator(self): - CompositeHTTPPropagator.propagators = [ - self.mock_broken_extract, - self.mock_broken_inject, - ] + propagator = CompositeHTTPPropagator( + [self.mock_broken_extract, self.mock_broken_inject] + ) new_carrier = {} - CompositeHTTPPropagator.inject(dict.__setitem__, carrier=new_carrier) + propagator.inject(dict.__setitem__, carrier=new_carrier) self.assertEqual(new_carrier, {"mock-broken-extract": "data"}) context = {} - CompositeHTTPPropagator.extract( - get_as_list, carrier=new_carrier, context=context - ) + propagator.extract(get_as_list, carrier=new_carrier, context=context) self.assertEqual(context, {"mock-broken-inject": "context"}) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py index 47c81cc4fdc..3e03c9aa020 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py @@ -42,9 +42,8 @@ class B3Format(HTTPTextFormat): FLAGS_KEY = "x-b3-flags" _SAMPLE_PROPAGATE_VALUES = set(["1", "True", "true", "d"]) - @classmethod def extract( - cls, + self, get_from_carrier: Getter[HTTPTextFormatT], carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, @@ -55,7 +54,7 @@ def extract( flags = None single_header = _extract_first_element( - get_from_carrier(carrier, cls.SINGLE_HEADER_KEY) + get_from_carrier(carrier, self.SINGLE_HEADER_KEY) ) if single_header: # The b3 spec calls for the sampling state to be @@ -77,25 +76,25 @@ def extract( else: trace_id = ( _extract_first_element( - get_from_carrier(carrier, cls.TRACE_ID_KEY) + get_from_carrier(carrier, self.TRACE_ID_KEY) ) or trace_id ) span_id = ( _extract_first_element( - get_from_carrier(carrier, cls.SPAN_ID_KEY) + get_from_carrier(carrier, self.SPAN_ID_KEY) ) or span_id ) sampled = ( _extract_first_element( - get_from_carrier(carrier, cls.SAMPLED_KEY) + get_from_carrier(carrier, self.SAMPLED_KEY) ) or sampled ) flags = ( _extract_first_element( - get_from_carrier(carrier, cls.FLAGS_KEY) + get_from_carrier(carrier, self.FLAGS_KEY) ) or flags ) @@ -105,7 +104,7 @@ def extract( # flag values set. Since the setting of at least one implies # the desire for some form of sampling, propagate if either # header is set to allow. - if sampled in cls._SAMPLE_PROPAGATE_VALUES or flags == "1": + if sampled in self._SAMPLE_PROPAGATE_VALUES or flags == "1": options |= trace.TraceFlags.SAMPLED return set_span_in_context( trace.DefaultSpan( @@ -119,9 +118,8 @@ def extract( ) ) - @classmethod def inject( - cls, + self, set_in_carrier: Setter[HTTPTextFormatT], carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, @@ -129,18 +127,18 @@ def inject( span = get_span_from_context(context=context) sampled = (trace.TraceFlags.SAMPLED & span.context.trace_flags) != 0 set_in_carrier( - carrier, cls.TRACE_ID_KEY, format_trace_id(span.context.trace_id), + carrier, self.TRACE_ID_KEY, format_trace_id(span.context.trace_id), ) set_in_carrier( - carrier, cls.SPAN_ID_KEY, format_span_id(span.context.span_id) + carrier, self.SPAN_ID_KEY, format_span_id(span.context.span_id) ) if span.parent is not None: set_in_carrier( carrier, - cls.PARENT_SPAN_ID_KEY, + self.PARENT_SPAN_ID_KEY, format_span_id(span.parent.context.span_id), ) - set_in_carrier(carrier, cls.SAMPLED_KEY, "1" if sampled else "0") + set_in_carrier(carrier, self.SAMPLED_KEY, "1" if sampled else "0") def format_trace_id(trace_id: int) -> str: From a5ca36f7aeb19ec939298e17180084ffdb7d8218 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Thu, 5 Mar 2020 10:01:41 -0800 Subject: [PATCH 11/15] fix test to use return context --- .../tests/propagators/test_composite.py | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/opentelemetry-api/tests/propagators/test_composite.py b/opentelemetry-api/tests/propagators/test_composite.py index ee5e4aa5eda..4f5d9875f39 100644 --- a/opentelemetry-api/tests/propagators/test_composite.py +++ b/opentelemetry-api/tests/propagators/test_composite.py @@ -69,8 +69,11 @@ def test_no_propagators(self): new_carrier = {} propagator.inject(dict.__setitem__, carrier=new_carrier) self.assertEqual(new_carrier, {}) - propagator.extract(get_as_list, carrier=new_carrier) - self.assertEqual(new_carrier, {}) + + context = propagator.extract( + get_as_list, carrier=new_carrier, context={} + ) + self.assertEqual(context, {}) def test_single_propagator(self): propagator = CompositeHTTPPropagator([self.mock_propagator_0]) @@ -79,8 +82,9 @@ def test_single_propagator(self): propagator.inject(dict.__setitem__, carrier=new_carrier) self.assertEqual(new_carrier, {"mock-0": "data"}) - context = {} - propagator.extract(get_as_list, carrier=new_carrier, context=context) + context = propagator.extract( + get_as_list, carrier=new_carrier, context={} + ) self.assertEqual(context, {"mock-0": "context"}) def test_multiple_propagators(self): @@ -92,8 +96,9 @@ def test_multiple_propagators(self): propagator.inject(dict.__setitem__, carrier=new_carrier) self.assertEqual(new_carrier, {"mock-0": "data", "mock-1": "data"}) - context = {} - propagator.extract(get_as_list, carrier=new_carrier, context=context) + context = propagator.extract( + get_as_list, carrier=new_carrier, context={} + ) self.assertEqual(context, {"mock-0": "context", "mock-1": "context"}) def test_broken_propagator(self): @@ -105,6 +110,7 @@ def test_broken_propagator(self): propagator.inject(dict.__setitem__, carrier=new_carrier) self.assertEqual(new_carrier, {"mock-broken-extract": "data"}) - context = {} - propagator.extract(get_as_list, carrier=new_carrier, context=context) + context = propagator.extract( + get_as_list, carrier=new_carrier, context={} + ) self.assertEqual(context, {"mock-broken-inject": "context"}) From 88695211084aa25d886d06f6d9ea5cb721001a47 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Thu, 5 Mar 2020 10:07:51 -0800 Subject: [PATCH 12/15] remove unnecessary call to set_span_in_context --- .../src/opentelemetry/ext/http_requests/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ext/opentelemetry-ext-http-requests/src/opentelemetry/ext/http_requests/__init__.py b/ext/opentelemetry-ext-http-requests/src/opentelemetry/ext/http_requests/__init__.py index f577c634119..8e4b3e2cc06 100644 --- a/ext/opentelemetry-ext-http-requests/src/opentelemetry/ext/http_requests/__init__.py +++ b/ext/opentelemetry-ext-http-requests/src/opentelemetry/ext/http_requests/__init__.py @@ -25,7 +25,6 @@ from opentelemetry import context, propagators from opentelemetry.ext.http_requests.version import __version__ from opentelemetry.trace import SpanKind -from opentelemetry.trace.propagation import set_span_in_context # NOTE: Currently we force passing a tracer. But in turn, this forces the user @@ -77,8 +76,7 @@ def instrumented_request(self, method, url, *args, **kwargs): # to access propagators. headers = kwargs.setdefault("headers", {}) - ctx = set_span_in_context(span) - propagators.inject(type(headers).__setitem__, headers, ctx) + propagators.inject(type(headers).__setitem__, headers) result = wrapped(self, method, url, *args, **kwargs) # *** PROCEED span.set_attribute("http.status_code", result.status_code) From a94ee8b07f223fd12591a334cfa8056adbec6258 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Thu, 5 Mar 2020 12:23:59 -0800 Subject: [PATCH 13/15] removing unnecessary test, updating mock extract --- .../tests/propagators/test_composite.py | 34 ++----------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/opentelemetry-api/tests/propagators/test_composite.py b/opentelemetry-api/tests/propagators/test_composite.py index 4f5d9875f39..d2d9ad2ba31 100644 --- a/opentelemetry-api/tests/propagators/test_composite.py +++ b/opentelemetry-api/tests/propagators/test_composite.py @@ -33,8 +33,9 @@ def wrapped(setter, carrier=None, context=None): def mock_extract(name): def wrapped(getter, carrier=None, context=None): - context[name] = "context" - return context + new_context = context.copy() + new_context[name] = "context" + return new_context return wrapped @@ -49,21 +50,6 @@ def setUpClass(cls): inject=mock_inject("mock-1"), extract=mock_extract("mock-1") ) - def mock_broken_extract(getter, carrier=None, context=None): - return context - - def mock_broken_inject(setter, carrier=None, context=None): - return - - cls.mock_broken_extract = Mock( - inject=mock_inject("mock-broken-extract"), - extract=mock_broken_extract, - ) - cls.mock_broken_inject = Mock( - inject=mock_broken_inject, - extract=mock_extract("mock-broken-inject"), - ) - def test_no_propagators(self): propagator = CompositeHTTPPropagator([]) new_carrier = {} @@ -100,17 +86,3 @@ def test_multiple_propagators(self): get_as_list, carrier=new_carrier, context={} ) self.assertEqual(context, {"mock-0": "context", "mock-1": "context"}) - - def test_broken_propagator(self): - propagator = CompositeHTTPPropagator( - [self.mock_broken_extract, self.mock_broken_inject] - ) - - new_carrier = {} - propagator.inject(dict.__setitem__, carrier=new_carrier) - self.assertEqual(new_carrier, {"mock-broken-extract": "data"}) - - context = propagator.extract( - get_as_list, carrier=new_carrier, context={} - ) - self.assertEqual(context, {"mock-broken-inject": "context"}) From 154ab5b3a7c9c2f661f0c9d9341da5f56336fc98 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Thu, 5 Mar 2020 13:07:14 -0800 Subject: [PATCH 14/15] clean up docs --- .../src/opentelemetry/propagators/__init__.py | 60 +++++++++++++++---- .../opentelemetry/propagators/composite.py | 5 +- .../trace/propagation/httptextformat.py | 57 ++++-------------- .../propagation/tracecontexthttptextformat.py | 8 ++- 4 files changed, 70 insertions(+), 60 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/propagators/__init__.py b/opentelemetry-api/src/opentelemetry/propagators/__init__.py index 1b1e1ec27a9..f9b537cd866 100644 --- a/opentelemetry-api/src/opentelemetry/propagators/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagators/__init__.py @@ -12,6 +12,47 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +API for propagation of context. + +Example:: + + import flask + import requests + from opentelemetry import propagators + + + PROPAGATOR = propagators.get_global_httptextformat() + + + def get_header_from_flask_request(request, key): + return request.headers.get_all(key) + + def set_header_into_requests_request(request: requests.Request, + key: str, value: str): + request.headers[key] = value + + def example_route(): + context = PROPAGATOR.extract( + get_header_from_flask_request, + flask.request + ) + request_to_downstream = requests.Request( + "GET", "http://httpbin.org/get" + ) + PROPAGATOR.inject( + set_header_into_requests_request, + request_to_downstream, + context=context + ) + session = requests.Session() + session.send(request_to_downstream.prepare()) + + +.. _Propagation API Specification: + https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/api-propagators.md +""" + import typing import opentelemetry.trace as trace @@ -28,20 +69,18 @@ def extract( carrier: httptextformat.HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> Context: - """Load the parent SpanContext from values in the carrier. - - Using the specified HTTPTextFormatter, the propagator will - extract a SpanContext from the carrier. If one is found, - it will be set as the parent context of the current span. + """ Uses the configured propagator to extract a Context from the carrier. Args: get_from_carrier: a function that can retrieve zero or more values from the carrier. In the case that the value does not exist, return an empty list. carrier: and object which contains values that are - used to construct a SpanContext. This object + used to construct a Context. This object must be paired with an appropriate get_from_carrier which understands how to extract a value from it. + context: an optional Context to use. Defaults to current + context if not set. """ return get_global_httptextformat().extract( get_from_carrier, carrier, context @@ -53,12 +92,7 @@ def inject( carrier: httptextformat.HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: - """Inject values from the current context into the carrier. - - inject enables the propagation of values into HTTP clients or - other objects which perform an HTTP request. Implementations - should use the set_in_carrier method to set values on the - carrier. + """ Uses the configured propagator to inject a Context into the carrier. Args: set_in_carrier: A setter function that can set values @@ -66,6 +100,8 @@ def inject( carrier: An object that contains a representation of HTTP headers. Should be paired with set_in_carrier, which should know how to set header values on the carrier. + context: an optional Context to use. Defaults to current + context if not set. """ get_global_httptextformat().inject(set_in_carrier, carrier, context) diff --git a/opentelemetry-api/src/opentelemetry/propagators/composite.py b/opentelemetry-api/src/opentelemetry/propagators/composite.py index 30efc4716de..4ec953c8395 100644 --- a/opentelemetry-api/src/opentelemetry/propagators/composite.py +++ b/opentelemetry-api/src/opentelemetry/propagators/composite.py @@ -23,10 +23,13 @@ class CompositeHTTPPropagator(httptextformat.HTTPTextFormat): """ CompositeHTTPPropagator provides a mechanism for combining multiple propagators into a single one. + + Args: + propagators: the list of propagators to use """ def __init__( - self, propagators: typing.List[httptextformat.HTTPTextFormat] + self, propagators: typing.Sequence[httptextformat.HTTPTextFormat] ) -> None: self._propagators = propagators diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/trace/propagation/httptextformat.py index 1b279c83089..500014d7384 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/httptextformat.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/httptextformat.py @@ -24,50 +24,12 @@ class HTTPTextFormat(abc.ABC): - """API for propagation of span context via headers. - - This class provides an interface that enables extracting and injecting - span context into headers of HTTP requests. HTTP frameworks and clients + """This class provides an interface that enables extracting and injecting + context into headers of HTTP requests. HTTP frameworks and clients can integrate with HTTPTextFormat by providing the object containing the headers, and a getter and setter function for the extraction and injection of values, respectively. - Example:: - - import flask - import requests - from opentelemetry.context.propagation import HTTPTextFormat - - PROPAGATOR = HTTPTextFormat() - - - - def get_header_from_flask_request(request, key): - return request.headers.get_all(key) - - def set_header_into_requests_request(request: requests.Request, - key: str, value: str): - request.headers[key] = value - - def example_route(): - span_context = PROPAGATOR.extract( - get_header_from_flask_request, - flask.request - ) - request_to_downstream = requests.Request( - "GET", "http://httpbin.org/get" - ) - PROPAGATOR.inject( - span_context, - set_header_into_requests_request, - request_to_downstream - ) - session = requests.Session() - session.send(request_to_downstream.prepare()) - - - .. _Propagation API Specification: - https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/api-propagators.md """ @abc.abstractmethod @@ -77,22 +39,24 @@ def extract( carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> Context: - """Create a SpanContext from values in the carrier. + """Create a Context from values in the carrier. The extract function should retrieve values from the carrier object using get_from_carrier, and use values to populate a - SpanContext value and return it. + Context value and return it. Args: get_from_carrier: a function that can retrieve zero or more values from the carrier. In the case that the value does not exist, return an empty list. carrier: and object which contains values that are - used to construct a SpanContext. This object + used to construct a Context. This object must be paired with an appropriate get_from_carrier which understands how to extract a value from it. + context: an optional Context to use. Defaults to current + context if not set. Returns: - A SpanContext with configuration found in the carrier. + A Context with configuration found in the carrier. """ @@ -103,7 +67,7 @@ def inject( carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: - """Inject values from a Span into a carrier. + """Inject values from a Context into a carrier. inject enables the propagation of values into HTTP clients or other objects which perform an HTTP request. Implementations @@ -111,11 +75,12 @@ def inject( carrier. Args: - context: The SpanContext to read values from. set_in_carrier: A setter function that can set values on the carrier. carrier: An object that a place to define HTTP headers. Should be paired with set_in_carrier, which should know how to set header values on the carrier. + context: an optional Context to use. Defaults to current + context if not set. """ diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py b/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py index e1ab087eb6e..28db4e45575 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py @@ -70,7 +70,9 @@ def extract( carrier: httptextformat.HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> Context: - """Extracts a valid SpanContext from the carrier. + """Extracts SpanContext from the carrier. + + See `opentelemetry.trace.propagation.httptextformat.HTTPTextFormat.extract` """ header = get_from_carrier(carrier, self._TRACEPARENT_HEADER_NAME) @@ -114,6 +116,10 @@ def inject( carrier: httptextformat.HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: + """Injects SpanContext into the carrier. + + See `opentelemetry.trace.propagation.httptextformat.HTTPTextFormat.inject` + """ span_context = get_span_from_context(context).get_context() if span_context == trace.INVALID_SPAN_CONTEXT: From 90c0535edf2fbfd91f7938c2ee644cdecf192eda Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Fri, 6 Mar 2020 08:17:46 -0800 Subject: [PATCH 15/15] fix unused import, adding one more test --- .../tests/propagators/test_composite.py | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/opentelemetry-api/tests/propagators/test_composite.py b/opentelemetry-api/tests/propagators/test_composite.py index d2d9ad2ba31..09ac0ecf689 100644 --- a/opentelemetry-api/tests/propagators/test_composite.py +++ b/opentelemetry-api/tests/propagators/test_composite.py @@ -13,7 +13,6 @@ # limitations under the License. import unittest -from logging import ERROR from unittest.mock import Mock from opentelemetry.propagators.composite import CompositeHTTPPropagator @@ -24,17 +23,17 @@ def get_as_list(dict_object, key): return [value] if value is not None else [] -def mock_inject(name): +def mock_inject(name, value="data"): def wrapped(setter, carrier=None, context=None): - carrier[name] = "data" + carrier[name] = value return wrapped -def mock_extract(name): +def mock_extract(name, value="context"): def wrapped(getter, carrier=None, context=None): new_context = context.copy() - new_context[name] = "context" + new_context[name] = value return new_context return wrapped @@ -49,6 +48,10 @@ def setUpClass(cls): cls.mock_propagator_1 = Mock( inject=mock_inject("mock-1"), extract=mock_extract("mock-1") ) + cls.mock_propagator_2 = Mock( + inject=mock_inject("mock-0", value="data2"), + extract=mock_extract("mock-0", value="context2"), + ) def test_no_propagators(self): propagator = CompositeHTTPPropagator([]) @@ -86,3 +89,19 @@ def test_multiple_propagators(self): get_as_list, carrier=new_carrier, context={} ) self.assertEqual(context, {"mock-0": "context", "mock-1": "context"}) + + def test_multiple_propagators_same_key(self): + # test that when multiple propagators extract/inject the same + # key, the later propagator values are extracted/injected + propagator = CompositeHTTPPropagator( + [self.mock_propagator_0, self.mock_propagator_2] + ) + + new_carrier = {} + propagator.inject(dict.__setitem__, carrier=new_carrier) + self.assertEqual(new_carrier, {"mock-0": "data2"}) + + context = propagator.extract( + get_as_list, carrier=new_carrier, context={} + ) + self.assertEqual(context, {"mock-0": "context2"})