From 5f84c2babbc2656e5a58d4d82f73838166e7c4ff Mon Sep 17 00:00:00 2001 From: Yusuke Tsutsumi Date: Thu, 7 Nov 2019 15:21:46 -0800 Subject: [PATCH 01/71] stopgap PR to start the discussion on context propagation. --- .../context_propagation_example.py | 81 +++++++++++++++++++ .../src/opentelemetry/baggage/__init__.py | 2 + .../context/propagation/httptextformat.py | 8 +- .../distributedcontext/__init__.py | 56 ++++++------- .../src/opentelemetry/propagators/__init__.py | 26 +++--- 5 files changed, 126 insertions(+), 47 deletions(-) create mode 100644 examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py create mode 100644 opentelemetry-api/src/opentelemetry/baggage/__init__.py diff --git a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py new file mode 100644 index 0000000000..fe87626f7d --- /dev/null +++ b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py @@ -0,0 +1,81 @@ +# 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. +# +""" +This module serves as an example for baggage, which exists +to pass application-defined key-value pairs from service to service. +""" +import flask +import requests + +import opentelemetry.ext.http_requests +from opentelemetry import propagators, trace +from opentelemetry import baggage +from opnetelemetry.context import Context +from opentelemetry.ext.wsgi import OpenTelemetryMiddleware +from opentelemetry.sdk.context.propagation.b3_format import B3Format +from opentelemetry.sdk.trace import Tracer + + +def configure_opentelemetry(flask_app: flask.Flask): + """Configure a flask application to use OpenTelemetry. + + This activates the specific components: + + * sets tracer to the SDK's Tracer + * enables requests integration on the Tracer + * uses a WSGI middleware to enable configuration + + TODO: + + * processors? + * exporters? + """ + # Start by configuring all objects required to ensure + # a complete end to end workflow. + # the preferred implementation of these objects must be set, + # as the opentelemetry-api defines the interface with a no-op + # implementation. + trace.set_preferred_tracer_implementation(lambda _: Tracer()) + # extractors and injectors are now separate, as it could be possible + # to want different behavior for those (e.g. don't propagate because of external services) + # + # the current configuration will only propagate w3c/correlationcontext + # and baggage. One would have to add other propagators to handle + # things such as w3c/tracecontext + propagator_list = [CorrelationContextFormat(), BaggageFormat()] + + propagators.set_http_extractors(propagator_list) + propagators.set_http_injectors(propagator_list) + + # Integrations are the glue that binds the OpenTelemetry API + # and the frameworks and libraries that are used together, automatically + # creating Spans and propagating context as appropriate. + opentelemetry.ext.http_requests.enable(trace.tracer()) + flask_app.wsgi_app = OpenTelemetryMiddleware(flask_app.wsgi_app) + + +app = flask.Flask(__name__) + + +@app.route("/") +def hello(): + # extract a baggage header + original_service = baggage.get(Context, "original-service") + # add a new one + baggage.set(Context, "environment", "foo") + return "hello" + + +configure_opentelemetry(app) diff --git a/opentelemetry-api/src/opentelemetry/baggage/__init__.py b/opentelemetry-api/src/opentelemetry/baggage/__init__.py new file mode 100644 index 0000000000..96623a8aca --- /dev/null +++ b/opentelemetry-api/src/opentelemetry/baggage/__init__.py @@ -0,0 +1,2 @@ +class Baggage: + """""" diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py index 9b6098a9a4..b90b4c9eba 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py @@ -16,6 +16,7 @@ import typing from opentelemetry.trace import SpanContext +from opentelemetry.context import BaseRuntimeContext _T = typing.TypeVar("_T") @@ -72,7 +73,7 @@ def example_route(): @abc.abstractmethod def extract( - self, get_from_carrier: Getter[_T], carrier: _T + context: BaseRuntimeContext, get_from_carrier: Getter[_T], carrier: _T ) -> SpanContext: """Create a SpanContext from values in the carrier. @@ -95,7 +96,10 @@ def extract( @abc.abstractmethod def inject( - self, context: SpanContext, set_in_carrier: Setter[_T], carrier: _T + self, + context: BaseRuntimeContext, + set_in_carrier: Setter[_T], + carrier: _T, ) -> None: """Inject values from a SpanContext into a carrier. diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index 38ef3739b9..80001d2288 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -15,7 +15,7 @@ import itertools import string import typing -from contextlib import contextmanager +from opentelemetry.context import BaseRuntimeContext PRINTABLE = frozenset( itertools.chain( @@ -81,45 +81,35 @@ def __init__( class DistributedContext: """A container for distributed context entries""" + KEY = "DistributedContext" + def __init__(self, entries: typing.Iterable[Entry]) -> None: self._container = {entry.key: entry for entry in entries} - def get_entries(self) -> typing.Iterable[Entry]: + @classmethod + def set_value( + cls, context: BaseRuntimeContext, entry_list: typing.Iterable[Entry] + ): + distributed_context = getattr(context, cls.KEY, {}) + for entry in entry_list: + distributed_context[entry.key] = entry + + @classmethod + def get_entries( + cls, context: BaseRuntimeContext + ) -> typing.Iterable[Entry]: """Returns an immutable iterator to entries.""" - return self._container.values() + return getattr(context, cls.KEY, {}).values() - def get_entry_value(self, key: EntryKey) -> typing.Optional[EntryValue]: + @classmethod + def get_entry_value( + cls, context: BaseRuntimeContext, key: EntryKey + ) -> typing.Optional[EntryValue]: """Returns the entry associated with a key or None Args: key: the key with which to perform a lookup """ - if key in self._container: - return self._container[key].value - return None - - -class DistributedContextManager: - def get_current_context(self) -> typing.Optional[DistributedContext]: - """Gets the current DistributedContext. - - Returns: - A DistributedContext instance representing the current context. - """ - - @contextmanager # type: ignore - def use_context( - self, context: DistributedContext - ) -> typing.Iterator[DistributedContext]: - """Context manager for controlling a DistributedContext lifetime. - - Set the context as the active DistributedContext. - - On exiting, the context manager will restore the parent - DistributedContext. - - Args: - context: A DistributedContext instance to make current. - """ - # pylint: disable=no-self-use - yield context + container = getattr(context, cls.KEY, {}) + if key in container: + return container[key].value diff --git a/opentelemetry-api/src/opentelemetry/propagators/__init__.py b/opentelemetry-api/src/opentelemetry/propagators/__init__.py index bb75d84c3a..190c77dc98 100644 --- a/opentelemetry-api/src/opentelemetry/propagators/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagators/__init__.py @@ -19,13 +19,14 @@ from opentelemetry.context.propagation.tracecontexthttptextformat import ( TraceContextHTTPTextFormat, ) +from opentelemetry.context import BaseRuntimeContext _T = typing.TypeVar("_T") def extract( get_from_carrier: httptextformat.Getter[_T], carrier: _T -) -> trace.SpanContext: +) -> BaseRuntimeContext: """Load the parent SpanContext from values in the carrier. Using the specified HTTPTextFormatter, the propagator will @@ -45,7 +46,7 @@ def extract( def inject( - tracer: trace.Tracer, + context: BaseRuntimeContext set_in_carrier: httptextformat.Setter[_T], carrier: _T, ) -> None: @@ -67,18 +68,19 @@ def inject( tracer.get_current_span().get_context(), set_in_carrier, carrier ) - -_HTTP_TEXT_FORMAT = ( +_HTTP_TEXT_INJECTORS = [ TraceContextHTTPTextFormat() -) # type: httptextformat.HTTPTextFormat +] # typing.List[httptextformat.HTTPTextFormat] +_HTTP_TEXT_EXTRACTORS = [ + TraceContextHTTPTextFormat() +] # typing.List[httptextformat.HTTPTextFormat] -def get_global_httptextformat() -> httptextformat.HTTPTextFormat: - return _HTTP_TEXT_FORMAT +def set_http_extractors(extractor_list: typing.List[httptextformat.HTTPTextFormat]) -> None: + global _HTTP_TEXT_EXTRACTORS # pylint:disable=global-statement + _HTTP_TEXT_EXTRACTORS = extractor_list -def set_global_httptextformat( - http_text_format: httptextformat.HTTPTextFormat, -) -> None: - global _HTTP_TEXT_FORMAT # pylint:disable=global-statement - _HTTP_TEXT_FORMAT = http_text_format +def set_http_injectors(extractor_list: typing.List[httptextformat.HTTPTextFormat]) -> None: + global _HTTP_TEXT_INJECTORS # pylint:disable=global-statement + _HTTP_TEXT_INJECTORS = injector_list \ No newline at end of file From bbb583d92d9a7eaef634cbe845276dab5211638f Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 10 Dec 2019 09:49:47 -0800 Subject: [PATCH 02/71] adding Baggage API Signed-off-by: Alex Boten --- .../src/opentelemetry/baggage/__init__.py | 78 ++++++++++++++++++- .../baggage/propagation/__init__.py | 47 +++++++++++ 2 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py diff --git a/opentelemetry-api/src/opentelemetry/baggage/__init__.py b/opentelemetry-api/src/opentelemetry/baggage/__init__.py index 96623a8aca..88065ec386 100644 --- a/opentelemetry-api/src/opentelemetry/baggage/__init__.py +++ b/opentelemetry-api/src/opentelemetry/baggage/__init__.py @@ -1,2 +1,76 @@ -class Baggage: - """""" +# 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 opentelemetry.context import Context +from opentelemetry.context.propagation import ( + HTTPExtractor, + HTTPInjector, + INVALID_HTTP_EXTRACTOR, + INVALID_HTTP_INJECTOR, +) + + +EMPTY_VALUE = "" +INVALID_CONTEXT = Context() + + +class BaggageManager: + """ TODO """ + + def set_value(self, ctx: Context, key: str, value: str) -> Context: + """ + Sets a value on a Context + + Args: + ctx: Context to modify + key: Key of the value to set + value: Value used to update the context + + Return: + Context: Updated context + """ + # pylint: disable=unused-argument + return ctx + + def value(self, ctx: Context, key: str) -> str: + """ + Gets a value on a Context + + Args: + ctx: Context to query + key: Key of the value to get + + Return: + str: Value of the key + """ + # pylint: disable=unused-argument + return EMPTY_VALUE + + def remove_value(self, ctx: Context, key: str) -> Context: + """ TODO """ + # pylint: disable=unused-argument + return INVALID_CONTEXT + + def clear(self, ctx: Context) -> Context: + """ TODO """ + # pylint: disable=unused-argument + return INVALID_CONTEXT + + def http_injector(self) -> HTTPInjector: + """ TODO """ + return INVALID_HTTP_INJECTOR + + def http_extractor(self) -> HTTPExtractor: + """ TODO """ + return INVALID_HTTP_EXTRACTOR diff --git a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py new file mode 100644 index 0000000000..28fe583ce9 --- /dev/null +++ b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py @@ -0,0 +1,47 @@ +# 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 opentelemetry.context import Context +from opentelemetry.context.propagation import Carrier, Getter, Setter + + +class ContextKeys: + """ TODO """ + + @classmethod + def span_context_key(cls): + """ TODO """ + return "baggage" + + +class BaggageExtractor: + """ TODO """ + + def extract( + self, ctx: Context, carrier: Carrier, getter: Getter + ) -> Context: + """ TODO """ + # pylint: disable=unused-argument + return ctx + + +class BaggageInjector: + """ TODO """ + + def inject( + self, ctx: Context, carrier: Carrier, setter: Setter + ) -> Context: + """ TODO """ + # pylint: disable=unused-argument + return ctx From c3f408b980157d02492551dd6a4094f09f72adf0 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 10 Dec 2019 10:01:52 -0800 Subject: [PATCH 03/71] no-op implementations of baggage injector/extractor Signed-off-by: Alex Boten --- .../opentelemetry/baggage/propagation/__init__.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py index 28fe583ce9..b404becad9 100644 --- a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py @@ -45,3 +45,17 @@ def inject( """ TODO """ # pylint: disable=unused-argument return ctx + + +class DefaultBaggageExtractor(BaggageExtractor): + """The default BaggageExtractor. + + Used when no BaggageExtractor implementation is available. + """ + + +class DefaultBaggageInjector(BaggageInjector): + """The default BaggageInjector. + + Used when no BaggageInjector implementation is available. + """ From 8ec6106561d1013d4c5f57435e2cc569b203742a Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 10 Dec 2019 19:55:10 -0800 Subject: [PATCH 04/71] cleaning up baggage prop interface Signed-off-by: Alex Boten --- .../baggage/propagation/__init__.py | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py index b404becad9..9f76f36107 100644 --- a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py @@ -19,14 +19,19 @@ class ContextKeys: """ TODO """ + KEY = "baggage" + @classmethod def span_context_key(cls): """ TODO """ - return "baggage" + return cls.KEY -class BaggageExtractor: - """ TODO """ +class HTTPExtractor: + """The default HTTPExtractor. + + Used when no HTTPExtractor implementation is available. + """ def extract( self, ctx: Context, carrier: Carrier, getter: Getter @@ -36,8 +41,11 @@ def extract( return ctx -class BaggageInjector: - """ TODO """ +class HTTPInjector: + """The default HTTPInjector. + + Used when no HTTPInjector implementation is available. + """ def inject( self, ctx: Context, carrier: Carrier, setter: Setter @@ -45,17 +53,3 @@ def inject( """ TODO """ # pylint: disable=unused-argument return ctx - - -class DefaultBaggageExtractor(BaggageExtractor): - """The default BaggageExtractor. - - Used when no BaggageExtractor implementation is available. - """ - - -class DefaultBaggageInjector(BaggageInjector): - """The default BaggageInjector. - - Used when no BaggageInjector implementation is available. - """ From 9788f6912b04daa296563833a05c733e8e9416f4 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 10 Dec 2019 22:31:54 -0800 Subject: [PATCH 05/71] make extract/inject static Signed-off-by: Alex Boten --- .../src/opentelemetry/baggage/propagation/__init__.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py index 9f76f36107..1494939100 100644 --- a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py @@ -33,9 +33,8 @@ class HTTPExtractor: Used when no HTTPExtractor implementation is available. """ - def extract( - self, ctx: Context, carrier: Carrier, getter: Getter - ) -> Context: + @staticmethod + def extract(ctx: Context, carrier: Carrier, getter: Getter) -> Context: """ TODO """ # pylint: disable=unused-argument return ctx @@ -47,9 +46,8 @@ class HTTPInjector: Used when no HTTPInjector implementation is available. """ - def inject( - self, ctx: Context, carrier: Carrier, setter: Setter - ) -> Context: + @staticmethod + def inject(ctx: Context, carrier: Carrier, setter: Setter) -> Context: """ TODO """ # pylint: disable=unused-argument return ctx From 6a46a2538f6e2062a42fdfaa8ecf8b084f468554 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 10 Dec 2019 23:12:19 -0800 Subject: [PATCH 06/71] rename DistributedContext to CorrelationContext Signed-off-by: Alex Boten --- .../distributedcontext/__init__.py | 23 +++++++++++---- .../propagation/binaryformat.py | 22 +++++++-------- .../propagation/httptextformat.py | 18 ++++++------ .../test_distributed_context.py | 27 +++++++++++------- .../sdk/distributedcontext/__init__.py | 28 +++++++++---------- .../test_distributed_context.py | 16 +++++------ 6 files changed, 76 insertions(+), 58 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index 80001d2288..0fc5721fb8 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -25,7 +25,7 @@ class EntryMetadata: - """A class representing metadata of a DistributedContext entry + """A class representing metadata of a CorrelationContext entry Args: entry_ttl: The time to live (in service hops) of an entry. Must be @@ -41,7 +41,7 @@ def __init__(self, entry_ttl: int) -> None: class EntryKey(str): - """A class representing a key for a DistributedContext entry""" + """A class representing a key for a CorrelationContext entry""" def __new__(cls, value: str) -> "EntryKey": return cls.create(value) @@ -56,7 +56,7 @@ def create(value: str) -> "EntryKey": class EntryValue(str): - """A class representing the value of a DistributedContext entry""" + """A class representing the value of a CorrelationContext entry""" def __new__(cls, value: str) -> "EntryValue": return cls.create(value) @@ -78,10 +78,10 @@ def __init__( self.value = value -class DistributedContext: +class CorrelationContext: """A container for distributed context entries""" - KEY = "DistributedContext" + KEY = "CorrelationContext" def __init__(self, entries: typing.Iterable[Entry]) -> None: self._container = {entry.key: entry for entry in entries} @@ -113,3 +113,16 @@ def get_entry_value( container = getattr(context, cls.KEY, {}) if key in container: return container[key].value + + +class CorrelationContextManager: + def current_context(self) -> typing.Optional[CorrelationContext]: + """ TODO """ + + def http_text_format(self): + """ TODO """ + + def use_context( + self, ctx: CorrelationContext + ) -> typing.Iterator[CorrelationContext]: + """ TODO """ diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/binaryformat.py b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/binaryformat.py index d6d083c0da..f0a4a1e4ad 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/binaryformat.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/binaryformat.py @@ -15,7 +15,7 @@ import abc import typing -from opentelemetry.distributedcontext import DistributedContext +from opentelemetry.distributedcontext import CorrelationContext class BinaryFormat(abc.ABC): @@ -27,17 +27,17 @@ class BinaryFormat(abc.ABC): @staticmethod @abc.abstractmethod - def to_bytes(context: DistributedContext) -> bytes: - """Creates a byte representation of a DistributedContext. + def to_bytes(context: CorrelationContext) -> bytes: + """Creates a byte representation of a CorrelationContext. - to_bytes should read values from a DistributedContext and return a data + to_bytes should read values from a CorrelationContext and return a data format to represent it, in bytes. Args: - context: the DistributedContext to serialize + context: the CorrelationContext to serialize Returns: - A bytes representation of the DistributedContext. + A bytes representation of the CorrelationContext. """ @@ -45,18 +45,18 @@ def to_bytes(context: DistributedContext) -> bytes: @abc.abstractmethod def from_bytes( byte_representation: bytes, - ) -> typing.Optional[DistributedContext]: - """Return a DistributedContext that was represented by bytes. + ) -> typing.Optional[CorrelationContext]: + """Return a CorrelationContext that was represented by bytes. - from_bytes should return back a DistributedContext that was constructed + from_bytes should return back a CorrelationContext 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. + not possible to read in a proper CorrelationContext, return None. Args: byte_representation: the bytes to deserialize Returns: - A bytes representation of the DistributedContext if it is valid. + A bytes representation of the CorrelationContext 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 index 3e2c186283..f4c7e6838f 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py @@ -15,7 +15,7 @@ import abc import typing -from opentelemetry.distributedcontext import DistributedContext +from opentelemetry.distributedcontext import CorrelationContext Setter = typing.Callable[[object, str, str], None] Getter = typing.Callable[[object, str], typing.List[str]] @@ -69,34 +69,34 @@ def example_route(): @abc.abstractmethod def extract( self, get_from_carrier: Getter, carrier: object - ) -> DistributedContext: - """Create a DistributedContext from values in the carrier. + ) -> CorrelationContext: + """Create a CorrelationContext 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. + CorrelationContext 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 + used to construct a CorrelationContext. 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. + A CorrelationContext with configuration found in the carrier. """ @abc.abstractmethod def inject( self, - context: DistributedContext, + context: CorrelationContext, set_in_carrier: Setter, carrier: object, ) -> None: - """Inject values from a DistributedContext into a carrier. + """Inject values from a CorrelationContext into a carrier. inject enables the propagation of values into HTTP clients or other objects which perform an HTTP request. Implementations @@ -104,7 +104,7 @@ def inject( carrier. Args: - context: The DistributedContext to read values from. + context: The CorrelationContext 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. diff --git a/opentelemetry-api/tests/distributedcontext/test_distributed_context.py b/opentelemetry-api/tests/distributedcontext/test_distributed_context.py index c730603b16..ce3b26a793 100644 --- a/opentelemetry-api/tests/distributedcontext/test_distributed_context.py +++ b/opentelemetry-api/tests/distributedcontext/test_distributed_context.py @@ -67,7 +67,7 @@ def test_key_new(self): self.assertEqual(key, "ok") -class TestDistributedContext(unittest.TestCase): +class TestCorrelationContext(unittest.TestCase): def setUp(self): entry = self.entry = distributedcontext.Entry( distributedcontext.EntryMetadata( @@ -76,30 +76,37 @@ def setUp(self): distributedcontext.EntryKey("key"), distributedcontext.EntryValue("value"), ) - self.context = distributedcontext.DistributedContext((entry,)) + self.context = distributedcontext.CorrelationContext((entry,)) def test_get_entries(self): - self.assertIn(self.entry, self.context.get_entries()) + self.assertIn( + self.entry, + distributedcontext.CorrelationContext.get_entries(self.context), + ) def test_get_entry_value_present(self): - value = self.context.get_entry_value(self.entry.key) + value = distributedcontext.CorrelationContext.get_entry_value( + self.context, self.entry.key + ) self.assertIs(value, self.entry.value) def test_get_entry_value_missing(self): key = distributedcontext.EntryKey("missing") - value = self.context.get_entry_value(key) + value = distributedcontext.CorrelationContext.get_entry_value( + self.context, key + ) self.assertIsNone(value) -class TestDistributedContextManager(unittest.TestCase): +class TestCorrelationContextManager(unittest.TestCase): def setUp(self): - self.manager = distributedcontext.DistributedContextManager() + self.manager = distributedcontext.CorrelationContextManager() - def test_get_current_context(self): - self.assertIsNone(self.manager.get_current_context()) + def test_current_context(self): + self.assertIsNone(self.manager.current_context()) def test_use_context(self): - expected = distributedcontext.DistributedContext( + expected = distributedcontext.CorrelationContext( ( distributedcontext.Entry( distributedcontext.EntryMetadata(0), diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py index a20cbf8963..62fa463776 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py @@ -19,8 +19,8 @@ from opentelemetry.context import Context -class DistributedContextManager(dctx_api.DistributedContextManager): - """See `opentelemetry.distributedcontext.DistributedContextManager` +class CorrelationContextManager(dctx_api.CorrelationContextManager): + """See `opentelemetry.distributedcontext.CorrelationContextManager` Args: name: The name of the context manager @@ -28,35 +28,33 @@ class DistributedContextManager(dctx_api.DistributedContextManager): def __init__(self, name: str = "") -> None: if name: - slot_name = "DistributedContext.{}".format(name) + slot_name = "CorrelationContext.{}".format(name) else: - slot_name = "DistributedContext" + slot_name = "CorrelationContext" self._current_context = Context.register_slot(slot_name) - def get_current_context( - self, - ) -> typing.Optional[dctx_api.DistributedContext]: - """Gets the current DistributedContext. + def current_context(self,) -> typing.Optional[dctx_api.CorrelationContext]: + """Gets the current CorrelationContext. Returns: - A DistributedContext instance representing the current context. + A CorrelationContext instance representing the current context. """ return self._current_context.get() @contextmanager def use_context( - self, context: dctx_api.DistributedContext - ) -> typing.Iterator[dctx_api.DistributedContext]: - """Context manager for controlling a DistributedContext lifetime. + self, context: dctx_api.CorrelationContext + ) -> typing.Iterator[dctx_api.CorrelationContext]: + """Context manager for controlling a CorrelationContext lifetime. - Set the context as the active DistributedContext. + Set the context as the active CorrelationContext. On exiting, the context manager will restore the parent - DistributedContext. + CorrelationContext. Args: - context: A DistributedContext instance to make current. + context: A CorrelationContext instance to make current. """ snapshot = self._current_context.get() self._current_context.set(context) diff --git a/opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py b/opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py index eddb61330d..bb174ccd0a 100644 --- a/opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py +++ b/opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py @@ -18,25 +18,25 @@ from opentelemetry.sdk import distributedcontext -class TestDistributedContextManager(unittest.TestCase): +class TestCorrelationContextManager(unittest.TestCase): def setUp(self): - self.manager = distributedcontext.DistributedContextManager() + self.manager = distributedcontext.CorrelationContextManager() def test_use_context(self): # Context is None initially - self.assertIsNone(self.manager.get_current_context()) + self.assertIsNone(self.manager.current_context()) # Start initial context - dctx = dctx_api.DistributedContext(()) + dctx = dctx_api.CorrelationContext(()) with self.manager.use_context(dctx) as current: self.assertIs(current, dctx) - self.assertIs(self.manager.get_current_context(), dctx) + self.assertIs(self.manager.current_context(), dctx) # Context is overridden - nested_dctx = dctx_api.DistributedContext(()) + nested_dctx = dctx_api.CorrelationContext(()) with self.manager.use_context(nested_dctx) as current: self.assertIs(current, nested_dctx) - self.assertIs(self.manager.get_current_context(), nested_dctx) + self.assertIs(self.manager.current_context(), nested_dctx) # Context is restored - self.assertIs(self.manager.get_current_context(), dctx) + self.assertIs(self.manager.current_context(), dctx) From 5ef40dfeb62ee1c337d6a4d54fcce66b075e7339 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 11 Dec 2019 09:19:39 -0800 Subject: [PATCH 07/71] adding ContextKeys for trace and distributedcontext Signed-off-by: Alex Boten --- .../propagation/__init__.py | 11 +++++++++ .../trace/propagation/__init__.py | 24 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py index c8706281ad..1635a19a29 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py @@ -16,3 +16,14 @@ from .httptextformat import HTTPTextFormat __all__ = ["BinaryFormat", "HTTPTextFormat"] + + +class ContextKeys: + """ TODO """ + + KEY = "correlation-context" + + @classmethod + def span_context_key(cls): + """ TODO """ + return cls.KEY diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py new file mode 100644 index 0000000000..ae14c05889 --- /dev/null +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py @@ -0,0 +1,24 @@ +# 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. + + +class ContextKeys: + """ TODO """ + + KEY = "span-context" + + @classmethod + def span_context_key(cls): + """ TODO """ + return cls.KEY From 0442f29f69925aa97ef191011ca48015810f05c0 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 11 Dec 2019 14:28:17 -0800 Subject: [PATCH 08/71] moving api/propagators -> api/propagation. adding more to context api Signed-off-by: Alex Boten --- .../flask_example.py | 5 +- .../src/opentelemetry/ext/flask/__init__.py | 9 ++- .../ext/http_requests/__init__.py | 10 +++- .../ext/opentracing_shim/__init__.py | 29 ++++++--- .../tests/test_shim.py | 13 ++-- .../src/opentelemetry/ext/wsgi/__init__.py | 8 ++- .../src/opentelemetry/context/__init__.py | 2 +- .../src/opentelemetry/context/base_context.py | 20 +++++++ .../context/propagation/httptextformat.py | 11 +++- .../propagation/tracecontexthttptextformat.py | 26 +++++--- .../{propagators => propagation}/__init__.py | 43 +++++++++---- .../test_tracecontexthttptextformat.py | 60 ++++++++++++++----- .../src/opentelemetry/sdk/trace/__init__.py | 13 +++- 13 files changed, 186 insertions(+), 63 deletions(-) rename opentelemetry-api/src/opentelemetry/{propagators => propagation}/__init__.py (71%) diff --git a/examples/opentelemetry-example-app/src/opentelemetry_example_app/flask_example.py b/examples/opentelemetry-example-app/src/opentelemetry_example_app/flask_example.py index 85df625efe..98e1ff3f87 100644 --- a/examples/opentelemetry-example-app/src/opentelemetry_example_app/flask_example.py +++ b/examples/opentelemetry-example-app/src/opentelemetry_example_app/flask_example.py @@ -20,7 +20,7 @@ import requests import opentelemetry.ext.http_requests -from opentelemetry import propagators, trace +from opentelemetry import propagation, trace from opentelemetry.ext.flask import instrument_app from opentelemetry.sdk.context.propagation.b3_format import B3Format from opentelemetry.sdk.trace import Tracer @@ -51,7 +51,8 @@ def configure_opentelemetry(flask_app: flask.Flask): # carry this value). # TBD: can remove once default TraceContext propagators are installed. - propagators.set_global_httptextformat(B3Format()) + propagation.set_http_extractors([B3Format]) + propagation.set_http_injectors([B3Format]) # Integrations are the glue that binds the OpenTelemetry API # and the frameworks and libraries that are used together, automatically 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 cce038ccb5..c24c782080 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,8 @@ from flask import request as flask_request import opentelemetry.ext.wsgi as otel_wsgi -from opentelemetry import propagators, trace +from opentelemetry import propagation, trace +from opentelemetry.context import BaseRuntimeContext from opentelemetry.util import time_ns logger = logging.getLogger(__name__) @@ -56,8 +57,10 @@ 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 + parent_span = propagation.extract( + BaseRuntimeContext.current(), + otel_wsgi.get_header_from_environ, + environ, ) tracer = trace.tracer() 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 f05202c055..1518596ded 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 @@ -22,8 +22,8 @@ from requests.sessions import Session -from opentelemetry import propagators -from opentelemetry.context import Context +from opentelemetry import propagation +from opentelemetry.context import BaseRuntimeContext, Context from opentelemetry.trace import SpanKind @@ -74,7 +74,11 @@ def instrumented_request(self, method, url, *args, **kwargs): # to access propagators. headers = kwargs.setdefault("headers", {}) - propagators.inject(tracer, type(headers).__setitem__, headers) + propagation.inject( + BaseRuntimeContext.current(), + type(headers).__setitem__, + headers, + ) result = wrapped(self, method, url, *args, **kwargs) # *** PROCEED span.set_attribute("http.status_code", result.status_code) 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 a9e74dbc58..c3a1fc6459 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 @@ -84,8 +84,11 @@ from deprecated import deprecated import opentelemetry.trace as trace_api -from opentelemetry import propagators +from opentelemetry import propagation +from opentelemetry.context import Context +from opentelemetry.context.base_context import BaseRuntimeContext from opentelemetry.ext.opentracing_shim import util +from opentelemetry.trace.propagation import ContextKeys logger = logging.getLogger(__name__) @@ -657,15 +660,19 @@ def inject(self, span_context, format, carrier): # TODO: Finish documentation. # pylint: disable=redefined-builtin # This implementation does not perform the injecting by itself but - # uses the configured propagators in opentelemetry.propagators. + # uses the configured propagation in opentelemetry.propagation. # TODO: Support Format.BINARY once it is supported in # opentelemetry-python. if format not in self._supported_formats: raise opentracing.UnsupportedFormatException - propagator = propagators.get_global_httptextformat() - propagator.inject( - span_context.unwrap(), type(carrier).__setitem__, carrier + BaseRuntimeContext.set_value( + BaseRuntimeContext.current(), + ContextKeys.span_context_key(), + span_context.unwrap(), + ) + propagation.inject( + BaseRuntimeContext.current(), type(carrier).__setitem__, carrier ) def extract(self, format, carrier): @@ -674,7 +681,7 @@ def extract(self, format, carrier): # TODO: Finish documentation. # pylint: disable=redefined-builtin # This implementation does not perform the extracing by itself but - # uses the configured propagators in opentelemetry.propagators. + # uses the configured propagation in opentelemetry.propagation. # TODO: Support Format.BINARY once it is supported in # opentelemetry-python. if format not in self._supported_formats: @@ -684,7 +691,13 @@ def get_as_list(dict_object, key): value = dict_object.get(key) return [value] if value is not None else [] - propagator = propagators.get_global_httptextformat() - otel_context = propagator.extract(get_as_list, carrier) + # propagator = propagation.get_global_httptextformat() + + otel_context = BaseRuntimeContext.value( + propagation.extract( + BaseRuntimeContext.current(), get_as_list, carrier + ), + ContextKeys.span_context_key(), + ) 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 0daabf199a..64a3b3cff8 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -18,7 +18,7 @@ import opentracing import opentelemetry.ext.opentracing_shim as opentracingshim -from opentelemetry import propagators, trace +from opentelemetry import propagation, trace from opentelemetry.context.propagation.httptextformat import HTTPTextFormat from opentelemetry.ext.opentracing_shim import util from opentelemetry.sdk.trace import Tracer @@ -42,15 +42,18 @@ def setUpClass(cls): trace.set_preferred_tracer_implementation(lambda T: Tracer()) # Save current propagator to be restored on teardown. - cls._previous_propagator = propagators.get_global_httptextformat() + cls._previous_injectors = propagation.get_http_injectors() + cls._previous_extractors = propagation.get_http_extractors() # Set mock propagator for testing. - propagators.set_global_httptextformat(MockHTTPTextFormat) + propagation.set_http_extractors([MockHTTPTextFormat]) + propagation.set_http_injectors([MockHTTPTextFormat]) @classmethod def tearDownClass(cls): # Restore previous propagator. - propagators.set_global_httptextformat(cls._previous_propagator) + propagation.set_http_extractors(cls._previous_extractors) + propagation.set_http_injectors(cls._previous_injectors) def test_shim_type(self): # Verify shim is an OpenTracing tracer. @@ -538,7 +541,7 @@ class MockHTTPTextFormat(HTTPTextFormat): SPAN_ID_KEY = "mock-spanid" @classmethod - def extract(cls, get_from_carrier, carrier): + def extract(cls, context, get_from_carrier, carrier): trace_id_list = get_from_carrier(carrier, cls.TRACE_ID_KEY) span_id_list = get_from_carrier(carrier, cls.SPAN_ID_KEY) 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 16a0f9d944..b702d49ce3 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,8 @@ import typing import wsgiref.util as wsgiref_util -from opentelemetry import propagators, trace +from opentelemetry import propagation, trace +from opentelemetry.context import BaseRuntimeContext from opentelemetry.ext.wsgi.version import __version__ # noqa from opentelemetry.trace.status import Status, StatusCanonicalCode @@ -183,7 +184,10 @@ def __call__(self, environ, start_response): """ tracer = trace.tracer() - parent_span = propagators.extract(get_header_from_environ, environ) + # TODO: fix the return value here, expect a context + parent_span = propagation.extract( + BaseRuntimeContext.current(), get_header_from_environ, environ + ) span_name = get_default_span_name(environ) span = tracer.start_span( diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 43a7722f88..9cd42bf557 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -140,7 +140,7 @@ async def main(): from .base_context import BaseRuntimeContext -__all__ = ["Context"] +__all__ = ["BaseRuntimeContext", "Context"] try: from .async_context import AsyncRuntimeContext diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py index 99d6869dd5..a3a838d20e 100644 --- a/opentelemetry-api/src/opentelemetry/context/base_context.py +++ b/opentelemetry-api/src/opentelemetry/context/base_context.py @@ -128,3 +128,23 @@ def call_with_current_context( self.apply(backup_context) return call_with_current_context + + @classmethod + def value(cls, ctx: "BaseRuntimeContext", key: str) -> str: + return ctx.__getitem__(key) + + @classmethod + def set_value( + cls, ctx: "BaseRuntimeContext", key: str, value: "object" + ) -> "BaseRuntimeContext": + # TODO: dont use the context directly here + ctx.__setattr__(key, value) + return ctx + + @classmethod + def current(cls) -> "BaseRuntimeContext": + return ctx.__getitem__("current-context") + + @classmethod + def set_current(cls, ctx: "BaseRuntimeContext"): + ctx.__setattr__("current-context", ctx) diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py index b90b4c9eba..f225bdc5fa 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py @@ -71,10 +71,14 @@ def example_route(): https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/api-propagators.md """ + @classmethod @abc.abstractmethod def extract( - context: BaseRuntimeContext, get_from_carrier: Getter[_T], carrier: _T - ) -> SpanContext: + cls, + context: BaseRuntimeContext, + get_from_carrier: Getter[_T], + carrier: _T, + ) -> BaseRuntimeContext: """Create a SpanContext from values in the carrier. The extract function should retrieve values from the carrier @@ -94,9 +98,10 @@ def extract( """ + @classmethod @abc.abstractmethod def inject( - self, + cls, context: BaseRuntimeContext, set_in_carrier: Setter[_T], carrier: _T, diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py index 5d00632ed1..392a4707f3 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py @@ -16,7 +16,9 @@ import typing import opentelemetry.trace as trace +from opentelemetry.context import BaseRuntimeContext from opentelemetry.context.propagation import httptextformat +from opentelemetry.trace.propagation import ContextKeys _T = typing.TypeVar("_T") @@ -61,8 +63,11 @@ class TraceContextHTTPTextFormat(httptextformat.HTTPTextFormat): @classmethod def extract( - cls, get_from_carrier: httptextformat.Getter[_T], carrier: _T - ) -> trace.SpanContext: + cls, + context: BaseRuntimeContext, + get_from_carrier: httptextformat.Getter[_T], + carrier: _T, + ) -> BaseRuntimeContext: """Extracts a valid SpanContext from the carrier. """ header = get_from_carrier(carrier, cls._TRACEPARENT_HEADER_NAME) @@ -100,25 +105,30 @@ def extract( trace_state=tracestate, ) - return span_context + ctx = BaseRuntimeContext.set_value( + context, ContextKeys.span_context_key(), span_context + ) + + return ctx @classmethod def inject( cls, - context: trace.SpanContext, + context: BaseRuntimeContext, set_in_carrier: httptextformat.Setter[_T], carrier: _T, ) -> None: - if context == trace.INVALID_SPAN_CONTEXT: + sc = BaseRuntimeContext.value(context, ContextKeys.span_context_key()) + if sc is None or sc == trace.INVALID_SPAN_CONTEXT: return traceparent_string = "00-{:032x}-{:016x}-{:02x}".format( - context.trace_id, context.span_id, context.trace_options + sc.trace_id, sc.span_id, sc.trace_options, ) set_in_carrier( carrier, cls._TRACEPARENT_HEADER_NAME, traceparent_string ) - if context.trace_state: - tracestate_string = _format_tracestate(context.trace_state) + if sc.trace_state: + tracestate_string = _format_tracestate(sc.trace_state) set_in_carrier( carrier, cls._TRACESTATE_HEADER_NAME, tracestate_string ) diff --git a/opentelemetry-api/src/opentelemetry/propagators/__init__.py b/opentelemetry-api/src/opentelemetry/propagation/__init__.py similarity index 71% rename from opentelemetry-api/src/opentelemetry/propagators/__init__.py rename to opentelemetry-api/src/opentelemetry/propagation/__init__.py index 190c77dc98..1b18994a94 100644 --- a/opentelemetry-api/src/opentelemetry/propagators/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagation/__init__.py @@ -25,8 +25,10 @@ def extract( - get_from_carrier: httptextformat.Getter[_T], carrier: _T -) -> BaseRuntimeContext: + context: BaseRuntimeContext, + get_from_carrier: httptextformat.Getter[_T], + carrier: _T, +) -> typing.Optional[BaseRuntimeContext]: """Load the parent SpanContext from values in the carrier. Using the specified HTTPTextFormatter, the propagator will @@ -42,11 +44,13 @@ 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) + for extractor in get_http_extractors(): + return extractor.extract(context, get_from_carrier, carrier) + return None def inject( - context: BaseRuntimeContext + context: BaseRuntimeContext, set_in_carrier: httptextformat.Setter[_T], carrier: _T, ) -> None: @@ -64,23 +68,36 @@ 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().get_context(), set_in_carrier, carrier - ) + for injector in get_http_injectors(): + injector.inject(context, set_in_carrier, carrier) + _HTTP_TEXT_INJECTORS = [ - TraceContextHTTPTextFormat() + TraceContextHTTPTextFormat ] # typing.List[httptextformat.HTTPTextFormat] _HTTP_TEXT_EXTRACTORS = [ - TraceContextHTTPTextFormat() + TraceContextHTTPTextFormat ] # typing.List[httptextformat.HTTPTextFormat] -def set_http_extractors(extractor_list: typing.List[httptextformat.HTTPTextFormat]) -> None: +def set_http_extractors( + extractor_list: typing.List[httptextformat.HTTPTextFormat], +) -> None: global _HTTP_TEXT_EXTRACTORS # pylint:disable=global-statement _HTTP_TEXT_EXTRACTORS = extractor_list -def set_http_injectors(extractor_list: typing.List[httptextformat.HTTPTextFormat]) -> None: - global _HTTP_TEXT_INJECTORS # pylint:disable=global-statement - _HTTP_TEXT_INJECTORS = injector_list \ No newline at end of file + +def set_http_injectors( + injector_list: typing.List[httptextformat.HTTPTextFormat], +) -> None: + global _HTTP_TEXT_INJECTORS # pylint:disable=global-statement + _HTTP_TEXT_INJECTORS = injector_list + + +def get_http_extractors() -> typing.List[httptextformat.HTTPTextFormat]: + return _HTTP_TEXT_EXTRACTORS + + +def get_http_injectors() -> typing.List[httptextformat.HTTPTextFormat]: + return _HTTP_TEXT_INJECTORS diff --git a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py index ed952e0dba..f7bca11c5c 100644 --- a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py @@ -16,9 +16,15 @@ import unittest from opentelemetry import trace +from opentelemetry.context import BaseRuntimeContext, Context from opentelemetry.context.propagation import tracecontexthttptextformat +from opentelemetry.context.propagation.tracecontexthttptextformat import ( + TraceContextHTTPTextFormat, +) +from opentelemetry.trace.propagation import ContextKeys -FORMAT = tracecontexthttptextformat.TraceContextHTTPTextFormat() +INJECT = TraceContextHTTPTextFormat +EXTRACT = TraceContextHTTPTextFormat def get_as_list( @@ -32,6 +38,9 @@ class TestTraceContextFormat(unittest.TestCase): TRACE_ID = int("12345678901234567890123456789012", 16) # type:int SPAN_ID = int("1234567890123456", 16) # type:int + def setUp(self): + self.ctx = BaseRuntimeContext.current() + def test_no_traceparent_header(self): """When tracecontext headers are not present, a new SpanContext should be created. @@ -41,7 +50,7 @@ def test_no_traceparent_header(self): If no traceparent header is received, the vendor creates a new 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) + span_context = EXTRACT.extract(self.ctx, get_as_list, output) self.assertTrue(isinstance(span_context, trace.SpanContext)) def test_headers_with_tracestate(self): @@ -53,13 +62,17 @@ 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( + ctx = EXTRACT.extract( + self.ctx, get_as_list, { "traceparent": [traceparent_value], "tracestate": [tracestate_value], }, ) + span_context = BaseRuntimeContext.value( + ctx, ContextKeys.span_context_key() + ) self.assertEqual(span_context.trace_id, self.TRACE_ID) self.assertEqual(span_context.span_id, self.SPAN_ID) self.assertEqual( @@ -67,7 +80,7 @@ def test_headers_with_tracestate(self): ) output = {} # type:typing.Dict[str, str] - FORMAT.inject(span_context, dict.__setitem__, output) + INJECT.inject(self.ctx, dict.__setitem__, output) self.assertEqual(output["traceparent"], traceparent_value) for pair in ["foo=1", "bar=2", "baz=3"]: self.assertIn(pair, output["tracestate"]) @@ -89,7 +102,8 @@ def test_invalid_trace_id(self): If the vendor failed to parse traceparent, it MUST NOT attempt to parse tracestate. Note that the opposite is not true: failure to parse tracestate MUST NOT affect the parsing of traceparent. """ - span_context = FORMAT.extract( + span_context = EXTRACT.extract( + self.ctx, get_as_list, { "traceparent": [ @@ -115,7 +129,8 @@ def test_invalid_parent_id(self): If the vendor failed to parse traceparent, it MUST NOT attempt to parse tracestate. Note that the opposite is not true: failure to parse tracestate MUST NOT affect the parsing of traceparent. """ - span_context = FORMAT.extract( + span_context = EXTRACT.extract( + self.ctx, get_as_list, { "traceparent": [ @@ -134,11 +149,14 @@ def test_no_send_empty_tracestate(self): Empty and whitespace-only list members are allowed. Vendors MUST accept empty tracestate headers but SHOULD avoid sending them. """ - output = {} # type:typing.Dict[str, str] - FORMAT.inject( + ctx = BaseRuntimeContext.set_value( + self.ctx, + ContextKeys.span_context_key(), trace.SpanContext(self.TRACE_ID, self.SPAN_ID), - dict.__setitem__, - output, + ) + output = {} # type:typing.Dict[str, str] + INJECT.inject( + ctx, dict.__setitem__, output, ) self.assertTrue("traceparent" in output) self.assertFalse("tracestate" in output) @@ -151,7 +169,8 @@ def test_format_not_supported(self): If the version cannot be parsed, return an invalid trace header. """ - span_context = FORMAT.extract( + span_context = EXTRACT.extract( + self.ctx, get_as_list, { "traceparent": [ @@ -166,12 +185,18 @@ def test_propagate_invalid_context(self): """Do not propagate invalid trace context. """ output = {} # type:typing.Dict[str, str] - FORMAT.inject(trace.INVALID_SPAN_CONTEXT, dict.__setitem__, output) + ctx = BaseRuntimeContext.set_value( + self.ctx, + ContextKeys.span_context_key(), + trace.INVALID_SPAN_CONTEXT, + ) + INJECT.inject(ctx, dict.__setitem__, output) 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( + ctx = EXTRACT.extract( + self.ctx, get_as_list, { "traceparent": [ @@ -180,12 +205,16 @@ def test_tracestate_empty_header(self): "tracestate": ["foo=1", ""], }, ) + span_context = BaseRuntimeContext.value( + ctx, ContextKeys.span_context_key() + ) self.assertEqual(span_context.trace_state["foo"], "1") def test_tracestate_header_with_trailing_comma(self): """Do not propagate invalid trace context. """ - span_context = FORMAT.extract( + ctx = EXTRACT.extract( + self.ctx, get_as_list, { "traceparent": [ @@ -194,4 +223,7 @@ def test_tracestate_header_with_trailing_comma(self): "tracestate": ["foo=1,"], }, ) + span_context = BaseRuntimeContext.value( + ctx, ContextKeys.span_context_key() + ) self.assertEqual(span_context.trace_state["foo"], "1") diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 5967960ba3..f46918bba2 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -21,10 +21,11 @@ from typing import Iterator, Optional, Sequence, Tuple from opentelemetry import trace as trace_api -from opentelemetry.context import Context +from opentelemetry.context import BaseRuntimeContext, Context from opentelemetry.sdk import util from opentelemetry.sdk.util import BoundedDict, BoundedList from opentelemetry.trace import sampling +from opentelemetry.trace.propagation import ContextKeys from opentelemetry.util import time_ns, types logger = logging.getLogger(__name__) @@ -352,6 +353,11 @@ def start_span( if isinstance(parent_context, trace_api.Span): parent_context = parent.get_context() + if parent_context is None: + parent_context = BaseRuntimeContext.value( + BaseRuntimeContext.current(), ContextKeys.span_context_key() + ) + if parent_context is not None and not isinstance( parent_context, trace_api.SpanContext ): @@ -415,6 +421,11 @@ def use_span( try: span_snapshot = self._current_span_slot.get() self._current_span_slot.set(span) + BaseRuntimeContext.set_value( + BaseRuntimeContext.current(), + ContextKeys.span_context_key(), + span.get_context(), + ) try: yield span finally: From 622d787f018031dbcce144ef4166078a314faf90 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 11 Dec 2019 20:18:04 -0800 Subject: [PATCH 09/71] add from_context/with_span_context helpers Signed-off-by: Alex Boten --- .../src/opentelemetry/ext/flask/__init__.py | 6 ++-- .../ext/http_requests/__init__.py | 6 ++-- .../ext/opentracing_shim/__init__.py | 23 +++++-------- .../src/opentelemetry/ext/wsgi/__init__.py | 4 +-- .../src/opentelemetry/context/base_context.py | 10 +++--- .../trace/propagation/context/__init__.py | 31 +++++++++++++++++ .../test_tracecontexthttptextformat.py | 33 +++++++------------ .../src/opentelemetry/sdk/trace/__init__.py | 17 ++++------ 8 files changed, 68 insertions(+), 62 deletions(-) create mode 100644 opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py 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 c24c782080..1c42ff0611 100644 --- a/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py +++ b/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py @@ -7,7 +7,7 @@ import opentelemetry.ext.wsgi as otel_wsgi from opentelemetry import propagation, trace -from opentelemetry.context import BaseRuntimeContext +from opentelemetry.context import Context from opentelemetry.util import time_ns logger = logging.getLogger(__name__) @@ -58,9 +58,7 @@ def _before_flask_request(): environ ) parent_span = propagation.extract( - BaseRuntimeContext.current(), - otel_wsgi.get_header_from_environ, - environ, + Context.current(), otel_wsgi.get_header_from_environ, environ, ) tracer = trace.tracer() 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 1518596ded..db2efdc527 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 @@ -23,7 +23,7 @@ from requests.sessions import Session from opentelemetry import propagation -from opentelemetry.context import BaseRuntimeContext, Context +from opentelemetry.context import Context from opentelemetry.trace import SpanKind @@ -75,9 +75,7 @@ def instrumented_request(self, method, url, *args, **kwargs): headers = kwargs.setdefault("headers", {}) propagation.inject( - BaseRuntimeContext.current(), - type(headers).__setitem__, - headers, + Context.current(), type(headers).__setitem__, headers, ) result = wrapped(self, method, url, *args, **kwargs) # *** PROCEED 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 c3a1fc6459..fedce2536a 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 @@ -86,9 +86,11 @@ import opentelemetry.trace as trace_api from opentelemetry import propagation from opentelemetry.context import Context -from opentelemetry.context.base_context import BaseRuntimeContext from opentelemetry.ext.opentracing_shim import util -from opentelemetry.trace.propagation import ContextKeys +from opentelemetry.trace.propagation.context import ( + from_context, + with_span_context, +) logger = logging.getLogger(__name__) @@ -666,14 +668,8 @@ def inject(self, span_context, format, carrier): if format not in self._supported_formats: raise opentracing.UnsupportedFormatException - BaseRuntimeContext.set_value( - BaseRuntimeContext.current(), - ContextKeys.span_context_key(), - span_context.unwrap(), - ) - propagation.inject( - BaseRuntimeContext.current(), type(carrier).__setitem__, carrier - ) + ctx = with_span_context(Context.current(), span_context.unwrap()) + propagation.inject(ctx, type(carrier).__setitem__, carrier) def extract(self, format, carrier): """Implements the ``extract`` method from the base class.""" @@ -693,11 +689,8 @@ def get_as_list(dict_object, key): # propagator = propagation.get_global_httptextformat() - otel_context = BaseRuntimeContext.value( - propagation.extract( - BaseRuntimeContext.current(), get_as_list, carrier - ), - ContextKeys.span_context_key(), + otel_context = from_context( + propagation.extract(Context.current(), get_as_list, carrier) ) return SpanContextShim(otel_context) 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 b702d49ce3..7613152869 100644 --- a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py +++ b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py @@ -23,7 +23,7 @@ import wsgiref.util as wsgiref_util from opentelemetry import propagation, trace -from opentelemetry.context import BaseRuntimeContext +from opentelemetry.context import Context from opentelemetry.ext.wsgi.version import __version__ # noqa from opentelemetry.trace.status import Status, StatusCanonicalCode @@ -186,7 +186,7 @@ def __call__(self, environ, start_response): tracer = trace.tracer() # TODO: fix the return value here, expect a context parent_span = propagation.extract( - BaseRuntimeContext.current(), get_header_from_environ, environ + Context.current(), get_header_from_environ, environ ) span_name = get_default_span_name(environ) diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py index a3a838d20e..a81b477338 100644 --- a/opentelemetry-api/src/opentelemetry/context/base_context.py +++ b/opentelemetry-api/src/opentelemetry/context/base_context.py @@ -141,10 +141,8 @@ def set_value( ctx.__setattr__(key, value) return ctx - @classmethod - def current(cls) -> "BaseRuntimeContext": - return ctx.__getitem__("current-context") + def current(self) -> "BaseRuntimeContext": + return self.__getitem__("__current_context__") - @classmethod - def set_current(cls, ctx: "BaseRuntimeContext"): - ctx.__setattr__("current-context", ctx) + def set_current(self, ctx: "BaseRuntimeContext"): + self.__setattr__("__current_context__", ctx) diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py new file mode 100644 index 0000000000..d587000aa0 --- /dev/null +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py @@ -0,0 +1,31 @@ +# 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 opentelemetry.context import BaseRuntimeContext, Context +from opentelemetry.trace import SpanContext +from opentelemetry.trace.propagation import ContextKeys + + +def from_context(ctx: BaseRuntimeContext) -> SpanContext: + return BaseRuntimeContext.value(ctx, ContextKeys.span_context_key(),) + + +def with_span_context( + ctx: BaseRuntimeContext, span_context: SpanContext +) -> BaseRuntimeContext: + return BaseRuntimeContext.set_value( + ctx, ContextKeys.span_context_key(), span_context, + ) + diff --git a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py index f7bca11c5c..405c657ed5 100644 --- a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py @@ -16,12 +16,15 @@ import unittest from opentelemetry import trace -from opentelemetry.context import BaseRuntimeContext, Context +from opentelemetry.context import Context from opentelemetry.context.propagation import tracecontexthttptextformat from opentelemetry.context.propagation.tracecontexthttptextformat import ( TraceContextHTTPTextFormat, ) -from opentelemetry.trace.propagation import ContextKeys +from opentelemetry.trace.propagation.context import ( + from_context, + with_span_context, +) INJECT = TraceContextHTTPTextFormat EXTRACT = TraceContextHTTPTextFormat @@ -39,7 +42,7 @@ class TestTraceContextFormat(unittest.TestCase): SPAN_ID = int("1234567890123456", 16) # type:int def setUp(self): - self.ctx = BaseRuntimeContext.current() + self.ctx = Context.current() def test_no_traceparent_header(self): """When tracecontext headers are not present, a new SpanContext @@ -70,9 +73,7 @@ def test_headers_with_tracestate(self): "tracestate": [tracestate_value], }, ) - span_context = BaseRuntimeContext.value( - ctx, ContextKeys.span_context_key() - ) + span_context = from_context(ctx) self.assertEqual(span_context.trace_id, self.TRACE_ID) self.assertEqual(span_context.span_id, self.SPAN_ID) self.assertEqual( @@ -149,10 +150,8 @@ def test_no_send_empty_tracestate(self): Empty and whitespace-only list members are allowed. Vendors MUST accept empty tracestate headers but SHOULD avoid sending them. """ - ctx = BaseRuntimeContext.set_value( - self.ctx, - ContextKeys.span_context_key(), - trace.SpanContext(self.TRACE_ID, self.SPAN_ID), + ctx = with_span_context( + self.ctx, trace.SpanContext(self.TRACE_ID, self.SPAN_ID) ) output = {} # type:typing.Dict[str, str] INJECT.inject( @@ -185,11 +184,7 @@ def test_propagate_invalid_context(self): """Do not propagate invalid trace context. """ output = {} # type:typing.Dict[str, str] - ctx = BaseRuntimeContext.set_value( - self.ctx, - ContextKeys.span_context_key(), - trace.INVALID_SPAN_CONTEXT, - ) + ctx = with_span_context(self.ctx, trace.INVALID_SPAN_CONTEXT) INJECT.inject(ctx, dict.__setitem__, output) self.assertFalse("traceparent" in output) @@ -205,9 +200,7 @@ def test_tracestate_empty_header(self): "tracestate": ["foo=1", ""], }, ) - span_context = BaseRuntimeContext.value( - ctx, ContextKeys.span_context_key() - ) + span_context = from_context(ctx) self.assertEqual(span_context.trace_state["foo"], "1") def test_tracestate_header_with_trailing_comma(self): @@ -223,7 +216,5 @@ def test_tracestate_header_with_trailing_comma(self): "tracestate": ["foo=1,"], }, ) - span_context = BaseRuntimeContext.value( - ctx, ContextKeys.span_context_key() - ) + span_context = from_context(ctx) self.assertEqual(span_context.trace_state["foo"], "1") diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index f46918bba2..53ff756b9d 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -21,11 +21,14 @@ from typing import Iterator, Optional, Sequence, Tuple from opentelemetry import trace as trace_api -from opentelemetry.context import BaseRuntimeContext, Context +from opentelemetry.context import Context from opentelemetry.sdk import util from opentelemetry.sdk.util import BoundedDict, BoundedList from opentelemetry.trace import sampling -from opentelemetry.trace.propagation import ContextKeys +from opentelemetry.trace.propagation.context import ( + from_context, + with_span_context, +) from opentelemetry.util import time_ns, types logger = logging.getLogger(__name__) @@ -354,9 +357,7 @@ def start_span( parent_context = parent.get_context() if parent_context is None: - parent_context = BaseRuntimeContext.value( - BaseRuntimeContext.current(), ContextKeys.span_context_key() - ) + parent_context = from_context(Context.current()) if parent_context is not None and not isinstance( parent_context, trace_api.SpanContext @@ -421,11 +422,7 @@ def use_span( try: span_snapshot = self._current_span_slot.get() self._current_span_slot.set(span) - BaseRuntimeContext.set_value( - BaseRuntimeContext.current(), - ContextKeys.span_context_key(), - span.get_context(), - ) + with_span_context(Context.current(), span.get_context()) try: yield span finally: From 317e9370419e431384c3ccdae2ba22b8b55d2e97 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 11 Dec 2019 21:04:31 -0800 Subject: [PATCH 10/71] splitting propagator into extractor/injector Signed-off-by: Alex Boten --- .../flask_example.py | 9 +- .../tests/test_shim.py | 46 ++++--- .../context/propagation/httptextformat.py | 6 +- .../propagation/tracecontexthttptextformat.py | 29 +++-- .../src/opentelemetry/propagation/__init__.py | 21 +-- .../test_tracecontexthttptextformat.py | 7 +- .../sdk/context/propagation/b3_format.py | 37 +++--- .../context/propagation/test_b3_format.py | 121 +++++++++--------- 8 files changed, 146 insertions(+), 130 deletions(-) diff --git a/examples/opentelemetry-example-app/src/opentelemetry_example_app/flask_example.py b/examples/opentelemetry-example-app/src/opentelemetry_example_app/flask_example.py index 98e1ff3f87..45448d3a4f 100644 --- a/examples/opentelemetry-example-app/src/opentelemetry_example_app/flask_example.py +++ b/examples/opentelemetry-example-app/src/opentelemetry_example_app/flask_example.py @@ -22,7 +22,10 @@ import opentelemetry.ext.http_requests from opentelemetry import propagation, trace from opentelemetry.ext.flask import instrument_app -from opentelemetry.sdk.context.propagation.b3_format import B3Format +from opentelemetry.sdk.context.propagation.b3_format import ( + B3Extractor, + B3Injector, +) from opentelemetry.sdk.trace import Tracer @@ -51,8 +54,8 @@ def configure_opentelemetry(flask_app: flask.Flask): # carry this value). # TBD: can remove once default TraceContext propagators are installed. - propagation.set_http_extractors([B3Format]) - propagation.set_http_injectors([B3Format]) + propagation.set_http_extractors([B3Extractor]) + propagation.set_http_injectors([B3Injector]) # Integrations are the glue that binds the OpenTelemetry API # and the frameworks and libraries that are used together, automatically diff --git a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py index 64a3b3cff8..925de5e907 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -19,7 +19,10 @@ import opentelemetry.ext.opentracing_shim as opentracingshim from opentelemetry import propagation, trace -from opentelemetry.context.propagation.httptextformat import HTTPTextFormat +from opentelemetry.context.propagation.httptextformat import ( + HTTPExtractor, + HTTPInjector, +) from opentelemetry.ext.opentracing_shim import util from opentelemetry.sdk.trace import Tracer @@ -46,8 +49,8 @@ def setUpClass(cls): cls._previous_extractors = propagation.get_http_extractors() # Set mock propagator for testing. - propagation.set_http_extractors([MockHTTPTextFormat]) - propagation.set_http_injectors([MockHTTPTextFormat]) + propagation.set_http_extractors([MockHTTPExtractor]) + propagation.set_http_injectors([MockHTTPInjector]) @classmethod def tearDownClass(cls): @@ -477,8 +480,8 @@ def test_inject_http_headers(self): headers = {} self.shim.inject(context, opentracing.Format.HTTP_HEADERS, headers) - self.assertEqual(headers[MockHTTPTextFormat.TRACE_ID_KEY], str(1220)) - self.assertEqual(headers[MockHTTPTextFormat.SPAN_ID_KEY], str(7478)) + self.assertEqual(headers[_TRACE_ID_KEY], str(1220)) + self.assertEqual(headers[_SPAN_ID_KEY], str(7478)) def test_inject_text_map(self): """Test `inject()` method for Format.TEXT_MAP.""" @@ -489,8 +492,8 @@ def test_inject_text_map(self): # Verify Format.TEXT_MAP text_map = {} self.shim.inject(context, opentracing.Format.TEXT_MAP, text_map) - self.assertEqual(text_map[MockHTTPTextFormat.TRACE_ID_KEY], str(1220)) - self.assertEqual(text_map[MockHTTPTextFormat.SPAN_ID_KEY], str(7478)) + self.assertEqual(text_map[_TRACE_ID_KEY], str(1220)) + self.assertEqual(text_map[_SPAN_ID_KEY], str(7478)) def test_inject_binary(self): """Test `inject()` method for Format.BINARY.""" @@ -506,8 +509,8 @@ def test_extract_http_headers(self): """Test `extract()` method for Format.HTTP_HEADERS.""" carrier = { - MockHTTPTextFormat.TRACE_ID_KEY: 1220, - MockHTTPTextFormat.SPAN_ID_KEY: 7478, + _TRACE_ID_KEY: 1220, + _SPAN_ID_KEY: 7478, } ctx = self.shim.extract(opentracing.Format.HTTP_HEADERS, carrier) @@ -518,8 +521,8 @@ def test_extract_text_map(self): """Test `extract()` method for Format.TEXT_MAP.""" carrier = { - MockHTTPTextFormat.TRACE_ID_KEY: 1220, - MockHTTPTextFormat.SPAN_ID_KEY: 7478, + _TRACE_ID_KEY: 1220, + _SPAN_ID_KEY: 7478, } ctx = self.shim.extract(opentracing.Format.TEXT_MAP, carrier) @@ -534,16 +537,17 @@ def test_extract_binary(self): self.shim.extract(opentracing.Format.BINARY, bytearray()) -class MockHTTPTextFormat(HTTPTextFormat): - """Mock propagator for testing purposes.""" +_TRACE_ID_KEY = "mock-traceid" +_SPAN_ID_KEY = "mock-spanid" - TRACE_ID_KEY = "mock-traceid" - SPAN_ID_KEY = "mock-spanid" + +class MockHTTPExtractor(HTTPExtractor): + """Mock extractor for testing purposes.""" @classmethod def extract(cls, context, get_from_carrier, carrier): - 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, _TRACE_ID_KEY) + span_id_list = get_from_carrier(carrier, _SPAN_ID_KEY) if not trace_id_list or not span_id_list: return trace.INVALID_SPAN_CONTEXT @@ -552,7 +556,11 @@ def extract(cls, context, get_from_carrier, carrier): trace_id=int(trace_id_list[0]), span_id=int(span_id_list[0]) ) + +class MockHTTPInjector(HTTPInjector): + """Mock injector for testing purposes.""" + @classmethod def inject(cls, context, set_in_carrier, carrier): - set_in_carrier(carrier, cls.TRACE_ID_KEY, str(context.trace_id)) - set_in_carrier(carrier, cls.SPAN_ID_KEY, str(context.span_id)) + set_in_carrier(carrier, _TRACE_ID_KEY, str(context.trace_id)) + set_in_carrier(carrier, _SPAN_ID_KEY, str(context.span_id)) diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py index f225bdc5fa..efaead8b8f 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py @@ -24,7 +24,7 @@ Getter = typing.Callable[[_T, str], typing.List[str]] -class HTTPTextFormat(abc.ABC): +class HTTPExtractor(abc.ABC): """API for propagation of span context via headers. This class provides an interface that enables extracting and injecting @@ -37,7 +37,7 @@ class HTTPTextFormat(abc.ABC): import flask import requests - from opentelemetry.context.propagation import HTTPTextFormat + from opentelemetry.context.propagation import HTTPExtractor PROPAGATOR = HTTPTextFormat() @@ -98,6 +98,8 @@ def extract( """ + +class HTTPInjector(abc.ABC): @classmethod @abc.abstractmethod def inject( diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py index 392a4707f3..a697b2b6d5 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py @@ -49,12 +49,14 @@ _TRACECONTEXT_MAXIMUM_TRACESTATE_KEYS = 32 -class TraceContextHTTPTextFormat(httptextformat.HTTPTextFormat): - """Extracts and injects using w3c TraceContext's headers. +TRACEPARENT_HEADER_NAME = "traceparent" +TRACESTATE_HEADER_NAME = "tracestate" + + +class TraceContextHTTPExtractor(httptextformat.HTTPExtractor): + """Extracts using w3c TraceContext's headers. """ - _TRACEPARENT_HEADER_NAME = "traceparent" - _TRACESTATE_HEADER_NAME = "tracestate" _TRACEPARENT_HEADER_FORMAT = ( "^[ \t]*([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})" + "(-.*)?[ \t]*$" @@ -70,7 +72,7 @@ def extract( ) -> BaseRuntimeContext: """Extracts a valid SpanContext from the carrier. """ - header = get_from_carrier(carrier, cls._TRACEPARENT_HEADER_NAME) + header = get_from_carrier(carrier, TRACEPARENT_HEADER_NAME) if not header: return trace.INVALID_SPAN_CONTEXT @@ -93,9 +95,7 @@ def extract( if version == "ff": return trace.INVALID_SPAN_CONTEXT - tracestate_headers = get_from_carrier( - carrier, cls._TRACESTATE_HEADER_NAME - ) + tracestate_headers = get_from_carrier(carrier, TRACESTATE_HEADER_NAME) tracestate = _parse_tracestate(tracestate_headers) span_context = trace.SpanContext( @@ -111,6 +111,11 @@ def extract( return ctx + +class TraceContextHTTPInjector(httptextformat.HTTPInjector): + """Injects using w3c TraceContext's headers. + """ + @classmethod def inject( cls, @@ -124,14 +129,10 @@ def inject( traceparent_string = "00-{:032x}-{:016x}-{:02x}".format( sc.trace_id, sc.span_id, sc.trace_options, ) - set_in_carrier( - carrier, cls._TRACEPARENT_HEADER_NAME, traceparent_string - ) + set_in_carrier(carrier, TRACEPARENT_HEADER_NAME, traceparent_string) if sc.trace_state: tracestate_string = _format_tracestate(sc.trace_state) - set_in_carrier( - carrier, cls._TRACESTATE_HEADER_NAME, tracestate_string - ) + set_in_carrier(carrier, TRACESTATE_HEADER_NAME, tracestate_string) def _parse_tracestate(header_list: typing.List[str]) -> trace.TraceState: diff --git a/opentelemetry-api/src/opentelemetry/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/propagation/__init__.py index 1b18994a94..81b259004d 100644 --- a/opentelemetry-api/src/opentelemetry/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagation/__init__.py @@ -17,7 +17,8 @@ import opentelemetry.context.propagation.httptextformat as httptextformat import opentelemetry.trace as trace from opentelemetry.context.propagation.tracecontexthttptextformat import ( - TraceContextHTTPTextFormat, + TraceContextHTTPExtractor, + TraceContextHTTPInjector, ) from opentelemetry.context import BaseRuntimeContext @@ -31,7 +32,7 @@ def extract( ) -> typing.Optional[BaseRuntimeContext]: """Load the parent SpanContext from values in the carrier. - Using the specified HTTPTextFormatter, the propagator will + Using the specified HTTPExtractor, 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. @@ -73,31 +74,31 @@ def inject( _HTTP_TEXT_INJECTORS = [ - TraceContextHTTPTextFormat -] # typing.List[httptextformat.HTTPTextFormat] + TraceContextHTTPInjector +] # typing.List[httptextformat.HTTPInjector] _HTTP_TEXT_EXTRACTORS = [ - TraceContextHTTPTextFormat -] # typing.List[httptextformat.HTTPTextFormat] + TraceContextHTTPExtractor +] # typing.List[httptextformat.HTTPExtractor] def set_http_extractors( - extractor_list: typing.List[httptextformat.HTTPTextFormat], + extractor_list: typing.List[httptextformat.HTTPExtractor], ) -> None: global _HTTP_TEXT_EXTRACTORS # pylint:disable=global-statement _HTTP_TEXT_EXTRACTORS = extractor_list def set_http_injectors( - injector_list: typing.List[httptextformat.HTTPTextFormat], + injector_list: typing.List[httptextformat.HTTPInjector], ) -> None: global _HTTP_TEXT_INJECTORS # pylint:disable=global-statement _HTTP_TEXT_INJECTORS = injector_list -def get_http_extractors() -> typing.List[httptextformat.HTTPTextFormat]: +def get_http_extractors() -> typing.List[httptextformat.HTTPExtractor]: return _HTTP_TEXT_EXTRACTORS -def get_http_injectors() -> typing.List[httptextformat.HTTPTextFormat]: +def get_http_injectors() -> typing.List[httptextformat.HTTPInjector]: return _HTTP_TEXT_INJECTORS diff --git a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py index 405c657ed5..b8d5b9a284 100644 --- a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py @@ -19,15 +19,16 @@ from opentelemetry.context import Context from opentelemetry.context.propagation import tracecontexthttptextformat from opentelemetry.context.propagation.tracecontexthttptextformat import ( - TraceContextHTTPTextFormat, + TraceContextHTTPExtractor, + TraceContextHTTPInjector, ) from opentelemetry.trace.propagation.context import ( from_context, with_span_context, ) -INJECT = TraceContextHTTPTextFormat -EXTRACT = TraceContextHTTPTextFormat +INJECT = TraceContextHTTPInjector +EXTRACT = TraceContextHTTPExtractor def get_as_list( 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 7d59fddb9e..24762f470b 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py @@ -15,19 +15,24 @@ import typing import opentelemetry.trace as trace -from opentelemetry.context.propagation.httptextformat import HTTPTextFormat +from opentelemetry.context.propagation.httptextformat import ( + HTTPExtractor, + HTTPInjector, +) -class B3Format(HTTPTextFormat): +TRACE_ID_KEY = "x-b3-traceid" +SPAN_ID_KEY = "x-b3-spanid" +SAMPLED_KEY = "x-b3-sampled" + + +class B3Extractor(HTTPExtractor): """Propagator for the B3 HTTP header format. See: https://github.com/openzipkin/b3-propagation """ SINGLE_HEADER_KEY = "b3" - TRACE_ID_KEY = "x-b3-traceid" - SPAN_ID_KEY = "x-b3-spanid" - SAMPLED_KEY = "x-b3-sampled" FLAGS_KEY = "x-b3-flags" _SAMPLE_PROPAGATE_VALUES = set(["1", "True", "true", "d"]) @@ -60,21 +65,15 @@ def extract(cls, get_from_carrier, carrier): return trace.INVALID_SPAN_CONTEXT else: trace_id = ( - _extract_first_element( - get_from_carrier(carrier, cls.TRACE_ID_KEY) - ) + _extract_first_element(get_from_carrier(carrier, TRACE_ID_KEY)) or trace_id ) span_id = ( - _extract_first_element( - get_from_carrier(carrier, cls.SPAN_ID_KEY) - ) + _extract_first_element(get_from_carrier(carrier, SPAN_ID_KEY)) or span_id ) sampled = ( - _extract_first_element( - get_from_carrier(carrier, cls.SAMPLED_KEY) - ) + _extract_first_element(get_from_carrier(carrier, SAMPLED_KEY)) or sampled ) flags = ( @@ -99,16 +98,16 @@ def extract(cls, get_from_carrier, carrier): trace_state=trace.TraceState(), ) + +class B3Injector(HTTPInjector): @classmethod def inject(cls, context, set_in_carrier, carrier): sampled = (trace.TraceOptions.SAMPLED & context.trace_options) != 0 set_in_carrier( - carrier, cls.TRACE_ID_KEY, format_trace_id(context.trace_id) - ) - set_in_carrier( - carrier, cls.SPAN_ID_KEY, format_span_id(context.span_id) + carrier, TRACE_ID_KEY, format_trace_id(context.trace_id) ) - set_in_carrier(carrier, cls.SAMPLED_KEY, "1" if sampled else "0") + set_in_carrier(carrier, SPAN_ID_KEY, format_span_id(context.span_id)) + set_in_carrier(carrier, SAMPLED_KEY, "1" if sampled else "0") def format_trace_id(trace_id: int) -> str: diff --git a/opentelemetry-sdk/tests/context/propagation/test_b3_format.py b/opentelemetry-sdk/tests/context/propagation/test_b3_format.py index 1215508269..3e8458e0f8 100644 --- a/opentelemetry-sdk/tests/context/propagation/test_b3_format.py +++ b/opentelemetry-sdk/tests/context/propagation/test_b3_format.py @@ -18,7 +18,8 @@ import opentelemetry.sdk.trace as trace import opentelemetry.trace as trace_api -FORMAT = b3_format.B3Format() +INJECTOR = b3_format.B3Injector +EXTRACTOR = b3_format.B3Extractor def get_as_list(dict_object, key): @@ -39,38 +40,38 @@ def setUpClass(cls): def test_extract_multi_header(self): """Test the extraction of B3 headers.""" carrier = { - FORMAT.TRACE_ID_KEY: self.serialized_trace_id, - FORMAT.SPAN_ID_KEY: self.serialized_span_id, - FORMAT.SAMPLED_KEY: "1", + b3_format.TRACE_ID_KEY: self.serialized_trace_id, + b3_format.SPAN_ID_KEY: self.serialized_span_id, + b3_format.SAMPLED_KEY: "1", } - span_context = FORMAT.extract(get_as_list, carrier) + span_context = EXTRACTOR.extract(get_as_list, carrier) new_carrier = {} - FORMAT.inject(span_context, dict.__setitem__, new_carrier) + INJECTOR.inject(span_context, dict.__setitem__, new_carrier) self.assertEqual( - new_carrier[FORMAT.TRACE_ID_KEY], self.serialized_trace_id + new_carrier[b3_format.TRACE_ID_KEY], self.serialized_trace_id ) self.assertEqual( - new_carrier[FORMAT.SPAN_ID_KEY], self.serialized_span_id + new_carrier[b3_format.SPAN_ID_KEY], self.serialized_span_id ) - self.assertEqual(new_carrier[FORMAT.SAMPLED_KEY], "1") + self.assertEqual(new_carrier[b3_format.SAMPLED_KEY], "1") def test_extract_single_header(self): """Test the extraction from a single b3 header.""" carrier = { - FORMAT.SINGLE_HEADER_KEY: "{}-{}".format( + EXTRACTOR.SINGLE_HEADER_KEY: "{}-{}".format( self.serialized_trace_id, self.serialized_span_id ) } - span_context = FORMAT.extract(get_as_list, carrier) + span_context = EXTRACTOR.extract(get_as_list, carrier) new_carrier = {} - FORMAT.inject(span_context, dict.__setitem__, new_carrier) + INJECTOR.inject(span_context, dict.__setitem__, new_carrier) self.assertEqual( - new_carrier[FORMAT.TRACE_ID_KEY], self.serialized_trace_id + new_carrier[b3_format.TRACE_ID_KEY], self.serialized_trace_id ) self.assertEqual( - new_carrier[FORMAT.SPAN_ID_KEY], self.serialized_span_id + new_carrier[b3_format.SPAN_ID_KEY], self.serialized_span_id ) - self.assertEqual(new_carrier[FORMAT.SAMPLED_KEY], "1") + self.assertEqual(new_carrier[b3_format.SAMPLED_KEY], "1") def test_extract_header_precedence(self): """A single b3 header should take precedence over multiple @@ -78,108 +79,108 @@ def test_extract_header_precedence(self): """ single_header_trace_id = self.serialized_trace_id[:-3] + "123" carrier = { - FORMAT.SINGLE_HEADER_KEY: "{}-{}".format( + EXTRACTOR.SINGLE_HEADER_KEY: "{}-{}".format( single_header_trace_id, self.serialized_span_id ), - FORMAT.TRACE_ID_KEY: self.serialized_trace_id, - FORMAT.SPAN_ID_KEY: self.serialized_span_id, - FORMAT.SAMPLED_KEY: "1", + b3_format.TRACE_ID_KEY: self.serialized_trace_id, + b3_format.SPAN_ID_KEY: self.serialized_span_id, + b3_format.SAMPLED_KEY: "1", } - span_context = FORMAT.extract(get_as_list, carrier) + span_context = EXTRACTOR.extract(get_as_list, carrier) new_carrier = {} - FORMAT.inject(span_context, dict.__setitem__, new_carrier) + INJECTOR.inject(span_context, dict.__setitem__, new_carrier) self.assertEqual( - new_carrier[FORMAT.TRACE_ID_KEY], single_header_trace_id + new_carrier[b3_format.TRACE_ID_KEY], single_header_trace_id ) def test_enabled_sampling(self): """Test b3 sample key variants that turn on sampling.""" for variant in ["1", "True", "true", "d"]: carrier = { - FORMAT.TRACE_ID_KEY: self.serialized_trace_id, - FORMAT.SPAN_ID_KEY: self.serialized_span_id, - FORMAT.SAMPLED_KEY: variant, + b3_format.TRACE_ID_KEY: self.serialized_trace_id, + b3_format.SPAN_ID_KEY: self.serialized_span_id, + b3_format.SAMPLED_KEY: variant, } - span_context = FORMAT.extract(get_as_list, carrier) + span_context = EXTRACTOR.extract(get_as_list, carrier) new_carrier = {} - FORMAT.inject(span_context, dict.__setitem__, new_carrier) - self.assertEqual(new_carrier[FORMAT.SAMPLED_KEY], "1") + INJECTOR.inject(span_context, dict.__setitem__, new_carrier) + self.assertEqual(new_carrier[b3_format.SAMPLED_KEY], "1") def test_disabled_sampling(self): """Test b3 sample key variants that turn off sampling.""" for variant in ["0", "False", "false", None]: carrier = { - FORMAT.TRACE_ID_KEY: self.serialized_trace_id, - FORMAT.SPAN_ID_KEY: self.serialized_span_id, - FORMAT.SAMPLED_KEY: variant, + b3_format.TRACE_ID_KEY: self.serialized_trace_id, + b3_format.SPAN_ID_KEY: self.serialized_span_id, + b3_format.SAMPLED_KEY: variant, } - span_context = FORMAT.extract(get_as_list, carrier) + span_context = EXTRACTOR.extract(get_as_list, carrier) new_carrier = {} - FORMAT.inject(span_context, dict.__setitem__, new_carrier) - self.assertEqual(new_carrier[FORMAT.SAMPLED_KEY], "0") + INJECTOR.inject(span_context, dict.__setitem__, new_carrier) + self.assertEqual(new_carrier[b3_format.SAMPLED_KEY], "0") def test_flags(self): """x-b3-flags set to "1" should result in propagation.""" carrier = { - FORMAT.TRACE_ID_KEY: self.serialized_trace_id, - FORMAT.SPAN_ID_KEY: self.serialized_span_id, - FORMAT.FLAGS_KEY: "1", + b3_format.TRACE_ID_KEY: self.serialized_trace_id, + b3_format.SPAN_ID_KEY: self.serialized_span_id, + EXTRACTOR.FLAGS_KEY: "1", } - span_context = FORMAT.extract(get_as_list, carrier) + span_context = EXTRACTOR.extract(get_as_list, carrier) new_carrier = {} - FORMAT.inject(span_context, dict.__setitem__, new_carrier) - self.assertEqual(new_carrier[FORMAT.SAMPLED_KEY], "1") + INJECTOR.inject(span_context, dict.__setitem__, new_carrier) + self.assertEqual(new_carrier[b3_format.SAMPLED_KEY], "1") def test_flags_and_sampling(self): """Propagate if b3 flags and sampling are set.""" carrier = { - FORMAT.TRACE_ID_KEY: self.serialized_trace_id, - FORMAT.SPAN_ID_KEY: self.serialized_span_id, - FORMAT.FLAGS_KEY: "1", + b3_format.TRACE_ID_KEY: self.serialized_trace_id, + b3_format.SPAN_ID_KEY: self.serialized_span_id, + EXTRACTOR.FLAGS_KEY: "1", } - span_context = FORMAT.extract(get_as_list, carrier) + span_context = EXTRACTOR.extract(get_as_list, carrier) new_carrier = {} - FORMAT.inject(span_context, dict.__setitem__, new_carrier) - self.assertEqual(new_carrier[FORMAT.SAMPLED_KEY], "1") + INJECTOR.inject(span_context, dict.__setitem__, new_carrier) + self.assertEqual(new_carrier[b3_format.SAMPLED_KEY], "1") def test_64bit_trace_id(self): """64 bit trace ids should be padded to 128 bit trace ids.""" trace_id_64_bit = self.serialized_trace_id[:16] carrier = { - FORMAT.TRACE_ID_KEY: trace_id_64_bit, - FORMAT.SPAN_ID_KEY: self.serialized_span_id, - FORMAT.FLAGS_KEY: "1", + b3_format.TRACE_ID_KEY: trace_id_64_bit, + b3_format.SPAN_ID_KEY: self.serialized_span_id, + EXTRACTOR.FLAGS_KEY: "1", } - span_context = FORMAT.extract(get_as_list, carrier) + span_context = EXTRACTOR.extract(get_as_list, carrier) new_carrier = {} - FORMAT.inject(span_context, dict.__setitem__, new_carrier) + INJECTOR.inject(span_context, dict.__setitem__, new_carrier) self.assertEqual( - new_carrier[FORMAT.TRACE_ID_KEY], "0" * 16 + trace_id_64_bit + new_carrier[b3_format.TRACE_ID_KEY], "0" * 16 + trace_id_64_bit ) def test_invalid_single_header(self): """If an invalid single header is passed, return an invalid SpanContext. """ - carrier = {FORMAT.SINGLE_HEADER_KEY: "0-1-2-3-4-5-6-7"} - span_context = FORMAT.extract(get_as_list, carrier) + carrier = {EXTRACTOR.SINGLE_HEADER_KEY: "0-1-2-3-4-5-6-7"} + span_context = EXTRACTOR.extract(get_as_list, carrier) self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID) self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID) def test_missing_trace_id(self): """If a trace id is missing, populate an invalid trace id.""" carrier = { - FORMAT.SPAN_ID_KEY: self.serialized_span_id, - FORMAT.FLAGS_KEY: "1", + b3_format.SPAN_ID_KEY: self.serialized_span_id, + EXTRACTOR.FLAGS_KEY: "1", } - span_context = FORMAT.extract(get_as_list, carrier) + span_context = EXTRACTOR.extract(get_as_list, carrier) self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID) def test_missing_span_id(self): """If a trace id is missing, populate an invalid trace id.""" carrier = { - FORMAT.TRACE_ID_KEY: self.serialized_trace_id, - FORMAT.FLAGS_KEY: "1", + b3_format.TRACE_ID_KEY: self.serialized_trace_id, + EXTRACTOR.FLAGS_KEY: "1", } - span_context = FORMAT.extract(get_as_list, carrier) + span_context = EXTRACTOR.extract(get_as_list, carrier) self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID) From 64cfe9bff290a416134faa941e52e892a5e4ca17 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Fri, 13 Dec 2019 09:16:30 -0800 Subject: [PATCH 11/71] checkpoint checkin, this PR will not leave draft mode Signed-off-by: Alex Boten --- .../context_propagation_example.py | 114 ++++++++++-------- .../src/opentelemetry/baggage/__init__.py | 18 +-- .../src/opentelemetry/context/__init__.py | 30 ++++- .../src/opentelemetry/context/base_context.py | 30 ++--- .../context/propagation/__init__.py | 34 +++++- .../context/propagation/httptextformat.py | 4 +- .../distributedcontext/__init__.py | 17 ++- .../src/opentelemetry/propagation/__init__.py | 31 +++-- .../trace/propagation/__init__.py | 2 +- .../trace/propagation/context/__init__.py | 10 +- .../sdk/context/propagation/b3_format.py | 49 +++++--- .../src/opentelemetry/sdk/trace/__init__.py | 13 +- .../sdk/trace/export/__init__.py | 2 +- 13 files changed, 238 insertions(+), 116 deletions(-) diff --git a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py index fe87626f7d..6e3db77643 100644 --- a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py +++ b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py @@ -16,54 +16,57 @@ This module serves as an example for baggage, which exists to pass application-defined key-value pairs from service to service. """ +# import opentelemetry.ext.http_requests +# from opentelemetry.ext.wsgi import OpenTelemetryMiddleware + import flask +from flask import request import requests -import opentelemetry.ext.http_requests -from opentelemetry import propagators, trace -from opentelemetry import baggage -from opnetelemetry.context import Context -from opentelemetry.ext.wsgi import OpenTelemetryMiddleware -from opentelemetry.sdk.context.propagation.b3_format import B3Format +from opentelemetry import propagation, trace +from opentelemetry.distributedcontext import CorrelationContextManager +from opentelemetry.sdk.context.propagation import b3_format from opentelemetry.sdk.trace import Tracer +from opentelemetry.sdk.trace.export import ( + BatchExportSpanProcessor, + ConsoleSpanExporter, +) +from opentelemetry.baggage import BaggageManager def configure_opentelemetry(flask_app: flask.Flask): - """Configure a flask application to use OpenTelemetry. - - This activates the specific components: - - * sets tracer to the SDK's Tracer - * enables requests integration on the Tracer - * uses a WSGI middleware to enable configuration - - TODO: - - * processors? - * exporters? - """ - # Start by configuring all objects required to ensure - # a complete end to end workflow. - # the preferred implementation of these objects must be set, - # as the opentelemetry-api defines the interface with a no-op - # implementation. - trace.set_preferred_tracer_implementation(lambda _: Tracer()) - # extractors and injectors are now separate, as it could be possible - # to want different behavior for those (e.g. don't propagate because of external services) - # - # the current configuration will only propagate w3c/correlationcontext - # and baggage. One would have to add other propagators to handle - # things such as w3c/tracecontext - propagator_list = [CorrelationContextFormat(), BaggageFormat()] - - propagators.set_http_extractors(propagator_list) - propagators.set_http_injectors(propagator_list) - - # Integrations are the glue that binds the OpenTelemetry API - # and the frameworks and libraries that are used together, automatically - # creating Spans and propagating context as appropriate. - opentelemetry.ext.http_requests.enable(trace.tracer()) - flask_app.wsgi_app = OpenTelemetryMiddleware(flask_app.wsgi_app) + trace.set_preferred_tracer_implementation(lambda T: Tracer()) + + # Global initialization + (baggage_extractor, baggage_injector) = BaggageManager.http_propagator() + (b3_extractor, b3_injector) = b3_format.http_propagator() + # propagation.set_http_extractors([b3_extractor, baggage_extractor]) + # propagation.set_http_injectors([b3_injector, baggage_injector]) + propagation.set_http_extractors([b3_extractor]) + propagation.set_http_injectors([b3_injector]) + + # opentelemetry.ext.http_requests.enable(trace.tracer()) + # flask_app.wsgi_app = OpenTelemetryMiddleware(flask_app.wsgi_app) + + +def fetch_from_service_b() -> str: + # Inject the contexts to be propagated. Note that there is no direct + # reference to tracing or baggage. + headers = {"Accept": "application/json"} + propagation.inject(headers) + print(headers) + resp = requests.get("https://opentelemetry.io", headers=headers) + return resp.text + + +def fetch_from_service_c() -> str: + # Inject the contexts to be propagated. Note that there is no direct + # reference to tracing or baggage. + headers = {"Accept": "application/json"} + propagation.inject(headers) + print(headers) + resp = requests.get("https://opentelemetry.io", headers=headers) + return resp.text app = flask.Flask(__name__) @@ -71,11 +74,28 @@ def configure_opentelemetry(flask_app: flask.Flask): @app.route("/") def hello(): - # extract a baggage header - original_service = baggage.get(Context, "original-service") - # add a new one - baggage.set(Context, "environment", "foo") - return "hello" + tracer = trace.tracer() + tracer.add_span_processor(BatchExportSpanProcessor(ConsoleSpanExporter())) + with propagation.extract(request.headers): + # extract a baggage header + with tracer.start_as_current_span("service-span"): + with tracer.start_as_current_span("external-req-span"): + headers = {"Accept": "application/json"} + propagation.inject(headers) + version = CorrelationContextManager.value("version") + if version == "2.0": + return fetch_from_service_c() + + return fetch_from_service_b() + +request_headers = { + "Accept": "application/json", + "x-b3-traceid": "038c3fb613811e30898424c863eeae5a", + "x-b3-spanid": "6c7f9e56212a6ffa", + "x-b3-sampled": "0", +} -configure_opentelemetry(app) +if __name__ == "__main__": + configure_opentelemetry(app) + app.run(debug=True) diff --git a/opentelemetry-api/src/opentelemetry/baggage/__init__.py b/opentelemetry-api/src/opentelemetry/baggage/__init__.py index 88065ec386..cec77d1e30 100644 --- a/opentelemetry-api/src/opentelemetry/baggage/__init__.py +++ b/opentelemetry-api/src/opentelemetry/baggage/__init__.py @@ -12,17 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Tuple from opentelemetry.context import Context + from opentelemetry.context.propagation import ( HTTPExtractor, HTTPInjector, - INVALID_HTTP_EXTRACTOR, - INVALID_HTTP_INJECTOR, ) +# # INVALID_HTTP_EXTRACTOR, +# # INVALID_HTTP_INJECTOR, +# ) + EMPTY_VALUE = "" -INVALID_CONTEXT = Context() +INVALID_CONTEXT = Context class BaggageManager: @@ -67,10 +71,8 @@ def clear(self, ctx: Context) -> Context: # pylint: disable=unused-argument return INVALID_CONTEXT - def http_injector(self) -> HTTPInjector: + @classmethod + def http_propagator(cls) -> Tuple[HTTPExtractor, HTTPInjector]: """ TODO """ - return INVALID_HTTP_INJECTOR + return (HTTPExtractor, HTTPInjector) - def http_extractor(self) -> HTTPExtractor: - """ TODO """ - return INVALID_HTTP_EXTRACTOR diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 9cd42bf557..ef04031b19 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -142,11 +142,37 @@ async def main(): __all__ = ["BaseRuntimeContext", "Context"] +_CONTEXT = None try: from .async_context import AsyncRuntimeContext - Context = AsyncRuntimeContext() # type: BaseRuntimeContext + _CONTEXT = AsyncRuntimeContext() # type: BaseRuntimeContext except ImportError: from .thread_local_context import ThreadLocalRuntimeContext - Context = ThreadLocalRuntimeContext() + _CONTEXT = ThreadLocalRuntimeContext() + + +class Context: + @classmethod + def value(cls, ctx: "BaseRuntimeContext", key: str) -> str: + return ctx.__getitem__(key) + + @classmethod + def set_value( + cls, ctx: "BaseRuntimeContext", key: str, value: "object" + ) -> "BaseRuntimeContext": + # TODO: dont use the context directly here + ctx.__setattr__(key, value) + return ctx + + @classmethod + def current(cls) -> "BaseRuntimeContext": + global _CONTEXT + return _CONTEXT + + @classmethod + def set_current(cls, ctx: "BaseRuntimeContext"): + global _CONTEXT + _CONTEXT = ctx + diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py index a81b477338..5487802a92 100644 --- a/opentelemetry-api/src/opentelemetry/context/base_context.py +++ b/opentelemetry-api/src/opentelemetry/context/base_context.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import types as python_types import threading import typing from contextlib import contextmanager @@ -100,6 +101,17 @@ def __getitem__(self, name: str) -> "object": def __setitem__(self, name: str, value: "object") -> None: self.__setattr__(name, value) + def __enter__(self) -> "BaseRuntimeContext": + return self + + def __exit__( + self, + exc_type: typing.Optional[typing.Type[BaseException]], + exc_val: typing.Optional[BaseException], + exc_tb: typing.Optional[python_types.TracebackType], + ) -> None: + return None + @contextmanager # type: ignore def use(self, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: snapshot = {key: self[key] for key in kwargs} @@ -128,21 +140,3 @@ def call_with_current_context( self.apply(backup_context) return call_with_current_context - - @classmethod - def value(cls, ctx: "BaseRuntimeContext", key: str) -> str: - return ctx.__getitem__(key) - - @classmethod - def set_value( - cls, ctx: "BaseRuntimeContext", key: str, value: "object" - ) -> "BaseRuntimeContext": - # TODO: dont use the context directly here - ctx.__setattr__(key, value) - return ctx - - def current(self) -> "BaseRuntimeContext": - return self.__getitem__("__current_context__") - - def set_current(self, ctx: "BaseRuntimeContext"): - self.__setattr__("__current_context__", ctx) diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py index c8706281ad..0945aa0220 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py @@ -12,7 +12,37 @@ # See the License for the specific language governing permissions and # limitations under the License. +from opentelemetry.context import Context + +# from opentelemetry.context.propagation import Carrier, Getter, Setter + from .binaryformat import BinaryFormat -from .httptextformat import HTTPTextFormat +from .httptextformat import HTTPExtractor, HTTPInjector + +__all__ = ["BinaryFormat", "HTTPExtractor", "HTTPInjector"] + + +# class HTTPExtractor: +# """The default HTTPExtractor. + +# Used when no HTTPExtractor implementation is available. +# """ + +# @staticmethod +# def extract(ctx: Context, carrier: Carrier, getter: Getter) -> Context: +# """ TODO """ +# # pylint: disable=unused-argument +# return ctx + + +# class HTTPInjector: +# """The default HTTPInjector. + +# Used when no HTTPInjector implementation is available. +# """ -__all__ = ["BinaryFormat", "HTTPTextFormat"] +# @staticmethod +# def inject(ctx: Context, carrier: Carrier, setter: Setter) -> Context: +# """ TODO """ +# # pylint: disable=unused-argument +# return ctx diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py index efaead8b8f..8f6ddb9ebf 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py @@ -76,8 +76,8 @@ def example_route(): def extract( cls, context: BaseRuntimeContext, - get_from_carrier: Getter[_T], carrier: _T, + get_from_carrier: typing.Optional[Getter[_T]] = None, ) -> BaseRuntimeContext: """Create a SpanContext from values in the carrier. @@ -105,8 +105,8 @@ class HTTPInjector(abc.ABC): def inject( cls, context: BaseRuntimeContext, - set_in_carrier: Setter[_T], carrier: _T, + set_from_carrier: typing.Optional[Setter[_T]], ) -> None: """Inject values from a SpanContext into a carrier. diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index 0fc5721fb8..c50abc8008 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -15,7 +15,7 @@ import itertools import string import typing -from opentelemetry.context import BaseRuntimeContext +from opentelemetry.context import BaseRuntimeContext, Context PRINTABLE = frozenset( itertools.chain( @@ -89,7 +89,7 @@ def __init__(self, entries: typing.Iterable[Entry]) -> None: @classmethod def set_value( cls, context: BaseRuntimeContext, entry_list: typing.Iterable[Entry] - ): + ) -> None: distributed_context = getattr(context, cls.KEY, {}) for entry in entry_list: distributed_context[entry.key] = entry @@ -113,6 +113,7 @@ def get_entry_value( container = getattr(context, cls.KEY, {}) if key in container: return container[key].value + return None class CorrelationContextManager: @@ -126,3 +127,15 @@ def use_context( self, ctx: CorrelationContext ) -> typing.Iterator[CorrelationContext]: """ TODO """ + + @classmethod + def set_value(cls, key, value, context=None): + if context is None: + return Context.set_value(Context.current(), key, value) + return Context.set_value(context, key, value) + + @classmethod + def value(cls, key, context=None): + if context is None: + return Context.value(Context.current(), key) + return Context.value(context, key) diff --git a/opentelemetry-api/src/opentelemetry/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/propagation/__init__.py index 81b259004d..b97746ecb9 100644 --- a/opentelemetry-api/src/opentelemetry/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagation/__init__.py @@ -14,6 +14,7 @@ import typing +from opentelemetry.context import Context import opentelemetry.context.propagation.httptextformat as httptextformat import opentelemetry.trace as trace from opentelemetry.context.propagation.tracecontexthttptextformat import ( @@ -26,9 +27,11 @@ def extract( - context: BaseRuntimeContext, - get_from_carrier: httptextformat.Getter[_T], carrier: _T, + context: typing.Optional[BaseRuntimeContext] = None, + extractors: typing.Optional[ + typing.List[httptextformat.HTTPExtractor] + ] = None, ) -> typing.Optional[BaseRuntimeContext]: """Load the parent SpanContext from values in the carrier. @@ -45,15 +48,22 @@ def extract( must be paired with an appropriate get_from_carrier which understands how to extract a value from it. """ - for extractor in get_http_extractors(): - return extractor.extract(context, get_from_carrier, carrier) + if context is None: + context = Context.current() + if extractors is None: + extractors = get_http_extractors() + + for extractor in extractors: + return extractor.extract(context, carrier) return None def inject( - context: BaseRuntimeContext, - set_in_carrier: httptextformat.Setter[_T], carrier: _T, + injectors: typing.Optional[ + typing.List[httptextformat.HTTPInjector] + ] = None, + context: typing.Optional[BaseRuntimeContext] = None, ) -> None: """Inject values from the current context into the carrier. @@ -69,8 +79,13 @@ def inject( headers. Should be paired with set_in_carrier, which should know how to set header values on the carrier. """ - for injector in get_http_injectors(): - injector.inject(context, set_in_carrier, carrier) + if context is None: + context = Context.current() + if injectors is None: + injectors = get_http_injectors() + + for injector in injectors: + injector.inject(context, carrier) _HTTP_TEXT_INJECTORS = [ diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py index ae14c05889..25fe69993b 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py @@ -19,6 +19,6 @@ class ContextKeys: KEY = "span-context" @classmethod - def span_context_key(cls): + def span_context_key(cls) -> str: """ TODO """ return cls.KEY diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py index d587000aa0..c6cad0ec27 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py @@ -11,21 +11,23 @@ # 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 typing import Optional from opentelemetry.context import BaseRuntimeContext, Context from opentelemetry.trace import SpanContext from opentelemetry.trace.propagation import ContextKeys -def from_context(ctx: BaseRuntimeContext) -> SpanContext: - return BaseRuntimeContext.value(ctx, ContextKeys.span_context_key(),) +def from_context(ctx: Optional[BaseRuntimeContext] = None) -> SpanContext: + if ctx is None: + ctx = Context.current() + return Context.value(ctx, ContextKeys.span_context_key()) def with_span_context( ctx: BaseRuntimeContext, span_context: SpanContext ) -> BaseRuntimeContext: - return BaseRuntimeContext.set_value( + return Context.set_value( ctx, ContextKeys.span_context_key(), span_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 24762f470b..1201da834b 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py @@ -15,10 +15,14 @@ import typing import opentelemetry.trace as trace -from opentelemetry.context.propagation.httptextformat import ( +from opentelemetry.context.propagation import ( HTTPExtractor, HTTPInjector, ) +from opentelemetry.trace.propagation.context import ( + from_context, + with_span_context, +) TRACE_ID_KEY = "x-b3-traceid" @@ -26,6 +30,19 @@ SAMPLED_KEY = "x-b3-sampled" +def _getter(headers, key): + return headers.get(key) + + +def _setter(headers, key, value): + headers[key] = value + + +def http_propagator() -> typing.Tuple[HTTPExtractor, HTTPInjector]: + """ TODO """ + return B3Extractor, B3Injector + + class B3Extractor(HTTPExtractor): """Propagator for the B3 HTTP header format. @@ -37,7 +54,8 @@ class B3Extractor(HTTPExtractor): _SAMPLE_PROPAGATE_VALUES = set(["1", "True", "true", "d"]) @classmethod - def extract(cls, get_from_carrier, carrier): + def extract(cls, context, carrier, get_from_carrier=_getter): + trace_id = format_trace_id(trace.INVALID_TRACE_ID) span_id = format_span_id(trace.INVALID_SPAN_ID) sampled = "0" @@ -90,23 +108,26 @@ 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 with_span_context( + context, + 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(), + ), ) class B3Injector(HTTPInjector): @classmethod - def inject(cls, context, set_in_carrier, carrier): - sampled = (trace.TraceOptions.SAMPLED & context.trace_options) != 0 - set_in_carrier( - carrier, TRACE_ID_KEY, format_trace_id(context.trace_id) - ) - set_in_carrier(carrier, SPAN_ID_KEY, format_span_id(context.span_id)) + def inject(cls, context, carrier, set_in_carrier=_setter): + sc = from_context(context) + sampled = (trace.TraceOptions.SAMPLED & sc.trace_options) != 0 + set_in_carrier(carrier, TRACE_ID_KEY, format_trace_id(sc.trace_id)) + set_in_carrier(carrier, SPAN_ID_KEY, format_span_id(sc.span_id)) set_in_carrier(carrier, SAMPLED_KEY, "1" if sampled else "0") diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 53ff756b9d..648433d38d 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -26,6 +26,7 @@ from opentelemetry.sdk.util import BoundedDict, BoundedList from opentelemetry.trace import sampling from opentelemetry.trace.propagation.context import ( + ContextKeys, from_context, with_span_context, ) @@ -311,10 +312,9 @@ def __init__( sampler: sampling.Sampler = trace_api.sampling.ALWAYS_ON, shutdown_on_exit: bool = True, ) -> None: - slot_name = "current_span" + self._slot_name = ContextKeys.span_context_key() if name: - slot_name = "{}.current_span".format(name) - self._current_span_slot = Context.register_slot(slot_name) + self._slot_name = "{}.{}".format(name, self._slot_name) self._active_span_processor = MultiSpanProcessor() self.sampler = sampler self._atexit_handler = None @@ -323,7 +323,7 @@ def __init__( def get_current_span(self): """See `opentelemetry.trace.Tracer.get_current_span`.""" - return self._current_span_slot.get() + return Context.value(Context.current(), self._slot_name) def start_as_current_span( self, @@ -420,13 +420,12 @@ def use_span( ) -> Iterator[trace_api.Span]: """See `opentelemetry.trace.Tracer.use_span`.""" try: - span_snapshot = self._current_span_slot.get() - self._current_span_slot.set(span) + span_snapshot = self.get_current_span() with_span_context(Context.current(), span.get_context()) try: yield span finally: - self._current_span_slot.set(span_snapshot) + with_span_context(Context.current(), span_snapshot) finally: if end_on_exit: span.end() diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py index 442b2b2bac..850c53d333 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py @@ -182,7 +182,7 @@ def export(self) -> None: while idx < self.max_export_batch_size and self.queue: self.spans_list[idx] = self.queue.pop() idx += 1 - with Context.use(suppress_instrumentation=True): + with Context.current().use(suppress_instrumentation=True): try: # Ignore type b/c the Optional[None]+slicing is too "clever" # for mypy From 22a5c647b9520f50e39b6708e4d0aeff5aab396a Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Fri, 13 Dec 2019 15:28:50 -0800 Subject: [PATCH 12/71] implement context scoping Signed-off-by: Alex Boten --- .../src/opentelemetry/context/__init__.py | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index ef04031b19..36efbf4604 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -138,6 +138,7 @@ async def main(): asyncio.run(main()) """ +import copy from .base_context import BaseRuntimeContext __all__ = ["BaseRuntimeContext", "Context"] @@ -154,25 +155,43 @@ async def main(): class Context: + def __init__(self): + global _CONTEXT + self.contents = {} + self._id = "{}".format(id(self)) + self._slot = _CONTEXT.register_slot(self._id) + self._slot.set(self) + + def get(self, key): + return self.contents.get(key) + @classmethod - def value(cls, ctx: "BaseRuntimeContext", key: str) -> str: - return ctx.__getitem__(key) + def value(cls, key: str) -> str: + return cls.current().contents.get(key) @classmethod - def set_value( - cls, ctx: "BaseRuntimeContext", key: str, value: "object" - ) -> "BaseRuntimeContext": - # TODO: dont use the context directly here - ctx.__setattr__(key, value) - return ctx + def set_value(cls, key: str, value: "object") -> "Context": + getattr(_CONTEXT, _CONTEXT.current_context.get()).contents[key] = value + return cls.current() @classmethod - def current(cls) -> "BaseRuntimeContext": + def current(cls) -> "Context": global _CONTEXT - return _CONTEXT + if _CONTEXT.current_context is None: + ctx = Context() + cls.set_current(ctx) + snapshot = Context() + snapshot.contents = copy.deepcopy( + getattr(_CONTEXT, _CONTEXT.current_context.get()).contents + ) + return snapshot @classmethod - def set_current(cls, ctx: "BaseRuntimeContext"): + def set_current(cls, ctx: "Context"): global _CONTEXT - _CONTEXT = ctx - + if _CONTEXT.current_context is None: + _CONTEXT.current_context = _CONTEXT.register_slot( + # change the key here + "__current_prop_context__" + ) + _CONTEXT.current_context.set(ctx._id) From 375b78e9fcde8a347312a6d8046ce52ed2a8f29e Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Fri, 13 Dec 2019 15:29:05 -0800 Subject: [PATCH 13/71] minor cleanup --- .../src/opentelemetry/baggage/__init__.py | 4 --- .../context/propagation/__init__.py | 26 ------------------- 2 files changed, 30 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/baggage/__init__.py b/opentelemetry-api/src/opentelemetry/baggage/__init__.py index cec77d1e30..e22e268aeb 100644 --- a/opentelemetry-api/src/opentelemetry/baggage/__init__.py +++ b/opentelemetry-api/src/opentelemetry/baggage/__init__.py @@ -20,10 +20,6 @@ HTTPInjector, ) -# # INVALID_HTTP_EXTRACTOR, -# # INVALID_HTTP_INJECTOR, -# ) - EMPTY_VALUE = "" INVALID_CONTEXT = Context diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py index 0945aa0220..493b0de6f9 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py @@ -20,29 +20,3 @@ from .httptextformat import HTTPExtractor, HTTPInjector __all__ = ["BinaryFormat", "HTTPExtractor", "HTTPInjector"] - - -# class HTTPExtractor: -# """The default HTTPExtractor. - -# Used when no HTTPExtractor implementation is available. -# """ - -# @staticmethod -# def extract(ctx: Context, carrier: Carrier, getter: Getter) -> Context: -# """ TODO """ -# # pylint: disable=unused-argument -# return ctx - - -# class HTTPInjector: -# """The default HTTPInjector. - -# Used when no HTTPInjector implementation is available. -# """ - -# @staticmethod -# def inject(ctx: Context, carrier: Carrier, setter: Setter) -> Context: -# """ TODO """ -# # pylint: disable=unused-argument -# return ctx From 5ca5328c731caf0eb7cb2ddb59175d8dc1f55ca6 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 16 Dec 2019 10:28:13 -0800 Subject: [PATCH 14/71] clean up example code Signed-off-by: Alex Boten --- .../context_propagation_example.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py index 6e3db77643..94b9c7559b 100644 --- a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py +++ b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py @@ -54,7 +54,6 @@ def fetch_from_service_b() -> str: # reference to tracing or baggage. headers = {"Accept": "application/json"} propagation.inject(headers) - print(headers) resp = requests.get("https://opentelemetry.io", headers=headers) return resp.text @@ -64,7 +63,6 @@ def fetch_from_service_c() -> str: # reference to tracing or baggage. headers = {"Accept": "application/json"} propagation.inject(headers) - print(headers) resp = requests.get("https://opentelemetry.io", headers=headers) return resp.text @@ -80,8 +78,6 @@ def hello(): # extract a baggage header with tracer.start_as_current_span("service-span"): with tracer.start_as_current_span("external-req-span"): - headers = {"Accept": "application/json"} - propagation.inject(headers) version = CorrelationContextManager.value("version") if version == "2.0": return fetch_from_service_c() @@ -89,13 +85,6 @@ def hello(): return fetch_from_service_b() -request_headers = { - "Accept": "application/json", - "x-b3-traceid": "038c3fb613811e30898424c863eeae5a", - "x-b3-spanid": "6c7f9e56212a6ffa", - "x-b3-sampled": "0", -} - if __name__ == "__main__": configure_opentelemetry(app) app.run(debug=True) From b47593366e9c310dffd7f4ce8bebf943fb8a1d5b Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 16 Dec 2019 10:32:12 -0800 Subject: [PATCH 15/71] pass context where needed Signed-off-by: Alex Boten --- .../ext/opentracing_shim/__init__.py | 2 +- .../src/opentelemetry/context/__init__.py | 23 +++++++++++++++++-- .../distributedcontext/__init__.py | 10 +++----- .../trace/propagation/context/__init__.py | 14 ++++------- .../test_tracecontexthttptextformat.py | 6 ++--- .../sdk/context/propagation/b3_format.py | 1 - .../src/opentelemetry/sdk/trace/__init__.py | 18 +++++++++------ 7 files changed, 42 insertions(+), 32 deletions(-) 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 fedce2536a..25cdbe7e83 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 @@ -668,7 +668,7 @@ def inject(self, span_context, format, carrier): if format not in self._supported_formats: raise opentracing.UnsupportedFormatException - ctx = with_span_context(Context.current(), span_context.unwrap()) + ctx = with_span_context(span_context.unwrap()) propagation.inject(ctx, type(carrier).__setitem__, carrier) def extract(self, format, carrier): diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 36efbf4604..b04a1a7f6d 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -139,6 +139,8 @@ async def main(): """ import copy +import typing + from .base_context import BaseRuntimeContext __all__ = ["BaseRuntimeContext", "Context"] @@ -162,12 +164,24 @@ def __init__(self): self._slot = _CONTEXT.register_slot(self._id) self._slot.set(self) + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + def get(self, key): return self.contents.get(key) @classmethod - def value(cls, key: str) -> str: - return cls.current().contents.get(key) + def value( + cls, key: str, context: typing.Optional["Context"] = None + ) -> str: + if context is None: + return getattr( + _CONTEXT, _CONTEXT.current_context.get() + ).contents.get(key) + return context.contents.get(key) @classmethod def set_value(cls, key: str, value: "object") -> "Context": @@ -195,3 +209,8 @@ def set_current(cls, ctx: "Context"): "__current_prop_context__" ) _CONTEXT.current_context.set(ctx._id) + + @classmethod + def use(self, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: + """TODO: do we want this passthrough here? Review where use is used """ + return _CONTEXT.use() diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index c50abc8008..b6c3f85c9c 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -129,13 +129,9 @@ def use_context( """ TODO """ @classmethod - def set_value(cls, key, value, context=None): - if context is None: - return Context.set_value(Context.current(), key, value) - return Context.set_value(context, key, value) + def set_value(cls, key, value): + return Context.set_value(key, value) @classmethod def value(cls, key, context=None): - if context is None: - return Context.value(Context.current(), key) - return Context.value(context, key) + return Context.value(key, context=context) diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py index c6cad0ec27..d57b8fe7b1 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py @@ -18,16 +18,10 @@ from opentelemetry.trace.propagation import ContextKeys -def from_context(ctx: Optional[BaseRuntimeContext] = None) -> SpanContext: - if ctx is None: - ctx = Context.current() - return Context.value(ctx, ContextKeys.span_context_key()) +def from_context(ctx: Optional[Context] = None) -> SpanContext: + return Context.value(ContextKeys.span_context_key(), context=ctx) -def with_span_context( - ctx: BaseRuntimeContext, span_context: SpanContext -) -> BaseRuntimeContext: - return Context.set_value( - ctx, ContextKeys.span_context_key(), span_context, - ) +def with_span_context(span_context: SpanContext) -> BaseRuntimeContext: + return Context.set_value(ContextKeys.span_context_key(), span_context) diff --git a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py index b8d5b9a284..8e297ec34d 100644 --- a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py @@ -151,9 +151,7 @@ def test_no_send_empty_tracestate(self): Empty and whitespace-only list members are allowed. Vendors MUST accept empty tracestate headers but SHOULD avoid sending them. """ - ctx = with_span_context( - self.ctx, trace.SpanContext(self.TRACE_ID, self.SPAN_ID) - ) + ctx = with_span_context(trace.SpanContext(self.TRACE_ID, self.SPAN_ID)) output = {} # type:typing.Dict[str, str] INJECT.inject( ctx, dict.__setitem__, output, @@ -185,7 +183,7 @@ def test_propagate_invalid_context(self): """Do not propagate invalid trace context. """ output = {} # type:typing.Dict[str, str] - ctx = with_span_context(self.ctx, trace.INVALID_SPAN_CONTEXT) + ctx = with_span_context(trace.INVALID_SPAN_CONTEXT) INJECT.inject(ctx, dict.__setitem__, output) self.assertFalse("traceparent" in output) 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 1201da834b..fcb7f5cdd9 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py @@ -110,7 +110,6 @@ def extract(cls, context, carrier, get_from_carrier=_getter): options |= trace.TraceOptions.SAMPLED return with_span_context( - context, trace.SpanContext( # trace an span ids are encoded in hex, so must be converted trace_id=int(trace_id, 16), diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 648433d38d..b34c3512c7 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -321,9 +321,9 @@ def __init__( if shutdown_on_exit: self._atexit_handler = atexit.register(self.shutdown) - def get_current_span(self): + def get_current_span(self, context=None): """See `opentelemetry.trace.Tracer.get_current_span`.""" - return Context.value(Context.current(), self._slot_name) + return Context.value(self._slot_name, context=context) def start_as_current_span( self, @@ -332,10 +332,13 @@ def start_as_current_span( kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL, attributes: Optional[types.Attributes] = None, links: Sequence[trace_api.Link] = (), + context: Optional[Context] = None, ) -> Iterator[trace_api.Span]: """See `opentelemetry.trace.Tracer.start_as_current_span`.""" - span = self.start_span(name, parent, kind, attributes, links) + span = self.start_span( + name, parent, kind, attributes, links, context=context + ) return self.use_span(span, end_on_exit=True) def start_span( @@ -346,18 +349,19 @@ def start_span( attributes: Optional[types.Attributes] = None, links: Sequence[trace_api.Link] = (), start_time: Optional[int] = None, + context: Optional[Context] = None, ) -> "Span": """See `opentelemetry.trace.Tracer.start_span`.""" if parent is Tracer.CURRENT_SPAN: - parent = self.get_current_span() + parent = self.get_current_span(context=context) parent_context = parent if isinstance(parent_context, trace_api.Span): parent_context = parent.get_context() if parent_context is None: - parent_context = from_context(Context.current()) + parent_context = from_context(context) if parent_context is not None and not isinstance( parent_context, trace_api.SpanContext @@ -421,11 +425,11 @@ def use_span( """See `opentelemetry.trace.Tracer.use_span`.""" try: span_snapshot = self.get_current_span() - with_span_context(Context.current(), span.get_context()) + with_span_context(span.get_context()) try: yield span finally: - with_span_context(Context.current(), span_snapshot) + with_span_context(span_snapshot) finally: if end_on_exit: span.end() From c4829c413e84278ac49e0be8b08fb63e4211d7b2 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 16 Dec 2019 14:41:17 -0800 Subject: [PATCH 16/71] removing baggage module Signed-off-by: Alex Boten --- .../context_propagation_example.py | 2 - .../src/opentelemetry/baggage/__init__.py | 74 ------------------- .../baggage/propagation/__init__.py | 53 ------------- 3 files changed, 129 deletions(-) delete mode 100644 opentelemetry-api/src/opentelemetry/baggage/__init__.py delete mode 100644 opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py diff --git a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py index 94b9c7559b..40448ec38b 100644 --- a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py +++ b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py @@ -31,14 +31,12 @@ BatchExportSpanProcessor, ConsoleSpanExporter, ) -from opentelemetry.baggage import BaggageManager def configure_opentelemetry(flask_app: flask.Flask): trace.set_preferred_tracer_implementation(lambda T: Tracer()) # Global initialization - (baggage_extractor, baggage_injector) = BaggageManager.http_propagator() (b3_extractor, b3_injector) = b3_format.http_propagator() # propagation.set_http_extractors([b3_extractor, baggage_extractor]) # propagation.set_http_injectors([b3_injector, baggage_injector]) diff --git a/opentelemetry-api/src/opentelemetry/baggage/__init__.py b/opentelemetry-api/src/opentelemetry/baggage/__init__.py deleted file mode 100644 index e22e268aeb..0000000000 --- a/opentelemetry-api/src/opentelemetry/baggage/__init__.py +++ /dev/null @@ -1,74 +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 typing import Tuple -from opentelemetry.context import Context - -from opentelemetry.context.propagation import ( - HTTPExtractor, - HTTPInjector, -) - - -EMPTY_VALUE = "" -INVALID_CONTEXT = Context - - -class BaggageManager: - """ TODO """ - - def set_value(self, ctx: Context, key: str, value: str) -> Context: - """ - Sets a value on a Context - - Args: - ctx: Context to modify - key: Key of the value to set - value: Value used to update the context - - Return: - Context: Updated context - """ - # pylint: disable=unused-argument - return ctx - - def value(self, ctx: Context, key: str) -> str: - """ - Gets a value on a Context - - Args: - ctx: Context to query - key: Key of the value to get - - Return: - str: Value of the key - """ - # pylint: disable=unused-argument - return EMPTY_VALUE - - def remove_value(self, ctx: Context, key: str) -> Context: - """ TODO """ - # pylint: disable=unused-argument - return INVALID_CONTEXT - - def clear(self, ctx: Context) -> Context: - """ TODO """ - # pylint: disable=unused-argument - return INVALID_CONTEXT - - @classmethod - def http_propagator(cls) -> Tuple[HTTPExtractor, HTTPInjector]: - """ TODO """ - return (HTTPExtractor, HTTPInjector) - diff --git a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py deleted file mode 100644 index 1494939100..0000000000 --- a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py +++ /dev/null @@ -1,53 +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 opentelemetry.context import Context -from opentelemetry.context.propagation import Carrier, Getter, Setter - - -class ContextKeys: - """ TODO """ - - KEY = "baggage" - - @classmethod - def span_context_key(cls): - """ TODO """ - return cls.KEY - - -class HTTPExtractor: - """The default HTTPExtractor. - - Used when no HTTPExtractor implementation is available. - """ - - @staticmethod - def extract(ctx: Context, carrier: Carrier, getter: Getter) -> Context: - """ TODO """ - # pylint: disable=unused-argument - return ctx - - -class HTTPInjector: - """The default HTTPInjector. - - Used when no HTTPInjector implementation is available. - """ - - @staticmethod - def inject(ctx: Context, carrier: Carrier, setter: Setter) -> Context: - """ TODO """ - # pylint: disable=unused-argument - return ctx From c7610fa44c97ec93a075ee9d2ae0e19e28e1fc3b Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 16 Dec 2019 14:44:24 -0800 Subject: [PATCH 17/71] small lint improvements Signed-off-by: Alex Boten --- opentelemetry-api/src/opentelemetry/context/base_context.py | 2 +- .../src/opentelemetry/context/propagation/__init__.py | 5 +++-- .../src/opentelemetry/distributedcontext/__init__.py | 1 + .../src/opentelemetry/trace/propagation/context/__init__.py | 1 - .../src/opentelemetry/sdk/context/propagation/b3_format.py | 6 +----- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py index 5487802a92..1167f022c9 100644 --- a/opentelemetry-api/src/opentelemetry/context/base_context.py +++ b/opentelemetry-api/src/opentelemetry/context/base_context.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import types as python_types import threading +import types as python_types import typing from contextlib import contextmanager diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py index 493b0de6f9..030f089781 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py @@ -14,9 +14,10 @@ from opentelemetry.context import Context -# from opentelemetry.context.propagation import Carrier, Getter, Setter - from .binaryformat import BinaryFormat from .httptextformat import HTTPExtractor, HTTPInjector +# from opentelemetry.context.propagation import Carrier, Getter, Setter + + __all__ = ["BinaryFormat", "HTTPExtractor", "HTTPInjector"] diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index b6c3f85c9c..553ccdf852 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -15,6 +15,7 @@ import itertools import string import typing + from opentelemetry.context import BaseRuntimeContext, Context PRINTABLE = frozenset( diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py index d57b8fe7b1..d1d6cde504 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py @@ -24,4 +24,3 @@ def from_context(ctx: Optional[Context] = None) -> SpanContext: def with_span_context(span_context: SpanContext) -> BaseRuntimeContext: return Context.set_value(ContextKeys.span_context_key(), span_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 fcb7f5cdd9..22364abc3c 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py @@ -15,16 +15,12 @@ import typing import opentelemetry.trace as trace -from opentelemetry.context.propagation import ( - HTTPExtractor, - HTTPInjector, -) +from opentelemetry.context.propagation import HTTPExtractor, HTTPInjector from opentelemetry.trace.propagation.context import ( from_context, with_span_context, ) - TRACE_ID_KEY = "x-b3-traceid" SPAN_ID_KEY = "x-b3-spanid" SAMPLED_KEY = "x-b3-sampled" From 7489eb5f56048cf05610faaf23240d9e4db6c9a8 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 16 Dec 2019 15:12:50 -0800 Subject: [PATCH 18/71] more lint fixes Signed-off-by: Alex Boten --- .../context_propagation_example.py | 2 +- .../tests/test_shim.py | 4 ++-- .../src/opentelemetry/context/__init__.py | 13 +++++-------- .../context/propagation/httptextformat.py | 11 +++++------ .../propagation/tracecontexthttptextformat.py | 4 ++-- .../src/opentelemetry/propagation/__init__.py | 13 ++++++------- .../src/opentelemetry/trace/__init__.py | 9 +++++++-- .../propagation/test_tracecontexthttptextformat.py | 1 - .../src/opentelemetry/sdk/trace/__init__.py | 2 +- 9 files changed, 29 insertions(+), 30 deletions(-) diff --git a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py index 40448ec38b..d3ad51763c 100644 --- a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py +++ b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py @@ -20,8 +20,8 @@ # from opentelemetry.ext.wsgi import OpenTelemetryMiddleware import flask -from flask import request import requests +from flask import request from opentelemetry import propagation, trace from opentelemetry.distributedcontext import CorrelationContextManager diff --git a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py index 925de5e907..40ef3c87a1 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -545,7 +545,7 @@ class MockHTTPExtractor(HTTPExtractor): """Mock extractor for testing purposes.""" @classmethod - def extract(cls, context, get_from_carrier, carrier): + def extract(cls, context, carrier, get_from_carrier=None): trace_id_list = get_from_carrier(carrier, _TRACE_ID_KEY) span_id_list = get_from_carrier(carrier, _SPAN_ID_KEY) @@ -561,6 +561,6 @@ class MockHTTPInjector(HTTPInjector): """Mock injector for testing purposes.""" @classmethod - def inject(cls, context, set_in_carrier, carrier): + def inject(cls, context, carrier, set_in_carrier=None): set_in_carrier(carrier, _TRACE_ID_KEY, str(context.trace_id)) set_in_carrier(carrier, _SPAN_ID_KEY, str(context.span_id)) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index b04a1a7f6d..fac4c33b69 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -158,10 +158,9 @@ async def main(): class Context: def __init__(self): - global _CONTEXT self.contents = {} - self._id = "{}".format(id(self)) - self._slot = _CONTEXT.register_slot(self._id) + self.slot_name = "{}".format(id(self)) + self._slot = _CONTEXT.register_slot(self.slot_name) self._slot.set(self) def __enter__(self): @@ -190,7 +189,6 @@ def set_value(cls, key: str, value: "object") -> "Context": @classmethod def current(cls) -> "Context": - global _CONTEXT if _CONTEXT.current_context is None: ctx = Context() cls.set_current(ctx) @@ -202,15 +200,14 @@ def current(cls) -> "Context": @classmethod def set_current(cls, ctx: "Context"): - global _CONTEXT if _CONTEXT.current_context is None: _CONTEXT.current_context = _CONTEXT.register_slot( # change the key here "__current_prop_context__" ) - _CONTEXT.current_context.set(ctx._id) + _CONTEXT.current_context.set(ctx.slot_name) @classmethod - def use(self, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: + def use(cls, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: """TODO: do we want this passthrough here? Review where use is used """ - return _CONTEXT.use() + return _CONTEXT.use(**kwargs) diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py index 8f6ddb9ebf..282f5dee37 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py @@ -15,8 +15,7 @@ import abc import typing -from opentelemetry.trace import SpanContext -from opentelemetry.context import BaseRuntimeContext +from opentelemetry.context import Context _T = typing.TypeVar("_T") @@ -75,10 +74,10 @@ def example_route(): @abc.abstractmethod def extract( cls, - context: BaseRuntimeContext, + context: Context, carrier: _T, get_from_carrier: typing.Optional[Getter[_T]] = None, - ) -> BaseRuntimeContext: + ) -> Context: """Create a SpanContext from values in the carrier. The extract function should retrieve values from the carrier @@ -104,9 +103,9 @@ class HTTPInjector(abc.ABC): @abc.abstractmethod def inject( cls, - context: BaseRuntimeContext, + context: Context, carrier: _T, - set_from_carrier: typing.Optional[Setter[_T]], + set_in_carrier: typing.Optional[Setter[_T]], ) -> None: """Inject values from a SpanContext into a carrier. diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py index a697b2b6d5..3497b9921f 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py @@ -67,8 +67,8 @@ class TraceContextHTTPExtractor(httptextformat.HTTPExtractor): def extract( cls, context: BaseRuntimeContext, - get_from_carrier: httptextformat.Getter[_T], carrier: _T, + get_from_carrier: typing.Optional[httptextformat.Getter[_T]] = None, ) -> BaseRuntimeContext: """Extracts a valid SpanContext from the carrier. """ @@ -120,8 +120,8 @@ class TraceContextHTTPInjector(httptextformat.HTTPInjector): def inject( cls, context: BaseRuntimeContext, - set_in_carrier: httptextformat.Setter[_T], carrier: _T, + set_in_carrier: typing.Optional[httptextformat.Setter[_T]] = None, ) -> None: sc = BaseRuntimeContext.value(context, ContextKeys.span_context_key()) if sc is None or sc == trace.INVALID_SPAN_CONTEXT: diff --git a/opentelemetry-api/src/opentelemetry/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/propagation/__init__.py index b97746ecb9..228fbbce23 100644 --- a/opentelemetry-api/src/opentelemetry/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagation/__init__.py @@ -14,25 +14,24 @@ import typing -from opentelemetry.context import Context import opentelemetry.context.propagation.httptextformat as httptextformat import opentelemetry.trace as trace +from opentelemetry.context import Context from opentelemetry.context.propagation.tracecontexthttptextformat import ( TraceContextHTTPExtractor, TraceContextHTTPInjector, ) -from opentelemetry.context import BaseRuntimeContext _T = typing.TypeVar("_T") def extract( carrier: _T, - context: typing.Optional[BaseRuntimeContext] = None, + context: typing.Optional[Context] = None, extractors: typing.Optional[ typing.List[httptextformat.HTTPExtractor] ] = None, -) -> typing.Optional[BaseRuntimeContext]: +) -> typing.Optional[Context]: """Load the parent SpanContext from values in the carrier. Using the specified HTTPExtractor, the propagator will @@ -54,7 +53,7 @@ def extract( extractors = get_http_extractors() for extractor in extractors: - return extractor.extract(context, carrier) + return extractor.extract(context=context, carrier=carrier) return None @@ -63,7 +62,7 @@ def inject( injectors: typing.Optional[ typing.List[httptextformat.HTTPInjector] ] = None, - context: typing.Optional[BaseRuntimeContext] = None, + context: typing.Optional[Context] = None, ) -> None: """Inject values from the current context into the carrier. @@ -85,7 +84,7 @@ def inject( injectors = get_http_injectors() for injector in injectors: - injector.inject(context, carrier) + injector.inject(context=context, carrier=carrier) _HTTP_TEXT_INJECTORS = [ diff --git a/opentelemetry-api/src/opentelemetry/trace/__init__.py b/opentelemetry-api/src/opentelemetry/trace/__init__.py index 27361b9a43..5168c2a968 100644 --- a/opentelemetry-api/src/opentelemetry/trace/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/__init__.py @@ -64,6 +64,7 @@ import typing from contextlib import contextmanager +from opentelemetry.context import Context from opentelemetry.trace.status import Status from opentelemetry.util import loader, types @@ -375,7 +376,9 @@ class Tracer: # This is the default behavior when creating spans. CURRENT_SPAN = Span() - def get_current_span(self) -> "Span": + def get_current_span( + self, context: typing.Optional[Context] = None + ) -> "Span": """Gets the currently active span from the context. If there is no current span, return a placeholder span with an invalid @@ -385,7 +388,7 @@ def get_current_span(self) -> "Span": The currently active :class:`.Span`, or a placeholder span with an invalid :class:`.SpanContext`. """ - # pylint: disable=no-self-use + # pylint: disable=unused-argument,no-self-use return INVALID_SPAN def start_span( @@ -396,6 +399,7 @@ def start_span( attributes: typing.Optional[types.Attributes] = None, links: typing.Sequence[Link] = (), start_time: typing.Optional[int] = None, + context: typing.Optional[Context] = None, ) -> "Span": """Starts a span. @@ -442,6 +446,7 @@ def start_as_current_span( kind: SpanKind = SpanKind.INTERNAL, attributes: typing.Optional[types.Attributes] = None, links: typing.Sequence[Link] = (), + context: typing.Optional[Context] = None, ) -> typing.Iterator["Span"]: """Context manager for creating a new span and set it as the current span in this tracer's context. diff --git a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py index 8e297ec34d..29bdb72088 100644 --- a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py @@ -17,7 +17,6 @@ from opentelemetry import trace from opentelemetry.context import Context -from opentelemetry.context.propagation import tracecontexthttptextformat from opentelemetry.context.propagation.tracecontexthttptextformat import ( TraceContextHTTPExtractor, TraceContextHTTPInjector, diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index b34c3512c7..54580eab08 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -321,7 +321,7 @@ def __init__( if shutdown_on_exit: self._atexit_handler = atexit.register(self.shutdown) - def get_current_span(self, context=None): + def get_current_span(self, context: Optional[Context] = None): """See `opentelemetry.trace.Tracer.get_current_span`.""" return Context.value(self._slot_name, context=context) From a4c41503d603746cb07e2adc158dc5cf36834043 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 16 Dec 2019 16:11:00 -0800 Subject: [PATCH 19/71] fixing a few more lint issues Signed-off-by: Alex Boten --- opentelemetry-api/src/opentelemetry/context/__init__.py | 8 +++----- .../src/opentelemetry/distributedcontext/__init__.py | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index fac4c33b69..e97e2fcec5 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -169,6 +169,9 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): pass + def __getattr__(self, name: str) -> "object": + return _CONTEXT.__getattr__(name) + def get(self, key): return self.contents.get(key) @@ -206,8 +209,3 @@ def set_current(cls, ctx: "Context"): "__current_prop_context__" ) _CONTEXT.current_context.set(ctx.slot_name) - - @classmethod - def use(cls, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: - """TODO: do we want this passthrough here? Review where use is used """ - return _CONTEXT.use(**kwargs) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py index 553ccdf852..99fe361834 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py @@ -125,7 +125,7 @@ def http_text_format(self): """ TODO """ def use_context( - self, ctx: CorrelationContext + self, context: CorrelationContext ) -> typing.Iterator[CorrelationContext]: """ TODO """ From 09468464839eb0449eb08aa5c07ce8bd5f885a40 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 16 Dec 2019 17:08:33 -0800 Subject: [PATCH 20/71] fixing inject/extract signatures and tracecontext tests Signed-off-by: Alex Boten --- .../tests/test_shim.py | 4 +- .../context/propagation/httptextformat.py | 8 ++-- .../propagation/tracecontexthttptextformat.py | 39 +++++++++++-------- .../trace/propagation/context/__init__.py | 4 +- .../test_tracecontexthttptextformat.py | 32 +++++++-------- .../sdk/context/propagation/b3_format.py | 4 +- 6 files changed, 50 insertions(+), 41 deletions(-) diff --git a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py index 40ef3c87a1..73f921e20a 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -545,7 +545,7 @@ class MockHTTPExtractor(HTTPExtractor): """Mock extractor for testing purposes.""" @classmethod - def extract(cls, context, carrier, get_from_carrier=None): + def extract(cls, carrier, context=None, get_from_carrier=None): trace_id_list = get_from_carrier(carrier, _TRACE_ID_KEY) span_id_list = get_from_carrier(carrier, _SPAN_ID_KEY) @@ -561,6 +561,6 @@ class MockHTTPInjector(HTTPInjector): """Mock injector for testing purposes.""" @classmethod - def inject(cls, context, carrier, set_in_carrier=None): + def inject(cls, carrier, context=None, set_in_carrier=None): set_in_carrier(carrier, _TRACE_ID_KEY, str(context.trace_id)) set_in_carrier(carrier, _SPAN_ID_KEY, str(context.span_id)) diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py index 282f5dee37..b49ee8c16d 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py @@ -26,6 +26,8 @@ class HTTPExtractor(abc.ABC): """API for propagation of span context via headers. + TODO: update docs to reflect split into extractor/injector + 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 @@ -74,8 +76,8 @@ def example_route(): @abc.abstractmethod def extract( cls, - context: Context, carrier: _T, + context: typing.Optional[Context] = None, get_from_carrier: typing.Optional[Getter[_T]] = None, ) -> Context: """Create a SpanContext from values in the carrier. @@ -103,9 +105,9 @@ class HTTPInjector(abc.ABC): @abc.abstractmethod def inject( cls, - context: Context, carrier: _T, - set_in_carrier: typing.Optional[Setter[_T]], + context: typing.Optional[Context] = None, + set_in_carrier: typing.Optional[Setter[_T]] = None, ) -> None: """Inject values from a SpanContext into a carrier. diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py index 3497b9921f..f4b2756ed6 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py @@ -16,9 +16,13 @@ import typing import opentelemetry.trace as trace -from opentelemetry.context import BaseRuntimeContext -from opentelemetry.context.propagation import httptextformat -from opentelemetry.trace.propagation import ContextKeys +from opentelemetry.context import Context +from opentelemetry.context.propagation import HTTPExtractor, HTTPInjector +from opentelemetry.context.propagation.httptextformat import Getter, Setter +from opentelemetry.trace.propagation.context import ( + from_context, + with_span_context, +) _T = typing.TypeVar("_T") @@ -53,7 +57,7 @@ TRACESTATE_HEADER_NAME = "tracestate" -class TraceContextHTTPExtractor(httptextformat.HTTPExtractor): +class TraceContextHTTPExtractor(HTTPExtractor): """Extracts using w3c TraceContext's headers. """ @@ -66,10 +70,10 @@ class TraceContextHTTPExtractor(httptextformat.HTTPExtractor): @classmethod def extract( cls, - context: BaseRuntimeContext, carrier: _T, - get_from_carrier: typing.Optional[httptextformat.Getter[_T]] = None, - ) -> BaseRuntimeContext: + context: typing.Optional[Context] = None, + get_from_carrier: typing.Optional[Getter[_T]] = None, + ) -> Context: """Extracts a valid SpanContext from the carrier. """ header = get_from_carrier(carrier, TRACEPARENT_HEADER_NAME) @@ -105,27 +109,30 @@ def extract( trace_state=tracestate, ) - ctx = BaseRuntimeContext.set_value( - context, ContextKeys.span_context_key(), span_context - ) - - return ctx + return with_span_context(span_context) -class TraceContextHTTPInjector(httptextformat.HTTPInjector): +class TraceContextHTTPInjector(HTTPInjector): """Injects using w3c TraceContext's headers. """ @classmethod def inject( cls, - context: BaseRuntimeContext, carrier: _T, - set_in_carrier: typing.Optional[httptextformat.Setter[_T]] = None, + context: typing.Optional[Context] = None, + set_in_carrier: typing.Optional[Setter[_T]] = None, ) -> None: - sc = BaseRuntimeContext.value(context, ContextKeys.span_context_key()) + sc = from_context(context) if sc is None or sc == trace.INVALID_SPAN_CONTEXT: return + + if ( + sc.trace_id == trace.INVALID_TRACE_ID + or sc.span_id == trace.INVALID_SPAN_ID + ): + return + traceparent_string = "00-{:032x}-{:016x}-{:02x}".format( sc.trace_id, sc.span_id, sc.trace_options, ) diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py index d1d6cde504..82ce0bbc3f 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py @@ -13,7 +13,7 @@ # limitations under the License. from typing import Optional -from opentelemetry.context import BaseRuntimeContext, Context +from opentelemetry.context import Context from opentelemetry.trace import SpanContext from opentelemetry.trace.propagation import ContextKeys @@ -22,5 +22,5 @@ def from_context(ctx: Optional[Context] = None) -> SpanContext: return Context.value(ContextKeys.span_context_key(), context=ctx) -def with_span_context(span_context: SpanContext) -> BaseRuntimeContext: +def with_span_context(span_context: SpanContext) -> Context: return Context.set_value(ContextKeys.span_context_key(), span_context) diff --git a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py index 29bdb72088..132649c63d 100644 --- a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py @@ -53,7 +53,7 @@ def test_no_traceparent_header(self): If no traceparent header is received, the vendor creates a new trace-id and parent-id that represents the current request. """ output = {} # type:typing.Dict[str, typing.List[str]] - span_context = EXTRACT.extract(self.ctx, get_as_list, output) + span_context = EXTRACT.extract(output, self.ctx, get_as_list) self.assertTrue(isinstance(span_context, trace.SpanContext)) def test_headers_with_tracestate(self): @@ -66,12 +66,12 @@ def test_headers_with_tracestate(self): ) tracestate_value = "foo=1,bar=2,baz=3" ctx = EXTRACT.extract( - self.ctx, - get_as_list, { "traceparent": [traceparent_value], "tracestate": [tracestate_value], }, + self.ctx, + get_as_list, ) span_context = from_context(ctx) self.assertEqual(span_context.trace_id, self.TRACE_ID) @@ -81,7 +81,7 @@ def test_headers_with_tracestate(self): ) output = {} # type:typing.Dict[str, str] - INJECT.inject(self.ctx, dict.__setitem__, output) + INJECT.inject(output, ctx, dict.__setitem__) self.assertEqual(output["traceparent"], traceparent_value) for pair in ["foo=1", "bar=2", "baz=3"]: self.assertIn(pair, output["tracestate"]) @@ -104,14 +104,14 @@ 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 = EXTRACT.extract( - self.ctx, - get_as_list, { "traceparent": [ "00-00000000000000000000000000000000-1234567890123456-00" ], "tracestate": ["foo=1,bar=2,foo=3"], }, + self.ctx, + get_as_list, ) self.assertEqual(span_context, trace.INVALID_SPAN_CONTEXT) @@ -131,14 +131,14 @@ 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 = EXTRACT.extract( - self.ctx, - get_as_list, { "traceparent": [ "00-00000000000000000000000000000000-0000000000000000-00" ], "tracestate": ["foo=1,bar=2,foo=3"], }, + self.ctx, + get_as_list, ) self.assertEqual(span_context, trace.INVALID_SPAN_CONTEXT) @@ -153,7 +153,7 @@ def test_no_send_empty_tracestate(self): ctx = with_span_context(trace.SpanContext(self.TRACE_ID, self.SPAN_ID)) output = {} # type:typing.Dict[str, str] INJECT.inject( - ctx, dict.__setitem__, output, + output, ctx, dict.__setitem__, ) self.assertTrue("traceparent" in output) self.assertFalse("tracestate" in output) @@ -167,14 +167,14 @@ def test_format_not_supported(self): If the version cannot be parsed, return an invalid trace header. """ span_context = EXTRACT.extract( - self.ctx, - get_as_list, { "traceparent": [ "00-12345678901234567890123456789012-1234567890123456-00-residue" ], "tracestate": ["foo=1,bar=2,foo=3"], }, + self.ctx, + get_as_list, ) self.assertEqual(span_context, trace.INVALID_SPAN_CONTEXT) @@ -183,20 +183,20 @@ def test_propagate_invalid_context(self): """ output = {} # type:typing.Dict[str, str] ctx = with_span_context(trace.INVALID_SPAN_CONTEXT) - INJECT.inject(ctx, dict.__setitem__, output) + INJECT.inject(output, ctx, dict.__setitem__) self.assertFalse("traceparent" in output) def test_tracestate_empty_header(self): """Test tracestate with an additional empty header (should be ignored)""" ctx = EXTRACT.extract( - self.ctx, - get_as_list, { "traceparent": [ "00-12345678901234567890123456789012-1234567890123456-00" ], "tracestate": ["foo=1", ""], }, + self.ctx, + get_as_list, ) span_context = from_context(ctx) self.assertEqual(span_context.trace_state["foo"], "1") @@ -205,14 +205,14 @@ def test_tracestate_header_with_trailing_comma(self): """Do not propagate invalid trace context. """ ctx = EXTRACT.extract( - self.ctx, - get_as_list, { "traceparent": [ "00-12345678901234567890123456789012-1234567890123456-00" ], "tracestate": ["foo=1,"], }, + self.ctx, + get_as_list, ) span_context = from_context(ctx) self.assertEqual(span_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 22364abc3c..247fc26d57 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py @@ -50,7 +50,7 @@ class B3Extractor(HTTPExtractor): _SAMPLE_PROPAGATE_VALUES = set(["1", "True", "true", "d"]) @classmethod - def extract(cls, context, carrier, get_from_carrier=_getter): + def extract(cls, carrier, context=None, get_from_carrier=_getter): trace_id = format_trace_id(trace.INVALID_TRACE_ID) span_id = format_span_id(trace.INVALID_SPAN_ID) @@ -118,7 +118,7 @@ def extract(cls, context, carrier, get_from_carrier=_getter): class B3Injector(HTTPInjector): @classmethod - def inject(cls, context, carrier, set_in_carrier=_setter): + def inject(cls, carrier, context=None, set_in_carrier=_setter): sc = from_context(context) sampled = (trace.TraceOptions.SAMPLED & sc.trace_options) != 0 set_in_carrier(carrier, TRACE_ID_KEY, format_trace_id(sc.trace_id)) From 033e27ea881c5247e1ebfc7b0738cd4313415d9f Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 16 Dec 2019 22:07:22 -0800 Subject: [PATCH 21/71] fixing tests --- .../src/opentelemetry/ext/wsgi/__init__.py | 2 +- .../context/propagation/__init__.py | 4 +- .../sdk/context/propagation/b3_format.py | 34 ++++++---- .../context/propagation/test_b3_format.py | 65 +++++++++++++------ opentelemetry-sdk/tests/trace/test_trace.py | 10 +-- 5 files changed, 77 insertions(+), 38 deletions(-) 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 7613152869..2d7a6639e3 100644 --- a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py +++ b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py @@ -186,7 +186,7 @@ def __call__(self, environ, start_response): tracer = trace.tracer() # TODO: fix the return value here, expect a context parent_span = propagation.extract( - Context.current(), get_header_from_environ, environ + environ, Context.current(), get_header_from_environ, ) span_name = get_default_span_name(environ) diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py index 030f089781..ffe05cf6f9 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py @@ -15,9 +15,9 @@ from opentelemetry.context import Context from .binaryformat import BinaryFormat -from .httptextformat import HTTPExtractor, HTTPInjector +from .httptextformat import Getter, HTTPExtractor, HTTPInjector, Setter # from opentelemetry.context.propagation import Carrier, Getter, Setter -__all__ = ["BinaryFormat", "HTTPExtractor", "HTTPInjector"] +__all__ = ["BinaryFormat", "Getter", "HTTPExtractor", "HTTPInjector", "Setter"] 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 247fc26d57..1bffb30e14 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py @@ -15,25 +15,25 @@ import typing import opentelemetry.trace as trace -from opentelemetry.context.propagation import HTTPExtractor, HTTPInjector +from opentelemetry.context.propagation import ( + Getter, + HTTPExtractor, + HTTPInjector, + Setter, +) from opentelemetry.trace.propagation.context import ( + Context, from_context, with_span_context, ) +_T = typing.TypeVar("_T") + TRACE_ID_KEY = "x-b3-traceid" SPAN_ID_KEY = "x-b3-spanid" SAMPLED_KEY = "x-b3-sampled" -def _getter(headers, key): - return headers.get(key) - - -def _setter(headers, key, value): - headers[key] = value - - def http_propagator() -> typing.Tuple[HTTPExtractor, HTTPInjector]: """ TODO """ return B3Extractor, B3Injector @@ -50,7 +50,12 @@ class B3Extractor(HTTPExtractor): _SAMPLE_PROPAGATE_VALUES = set(["1", "True", "true", "d"]) @classmethod - def extract(cls, carrier, context=None, get_from_carrier=_getter): + def extract( + cls, + carrier, + context: typing.Optional[Context] = None, + get_from_carrier: typing.Optional[Getter[_T]] = None, + ): trace_id = format_trace_id(trace.INVALID_TRACE_ID) span_id = format_span_id(trace.INVALID_SPAN_ID) @@ -76,7 +81,7 @@ def extract(cls, carrier, context=None, get_from_carrier=_getter): elif len(fields) == 4: trace_id, span_id, sampled, _parent_span_id = fields else: - return trace.INVALID_SPAN_CONTEXT + return with_span_context(trace.INVALID_SPAN_CONTEXT) else: trace_id = ( _extract_first_element(get_from_carrier(carrier, TRACE_ID_KEY)) @@ -118,7 +123,12 @@ def extract(cls, carrier, context=None, get_from_carrier=_getter): class B3Injector(HTTPInjector): @classmethod - def inject(cls, carrier, context=None, set_in_carrier=_setter): + def inject( + cls, + carrier, + context: typing.Optional[Context] = None, + set_in_carrier: typing.Optional[Setter[_T]] = None, + ): sc = from_context(context) sampled = (trace.TraceOptions.SAMPLED & sc.trace_options) != 0 set_in_carrier(carrier, TRACE_ID_KEY, format_trace_id(sc.trace_id)) diff --git a/opentelemetry-sdk/tests/context/propagation/test_b3_format.py b/opentelemetry-sdk/tests/context/propagation/test_b3_format.py index 3e8458e0f8..cc418aa9f5 100644 --- a/opentelemetry-sdk/tests/context/propagation/test_b3_format.py +++ b/opentelemetry-sdk/tests/context/propagation/test_b3_format.py @@ -14,6 +14,10 @@ import unittest +from opentelemetry.trace.propagation.context import ( + from_context, + with_span_context, +) import opentelemetry.sdk.context.propagation.b3_format as b3_format import opentelemetry.sdk.trace as trace import opentelemetry.trace as trace_api @@ -44,9 +48,11 @@ def test_extract_multi_header(self): b3_format.SPAN_ID_KEY: self.serialized_span_id, b3_format.SAMPLED_KEY: "1", } - span_context = EXTRACTOR.extract(get_as_list, carrier) + EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) new_carrier = {} - INJECTOR.inject(span_context, dict.__setitem__, new_carrier) + INJECTOR.inject( + new_carrier, set_in_carrier=dict.__setitem__, + ) self.assertEqual( new_carrier[b3_format.TRACE_ID_KEY], self.serialized_trace_id ) @@ -62,9 +68,11 @@ def test_extract_single_header(self): self.serialized_trace_id, self.serialized_span_id ) } - span_context = EXTRACTOR.extract(get_as_list, carrier) + EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) new_carrier = {} - INJECTOR.inject(span_context, dict.__setitem__, new_carrier) + INJECTOR.inject( + new_carrier, set_in_carrier=dict.__setitem__, + ) self.assertEqual( new_carrier[b3_format.TRACE_ID_KEY], self.serialized_trace_id ) @@ -86,9 +94,11 @@ def test_extract_header_precedence(self): b3_format.SPAN_ID_KEY: self.serialized_span_id, b3_format.SAMPLED_KEY: "1", } - span_context = EXTRACTOR.extract(get_as_list, carrier) + EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) new_carrier = {} - INJECTOR.inject(span_context, dict.__setitem__, new_carrier) + INJECTOR.inject( + new_carrier, set_in_carrier=dict.__setitem__, + ) self.assertEqual( new_carrier[b3_format.TRACE_ID_KEY], single_header_trace_id ) @@ -101,9 +111,11 @@ def test_enabled_sampling(self): b3_format.SPAN_ID_KEY: self.serialized_span_id, b3_format.SAMPLED_KEY: variant, } - span_context = EXTRACTOR.extract(get_as_list, carrier) + EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) new_carrier = {} - INJECTOR.inject(span_context, dict.__setitem__, new_carrier) + INJECTOR.inject( + new_carrier, set_in_carrier=dict.__setitem__, + ) self.assertEqual(new_carrier[b3_format.SAMPLED_KEY], "1") def test_disabled_sampling(self): @@ -114,9 +126,11 @@ def test_disabled_sampling(self): b3_format.SPAN_ID_KEY: self.serialized_span_id, b3_format.SAMPLED_KEY: variant, } - span_context = EXTRACTOR.extract(get_as_list, carrier) + EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) new_carrier = {} - INJECTOR.inject(span_context, dict.__setitem__, new_carrier) + INJECTOR.inject( + new_carrier, set_in_carrier=dict.__setitem__, + ) self.assertEqual(new_carrier[b3_format.SAMPLED_KEY], "0") def test_flags(self): @@ -126,9 +140,12 @@ def test_flags(self): b3_format.SPAN_ID_KEY: self.serialized_span_id, EXTRACTOR.FLAGS_KEY: "1", } - span_context = EXTRACTOR.extract(get_as_list, carrier) + + EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) new_carrier = {} - INJECTOR.inject(span_context, dict.__setitem__, new_carrier) + INJECTOR.inject( + new_carrier, set_in_carrier=dict.__setitem__, + ) self.assertEqual(new_carrier[b3_format.SAMPLED_KEY], "1") def test_flags_and_sampling(self): @@ -138,9 +155,11 @@ def test_flags_and_sampling(self): b3_format.SPAN_ID_KEY: self.serialized_span_id, EXTRACTOR.FLAGS_KEY: "1", } - span_context = EXTRACTOR.extract(get_as_list, carrier) + EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) new_carrier = {} - INJECTOR.inject(span_context, dict.__setitem__, new_carrier) + INJECTOR.inject( + new_carrier, set_in_carrier=dict.__setitem__, + ) self.assertEqual(new_carrier[b3_format.SAMPLED_KEY], "1") def test_64bit_trace_id(self): @@ -151,9 +170,11 @@ def test_64bit_trace_id(self): b3_format.SPAN_ID_KEY: self.serialized_span_id, EXTRACTOR.FLAGS_KEY: "1", } - span_context = EXTRACTOR.extract(get_as_list, carrier) + EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) new_carrier = {} - INJECTOR.inject(span_context, dict.__setitem__, new_carrier) + INJECTOR.inject( + new_carrier, set_in_carrier=dict.__setitem__, + ) self.assertEqual( new_carrier[b3_format.TRACE_ID_KEY], "0" * 16 + trace_id_64_bit ) @@ -163,7 +184,9 @@ def test_invalid_single_header(self): invalid SpanContext. """ carrier = {EXTRACTOR.SINGLE_HEADER_KEY: "0-1-2-3-4-5-6-7"} - span_context = EXTRACTOR.extract(get_as_list, carrier) + span_context = from_context( + EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) + ) self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID) self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID) @@ -173,7 +196,9 @@ def test_missing_trace_id(self): b3_format.SPAN_ID_KEY: self.serialized_span_id, EXTRACTOR.FLAGS_KEY: "1", } - span_context = EXTRACTOR.extract(get_as_list, carrier) + span_context = from_context( + EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) + ) self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID) def test_missing_span_id(self): @@ -182,5 +207,7 @@ def test_missing_span_id(self): b3_format.TRACE_ID_KEY: self.serialized_trace_id, EXTRACTOR.FLAGS_KEY: "1", } - span_context = EXTRACTOR.extract(get_as_list, carrier) + span_context = from_context( + EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) + ) self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID) diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index e8144c9373..fe7165ccd6 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -18,6 +18,7 @@ from unittest import mock from opentelemetry import trace as trace_api +from opentelemetry.context import Context from opentelemetry.sdk import trace from opentelemetry.trace import sampling from opentelemetry.util import time_ns @@ -140,7 +141,7 @@ def test_start_span_invalid_spancontext(self): def test_start_span_implicit(self): tracer = trace.Tracer("test_start_span_implicit") - + Context.set_current(Context()) self.assertIsNone(tracer.get_current_span()) root = tracer.start_span("root") @@ -185,7 +186,7 @@ def test_start_span_implicit(self): def test_start_span_explicit(self): tracer = trace.Tracer("test_start_span_explicit") - + Context.set_current(Context()) other_parent = trace_api.SpanContext( trace_id=0x000000000000000000000000DEADBEEF, span_id=0x00000000DEADBEF0, @@ -233,7 +234,7 @@ def test_start_span_explicit(self): def test_start_as_current_span_implicit(self): tracer = trace.Tracer("test_start_as_current_span_implicit") - + Context.set_current(Context()) self.assertIsNone(tracer.get_current_span()) with tracer.start_as_current_span("root") as root: @@ -253,7 +254,7 @@ def test_start_as_current_span_implicit(self): def test_start_as_current_span_explicit(self): tracer = trace.Tracer("test_start_as_current_span_explicit") - + Context.set_current(Context()) other_parent = trace_api.SpanContext( trace_id=0x000000000000000000000000DEADBEEF, span_id=0x00000000DEADBEF0, @@ -287,6 +288,7 @@ def test_start_as_current_span_explicit(self): class TestSpan(unittest.TestCase): def setUp(self): + Context.set_current(Context()) self.tracer = trace.Tracer("test_span") def test_basic_span(self): From cc813bbb9ff1f4b7fd25cef27879dbb5c2f726e9 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 16 Dec 2019 22:09:01 -0800 Subject: [PATCH 22/71] rename current -> snapshot Signed-off-by: Alex Boten --- .../src/opentelemetry/context/__init__.py | 27 +++++++++++++------ .../test_tracecontexthttptextformat.py | 2 +- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index e97e2fcec5..f038b629ee 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -141,6 +141,7 @@ async def main(): import copy import typing + from .base_context import BaseRuntimeContext __all__ = ["BaseRuntimeContext", "Context"] @@ -180,25 +181,25 @@ def value( cls, key: str, context: typing.Optional["Context"] = None ) -> str: if context is None: - return getattr( - _CONTEXT, _CONTEXT.current_context.get() - ).contents.get(key) + return cls.current().contents.get(key) return context.contents.get(key) @classmethod def set_value(cls, key: str, value: "object") -> "Context": - getattr(_CONTEXT, _CONTEXT.current_context.get()).contents[key] = value - return cls.current() + cls.current().contents[key] = value + return cls.snapshot() @classmethod def current(cls) -> "Context": if _CONTEXT.current_context is None: ctx = Context() cls.set_current(ctx) + return getattr(_CONTEXT, _CONTEXT.current_context.get()) + + @classmethod + def snapshot(cls) -> "Context": snapshot = Context() - snapshot.contents = copy.deepcopy( - getattr(_CONTEXT, _CONTEXT.current_context.get()).contents - ) + snapshot.contents = copy.deepcopy(cls.current().contents) return snapshot @classmethod @@ -209,3 +210,13 @@ def set_current(cls, ctx: "Context"): "__current_prop_context__" ) _CONTEXT.current_context.set(ctx.slot_name) + + @classmethod + def use(cls, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: + return _CONTEXT.use(**kwargs) + + @classmethod + def register_slot( + cls, name: str, default: "object" = None + ) -> "BaseRuntimeContext.Slot": + return _CONTEXT.register_slot(name, default) diff --git a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py index 132649c63d..0edc6bf980 100644 --- a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py @@ -42,7 +42,7 @@ class TestTraceContextFormat(unittest.TestCase): SPAN_ID = int("1234567890123456", 16) # type:int def setUp(self): - self.ctx = Context.current() + self.ctx = Context.snapshot() def test_no_traceparent_header(self): """When tracecontext headers are not present, a new SpanContext From 7391372e055e4e72177ec501510861953a56eace Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 16 Dec 2019 22:27:17 -0800 Subject: [PATCH 23/71] fix remaining sdk tests --- opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 54580eab08..9e1bef647d 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -425,11 +425,11 @@ def use_span( """See `opentelemetry.trace.Tracer.use_span`.""" try: span_snapshot = self.get_current_span() - with_span_context(span.get_context()) + Context.current().contents[self._slot_name] = span try: yield span finally: - with_span_context(span_snapshot) + Context.current().contents[self._slot_name] = span_snapshot finally: if end_on_exit: span.end() From fa6d437d40873ef98551a2e732e6299c1380af1a Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 17 Dec 2019 11:40:16 -0800 Subject: [PATCH 24/71] small context cleanup, return obj, remove unused __getattr__ Signed-off-by: Alex Boten --- .../src/opentelemetry/context/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index f038b629ee..10606e0dca 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -146,7 +146,6 @@ async def main(): __all__ = ["BaseRuntimeContext", "Context"] -_CONTEXT = None try: from .async_context import AsyncRuntimeContext @@ -170,16 +169,13 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): pass - def __getattr__(self, name: str) -> "object": - return _CONTEXT.__getattr__(name) - def get(self, key): return self.contents.get(key) @classmethod def value( cls, key: str, context: typing.Optional["Context"] = None - ) -> str: + ) -> "object": if context is None: return cls.current().contents.get(key) return context.contents.get(key) @@ -220,3 +216,7 @@ def register_slot( cls, name: str, default: "object" = None ) -> "BaseRuntimeContext.Slot": return _CONTEXT.register_slot(name, default) + + @classmethod + def suppress_instrumentation(cls) -> "object": + return _CONTEXT.suppress_instrumentation From 5fa8e0297ff5b4467c59195bc6b65b197711543e Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 17 Dec 2019 11:41:46 -0800 Subject: [PATCH 25/71] store both current span and extracted span context Signed-off-by: Alex Boten --- .../src/opentelemetry/trace/propagation/__init__.py | 10 ++++++++-- .../src/opentelemetry/sdk/trace/__init__.py | 11 +++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py index 25fe69993b..67de4cbc62 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py @@ -16,9 +16,15 @@ class ContextKeys: """ TODO """ - KEY = "span-context" + EXTRACT_SPAN_CONTEXT_KEY = "extracted-span-context" + SPAN_KEY = "current-span" @classmethod def span_context_key(cls) -> str: """ TODO """ - return cls.KEY + return cls.EXTRACT_SPAN_CONTEXT_KEY + + @classmethod + def span_key(cls) -> str: + """ TODO """ + return cls.SPAN_KEY diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 9e1bef647d..28925ccc92 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -312,9 +312,6 @@ def __init__( sampler: sampling.Sampler = trace_api.sampling.ALWAYS_ON, shutdown_on_exit: bool = True, ) -> None: - self._slot_name = ContextKeys.span_context_key() - if name: - self._slot_name = "{}.{}".format(name, self._slot_name) self._active_span_processor = MultiSpanProcessor() self.sampler = sampler self._atexit_handler = None @@ -323,7 +320,7 @@ def __init__( def get_current_span(self, context: Optional[Context] = None): """See `opentelemetry.trace.Tracer.get_current_span`.""" - return Context.value(self._slot_name, context=context) + return Context.value(ContextKeys.span_key, context=context) def start_as_current_span( self, @@ -425,11 +422,13 @@ def use_span( """See `opentelemetry.trace.Tracer.use_span`.""" try: span_snapshot = self.get_current_span() - Context.current().contents[self._slot_name] = span + Context.current().contents[ContextKeys.span_key] = span try: yield span finally: - Context.current().contents[self._slot_name] = span_snapshot + Context.current().contents[ + ContextKeys.span_key + ] = span_snapshot finally: if end_on_exit: span.end() From 164ef68f84a84105cb5e4244581ec8b0b043eee8 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 17 Dec 2019 13:55:04 -0800 Subject: [PATCH 26/71] test cleanup Signed-off-by: Alex Boten --- opentelemetry-sdk/tests/trace/test_trace.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index fe7165ccd6..e5a41e7ccd 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -125,6 +125,9 @@ def test_sampler_no_sampling(self): class TestSpanCreation(unittest.TestCase): + def setUp(self): + Context.set_current(Context()) + def test_start_span_invalid_spancontext(self): """If an invalid span context is passed as the parent, the created span should use a new span id. @@ -141,7 +144,6 @@ def test_start_span_invalid_spancontext(self): def test_start_span_implicit(self): tracer = trace.Tracer("test_start_span_implicit") - Context.set_current(Context()) self.assertIsNone(tracer.get_current_span()) root = tracer.start_span("root") @@ -186,7 +188,6 @@ def test_start_span_implicit(self): def test_start_span_explicit(self): tracer = trace.Tracer("test_start_span_explicit") - Context.set_current(Context()) other_parent = trace_api.SpanContext( trace_id=0x000000000000000000000000DEADBEEF, span_id=0x00000000DEADBEF0, @@ -234,7 +235,6 @@ def test_start_span_explicit(self): def test_start_as_current_span_implicit(self): tracer = trace.Tracer("test_start_as_current_span_implicit") - Context.set_current(Context()) self.assertIsNone(tracer.get_current_span()) with tracer.start_as_current_span("root") as root: @@ -254,7 +254,6 @@ def test_start_as_current_span_implicit(self): def test_start_as_current_span_explicit(self): tracer = trace.Tracer("test_start_as_current_span_explicit") - Context.set_current(Context()) other_parent = trace_api.SpanContext( trace_id=0x000000000000000000000000DEADBEEF, span_id=0x00000000DEADBEF0, From b37c3b064d5544f8c782138baf4054c4b431af19 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 17 Dec 2019 14:12:19 -0800 Subject: [PATCH 27/71] fix ot shim tests Signed-off-by: Alex Boten --- .../ext/opentracing_shim/__init__.py | 15 ++++------ .../tests/test_shim.py | 30 ++++++++++++++----- 2 files changed, 29 insertions(+), 16 deletions(-) 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 25cdbe7e83..28892f5237 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 @@ -669,7 +669,7 @@ def inject(self, span_context, format, carrier): raise opentracing.UnsupportedFormatException ctx = with_span_context(span_context.unwrap()) - propagation.inject(ctx, type(carrier).__setitem__, carrier) + propagation.inject(carrier, context=ctx) def extract(self, format, carrier): """Implements the ``extract`` method from the base class.""" @@ -683,14 +683,11 @@ def extract(self, format, carrier): if format not in self._supported_formats: raise opentracing.UnsupportedFormatException - def get_as_list(dict_object, key): - value = dict_object.get(key) - return [value] if value is not None else [] + # def get_as_list(dict_object, key): + # value = dict_object.get(key) + # return [value] if value is not None else [] - # propagator = propagation.get_global_httptextformat() - - otel_context = from_context( - propagation.extract(Context.current(), get_as_list, carrier) - ) + propagation.extract(carrier) + otel_context = from_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 73f921e20a..b2aa8265e5 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -19,6 +19,10 @@ import opentelemetry.ext.opentracing_shim as opentracingshim from opentelemetry import propagation, trace +from opentelemetry.trace.propagation.context import ( + from_context, + with_span_context, +) from opentelemetry.context.propagation.httptextformat import ( HTTPExtractor, HTTPInjector, @@ -27,6 +31,15 @@ from opentelemetry.sdk.trace import Tracer +def _getter(dict_object, key): + value = dict_object.get(key) + return [value] if value is not None else [] + + +def _setter(dict_object, key, value): + dict_object[key] = value + + class TestShim(unittest.TestCase): # pylint: disable=too-many-public-methods @@ -545,15 +558,17 @@ class MockHTTPExtractor(HTTPExtractor): """Mock extractor for testing purposes.""" @classmethod - def extract(cls, carrier, context=None, get_from_carrier=None): + def extract(cls, carrier, context=None, get_from_carrier=_getter): trace_id_list = get_from_carrier(carrier, _TRACE_ID_KEY) span_id_list = get_from_carrier(carrier, _SPAN_ID_KEY) if not trace_id_list or not span_id_list: - return trace.INVALID_SPAN_CONTEXT + return with_span_context(trace.INVALID_SPAN_CONTEXT) - return trace.SpanContext( - trace_id=int(trace_id_list[0]), span_id=int(span_id_list[0]) + return with_span_context( + trace.SpanContext( + trace_id=int(trace_id_list[0]), span_id=int(span_id_list[0]) + ) ) @@ -561,6 +576,7 @@ class MockHTTPInjector(HTTPInjector): """Mock injector for testing purposes.""" @classmethod - def inject(cls, carrier, context=None, set_in_carrier=None): - set_in_carrier(carrier, _TRACE_ID_KEY, str(context.trace_id)) - set_in_carrier(carrier, _SPAN_ID_KEY, str(context.span_id)) + def inject(cls, carrier, context=None, set_in_carrier=_setter): + sc = from_context(context) + set_in_carrier(carrier, _TRACE_ID_KEY, str(sc.trace_id)) + set_in_carrier(carrier, _SPAN_ID_KEY, str(sc.span_id)) From 4ddac9042376bbe7f0d69367204f9d715335eb6c Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 17 Dec 2019 21:22:27 -0800 Subject: [PATCH 28/71] fixing http_requests tests --- .../src/opentelemetry/ext/http_requests/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 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 db2efdc527..0601994b94 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 @@ -51,7 +51,7 @@ def enable(tracer): @functools.wraps(wrapped) def instrumented_request(self, method, url, *args, **kwargs): - if Context.suppress_instrumentation: + if Context.suppress_instrumentation(): return wrapped(self, method, url, *args, **kwargs) # See @@ -74,9 +74,7 @@ def instrumented_request(self, method, url, *args, **kwargs): # to access propagators. headers = kwargs.setdefault("headers", {}) - propagation.inject( - Context.current(), type(headers).__setitem__, headers, - ) + propagation.inject(headers) result = wrapped(self, method, url, *args, **kwargs) # *** PROCEED span.set_attribute("http.status_code", result.status_code) From f50013246ce8f307094f3cdfe45c089fc28eb735 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 17 Dec 2019 21:25:51 -0800 Subject: [PATCH 29/71] refactoring common getter/setter for propagation --- .../ext/opentracing_shim/__init__.py | 4 --- .../src/opentelemetry/context/base_context.py | 11 ------- .../context/propagation/__init__.py | 14 ++++++++ .../propagation/tracecontexthttptextformat.py | 15 ++++++--- .../test_tracecontexthttptextformat.py | 15 +-------- .../sdk/context/propagation/b3_format.py | 5 +-- .../context/propagation/test_b3_format.py | 33 +++++++------------ 7 files changed, 40 insertions(+), 57 deletions(-) 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 28892f5237..8ac39ef85c 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 @@ -683,10 +683,6 @@ def extract(self, format, carrier): if format not in self._supported_formats: raise opentracing.UnsupportedFormatException - # def get_as_list(dict_object, key): - # value = dict_object.get(key) - # return [value] if value is not None else [] - propagation.extract(carrier) otel_context = from_context() diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py index 1167f022c9..2d9f8b9503 100644 --- a/opentelemetry-api/src/opentelemetry/context/base_context.py +++ b/opentelemetry-api/src/opentelemetry/context/base_context.py @@ -101,17 +101,6 @@ def __getitem__(self, name: str) -> "object": def __setitem__(self, name: str, value: "object") -> None: self.__setattr__(name, value) - def __enter__(self) -> "BaseRuntimeContext": - return self - - def __exit__( - self, - exc_type: typing.Optional[typing.Type[BaseException]], - exc_val: typing.Optional[BaseException], - exc_tb: typing.Optional[python_types.TracebackType], - ) -> None: - return None - @contextmanager # type: ignore def use(self, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: snapshot = {key: self[key] for key in kwargs} diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py index ffe05cf6f9..16f2ee9b58 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py @@ -11,6 +11,7 @@ # 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 typing from opentelemetry.context import Context @@ -21,3 +22,16 @@ __all__ = ["BinaryFormat", "Getter", "HTTPExtractor", "HTTPInjector", "Setter"] + + +def get_as_list(dict_object: dict, key: str) -> typing.List[str]: + value = dict_object.get(key) + if value is None: + return [] + if isinstance(value, list): + return value + return [value] + + +def set_in_dict(dict_object: dict, key: str, value: str) -> None: + dict_object[key] = value diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py index f4b2756ed6..b78d85cdd6 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py @@ -17,8 +17,15 @@ import opentelemetry.trace as trace from opentelemetry.context import Context -from opentelemetry.context.propagation import HTTPExtractor, HTTPInjector -from opentelemetry.context.propagation.httptextformat import Getter, Setter +from opentelemetry.context.propagation import ( + get_as_list, + Getter, + HTTPExtractor, + HTTPInjector, + set_in_dict, + Setter, +) + from opentelemetry.trace.propagation.context import ( from_context, with_span_context, @@ -72,7 +79,7 @@ def extract( cls, carrier: _T, context: typing.Optional[Context] = None, - get_from_carrier: typing.Optional[Getter[_T]] = None, + get_from_carrier: typing.Optional[Getter[_T]] = get_as_list, ) -> Context: """Extracts a valid SpanContext from the carrier. """ @@ -121,7 +128,7 @@ def inject( cls, carrier: _T, context: typing.Optional[Context] = None, - set_in_carrier: typing.Optional[Setter[_T]] = None, + set_in_carrier: typing.Optional[Setter[_T]] = set_in_dict, ) -> None: sc = from_context(context) if sc is None or sc == trace.INVALID_SPAN_CONTEXT: diff --git a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py index 0edc6bf980..0a79c06c87 100644 --- a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py @@ -30,13 +30,6 @@ EXTRACT = TraceContextHTTPExtractor -def get_as_list( - dict_object: typing.Dict[str, typing.List[str]], key: str -) -> typing.List[str]: - value = dict_object.get(key) - return value if value is not None else [] - - class TestTraceContextFormat(unittest.TestCase): TRACE_ID = int("12345678901234567890123456789012", 16) # type:int SPAN_ID = int("1234567890123456", 16) # type:int @@ -53,7 +46,7 @@ def test_no_traceparent_header(self): If no traceparent header is received, the vendor creates a new trace-id and parent-id that represents the current request. """ output = {} # type:typing.Dict[str, typing.List[str]] - span_context = EXTRACT.extract(output, self.ctx, get_as_list) + span_context = EXTRACT.extract(output, self.ctx) self.assertTrue(isinstance(span_context, trace.SpanContext)) def test_headers_with_tracestate(self): @@ -71,7 +64,6 @@ def test_headers_with_tracestate(self): "tracestate": [tracestate_value], }, self.ctx, - get_as_list, ) span_context = from_context(ctx) self.assertEqual(span_context.trace_id, self.TRACE_ID) @@ -111,7 +103,6 @@ def test_invalid_trace_id(self): "tracestate": ["foo=1,bar=2,foo=3"], }, self.ctx, - get_as_list, ) self.assertEqual(span_context, trace.INVALID_SPAN_CONTEXT) @@ -138,7 +129,6 @@ def test_invalid_parent_id(self): "tracestate": ["foo=1,bar=2,foo=3"], }, self.ctx, - get_as_list, ) self.assertEqual(span_context, trace.INVALID_SPAN_CONTEXT) @@ -174,7 +164,6 @@ def test_format_not_supported(self): "tracestate": ["foo=1,bar=2,foo=3"], }, self.ctx, - get_as_list, ) self.assertEqual(span_context, trace.INVALID_SPAN_CONTEXT) @@ -196,7 +185,6 @@ def test_tracestate_empty_header(self): "tracestate": ["foo=1", ""], }, self.ctx, - get_as_list, ) span_context = from_context(ctx) self.assertEqual(span_context.trace_state["foo"], "1") @@ -212,7 +200,6 @@ def test_tracestate_header_with_trailing_comma(self): "tracestate": ["foo=1,"], }, self.ctx, - get_as_list, ) span_context = from_context(ctx) self.assertEqual(span_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 1bffb30e14..ddc96e6bd4 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py @@ -21,6 +21,7 @@ HTTPInjector, Setter, ) +from opentelemetry.context.propagation import get_as_list, set_in_dict from opentelemetry.trace.propagation.context import ( Context, from_context, @@ -54,7 +55,7 @@ def extract( cls, carrier, context: typing.Optional[Context] = None, - get_from_carrier: typing.Optional[Getter[_T]] = None, + get_from_carrier: typing.Optional[Getter[_T]] = get_as_list, ): trace_id = format_trace_id(trace.INVALID_TRACE_ID) @@ -127,7 +128,7 @@ def inject( cls, carrier, context: typing.Optional[Context] = None, - set_in_carrier: typing.Optional[Setter[_T]] = None, + set_in_carrier: typing.Optional[Setter[_T]] = set_in_dict, ): sc = from_context(context) sampled = (trace.TraceOptions.SAMPLED & sc.trace_options) != 0 diff --git a/opentelemetry-sdk/tests/context/propagation/test_b3_format.py b/opentelemetry-sdk/tests/context/propagation/test_b3_format.py index cc418aa9f5..e6710acba2 100644 --- a/opentelemetry-sdk/tests/context/propagation/test_b3_format.py +++ b/opentelemetry-sdk/tests/context/propagation/test_b3_format.py @@ -26,11 +26,6 @@ EXTRACTOR = b3_format.B3Extractor -def get_as_list(dict_object, key): - value = dict_object.get(key) - return [value] if value is not None else [] - - class TestB3Format(unittest.TestCase): @classmethod def setUpClass(cls): @@ -48,7 +43,7 @@ def test_extract_multi_header(self): b3_format.SPAN_ID_KEY: self.serialized_span_id, b3_format.SAMPLED_KEY: "1", } - EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) + EXTRACTOR.extract(carrier) new_carrier = {} INJECTOR.inject( new_carrier, set_in_carrier=dict.__setitem__, @@ -68,7 +63,7 @@ def test_extract_single_header(self): self.serialized_trace_id, self.serialized_span_id ) } - EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) + EXTRACTOR.extract(carrier) new_carrier = {} INJECTOR.inject( new_carrier, set_in_carrier=dict.__setitem__, @@ -94,7 +89,7 @@ def test_extract_header_precedence(self): b3_format.SPAN_ID_KEY: self.serialized_span_id, b3_format.SAMPLED_KEY: "1", } - EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) + EXTRACTOR.extract(carrier) new_carrier = {} INJECTOR.inject( new_carrier, set_in_carrier=dict.__setitem__, @@ -111,7 +106,7 @@ def test_enabled_sampling(self): b3_format.SPAN_ID_KEY: self.serialized_span_id, b3_format.SAMPLED_KEY: variant, } - EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) + EXTRACTOR.extract(carrier) new_carrier = {} INJECTOR.inject( new_carrier, set_in_carrier=dict.__setitem__, @@ -126,7 +121,7 @@ def test_disabled_sampling(self): b3_format.SPAN_ID_KEY: self.serialized_span_id, b3_format.SAMPLED_KEY: variant, } - EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) + EXTRACTOR.extract(carrier) new_carrier = {} INJECTOR.inject( new_carrier, set_in_carrier=dict.__setitem__, @@ -141,7 +136,7 @@ def test_flags(self): EXTRACTOR.FLAGS_KEY: "1", } - EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) + EXTRACTOR.extract(carrier) new_carrier = {} INJECTOR.inject( new_carrier, set_in_carrier=dict.__setitem__, @@ -155,7 +150,7 @@ def test_flags_and_sampling(self): b3_format.SPAN_ID_KEY: self.serialized_span_id, EXTRACTOR.FLAGS_KEY: "1", } - EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) + EXTRACTOR.extract(carrier) new_carrier = {} INJECTOR.inject( new_carrier, set_in_carrier=dict.__setitem__, @@ -170,7 +165,7 @@ def test_64bit_trace_id(self): b3_format.SPAN_ID_KEY: self.serialized_span_id, EXTRACTOR.FLAGS_KEY: "1", } - EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) + EXTRACTOR.extract(carrier) new_carrier = {} INJECTOR.inject( new_carrier, set_in_carrier=dict.__setitem__, @@ -184,9 +179,7 @@ def test_invalid_single_header(self): invalid SpanContext. """ carrier = {EXTRACTOR.SINGLE_HEADER_KEY: "0-1-2-3-4-5-6-7"} - span_context = from_context( - EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) - ) + span_context = from_context(EXTRACTOR.extract(carrier)) self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID) self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID) @@ -196,9 +189,7 @@ def test_missing_trace_id(self): b3_format.SPAN_ID_KEY: self.serialized_span_id, EXTRACTOR.FLAGS_KEY: "1", } - span_context = from_context( - EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) - ) + span_context = from_context(EXTRACTOR.extract(carrier)) self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID) def test_missing_span_id(self): @@ -207,7 +198,5 @@ def test_missing_span_id(self): b3_format.TRACE_ID_KEY: self.serialized_trace_id, EXTRACTOR.FLAGS_KEY: "1", } - span_context = from_context( - EXTRACTOR.extract(carrier, get_from_carrier=get_as_list) - ) + span_context = from_context(EXTRACTOR.extract(carrier)) self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID) From 93e88de95526e340f9e9bc1253bab3c967da8662 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 17 Dec 2019 21:30:04 -0800 Subject: [PATCH 30/71] add convenience methods to set/get span from context --- .../trace/propagation/context/__init__.py | 10 +++++++++- .../src/opentelemetry/sdk/trace/__init__.py | 11 ++++++----- .../src/opentelemetry/sdk/trace/export/__init__.py | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py index 82ce0bbc3f..8c9dcf25e2 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py @@ -14,7 +14,7 @@ from typing import Optional from opentelemetry.context import Context -from opentelemetry.trace import SpanContext +from opentelemetry.trace import Span, SpanContext from opentelemetry.trace.propagation import ContextKeys @@ -24,3 +24,11 @@ def from_context(ctx: Optional[Context] = None) -> SpanContext: def with_span_context(span_context: SpanContext) -> Context: return Context.set_value(ContextKeys.span_context_key(), span_context) + + +def span_from_context(context: Optional[Context] = None) -> Span: + return Context.value(ContextKeys.span_key(), context=context) + + +def with_span(span: Span) -> Context: + return Context.set_value(ContextKeys.span_key(), span) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 28925ccc92..985b4bb13a 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -28,6 +28,8 @@ from opentelemetry.trace.propagation.context import ( ContextKeys, from_context, + span_from_context, + with_span, with_span_context, ) from opentelemetry.util import time_ns, types @@ -320,7 +322,7 @@ def __init__( def get_current_span(self, context: Optional[Context] = None): """See `opentelemetry.trace.Tracer.get_current_span`.""" - return Context.value(ContextKeys.span_key, context=context) + return span_from_context(context=context) def start_as_current_span( self, @@ -422,13 +424,12 @@ def use_span( """See `opentelemetry.trace.Tracer.use_span`.""" try: span_snapshot = self.get_current_span() - Context.current().contents[ContextKeys.span_key] = span + with_span(span) + try: yield span finally: - Context.current().contents[ - ContextKeys.span_key - ] = span_snapshot + with_span(span_snapshot) finally: if end_on_exit: span.end() diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py index 850c53d333..442b2b2bac 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py @@ -182,7 +182,7 @@ def export(self) -> None: while idx < self.max_export_batch_size and self.queue: self.spans_list[idx] = self.queue.pop() idx += 1 - with Context.current().use(suppress_instrumentation=True): + with Context.use(suppress_instrumentation=True): try: # Ignore type b/c the Optional[None]+slicing is too "clever" # for mypy From 72c0dbd718f7311a512e8df79f1f9d389b623477 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 17 Dec 2019 21:42:41 -0800 Subject: [PATCH 31/71] fix wsgi and flask tests Signed-off-by: Alex Boten --- .../src/opentelemetry/ext/flask/__init__.py | 8 +-- .../src/opentelemetry/ext/wsgi/__init__.py | 12 +++-- .../src/opentelemetry/ext/wsgi/propagation.py | 52 +++++++++++++++++++ 3 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/propagation.py 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 1c42ff0611..80f1b3fb03 100644 --- a/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py +++ b/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py @@ -6,8 +6,9 @@ from flask import request as flask_request import opentelemetry.ext.wsgi as otel_wsgi +from opentelemetry.ext.wsgi.propagation import WSGIExtractor from opentelemetry import propagation, trace -from opentelemetry.context import Context +from opentelemetry.trace.propagation.context import from_context from opentelemetry.util import time_ns logger = logging.getLogger(__name__) @@ -57,10 +58,11 @@ def _before_flask_request(): span_name = flask_request.endpoint or otel_wsgi.get_default_span_name( environ ) - parent_span = propagation.extract( - Context.current(), otel_wsgi.get_header_from_environ, environ, + WSGIExtractor.extract( + environ, get_from_carrier=otel_wsgi.get_header_from_environ ) + parent_span = from_context() tracer = trace.tracer() attributes = otel_wsgi.collect_request_attributes(environ) 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 2d7a6639e3..bc199364fe 100644 --- a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py +++ b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py @@ -22,10 +22,13 @@ import typing import wsgiref.util as wsgiref_util -from opentelemetry import propagation, trace -from opentelemetry.context import Context +from opentelemetry import trace + from opentelemetry.ext.wsgi.version import __version__ # noqa from opentelemetry.trace.status import Status, StatusCanonicalCode +from opentelemetry.trace.propagation.context import from_context + +from .propagation import WSGIExtractor _HTTP_VERSION_PREFIX = "HTTP/" @@ -185,9 +188,10 @@ def __call__(self, environ, start_response): tracer = trace.tracer() # TODO: fix the return value here, expect a context - parent_span = propagation.extract( - environ, Context.current(), get_header_from_environ, + WSGIExtractor.extract( + environ, get_from_carrier=get_header_from_environ ) + parent_span = from_context() span_name = get_default_span_name(environ) span = tracer.start_span( diff --git a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/propagation.py b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/propagation.py new file mode 100644 index 0000000000..144a773738 --- /dev/null +++ b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/propagation.py @@ -0,0 +1,52 @@ +# 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 typing + +from opentelemetry.context.propagation import ( + Getter, + HTTPExtractor, +) + +import opentelemetry.trace as trace +from opentelemetry.trace.propagation.context import Context, with_span_context + +_T = typing.TypeVar("_T") + + +class WSGIExtractor(HTTPExtractor): + """ TODO """ + + @classmethod + def extract( + cls, + carrier, + context: typing.Optional[Context] = None, + get_from_carrier: typing.Optional[Getter[_T]] = None, + ): + """ TODO """ + trace_id = get_from_carrier(carrier, "TRACE_ID") + span_id = get_from_carrier(carrier, "SPAN_ID") + options = get_from_carrier(carrier, "OPTIONS") + if not trace_id or not span_id: + return with_span_context(trace.INVALID_SPAN_CONTEXT) + + return with_span_context( + 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(), + ), + ) From f3c807636a9878767e4dd100229d3d144d45e41a Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 18 Dec 2019 08:59:47 -0800 Subject: [PATCH 32/71] add parameter to extract to support custom getters Signed-off-by: Alex Boten --- .../src/opentelemetry/ext/flask/__init__.py | 4 +- .../tests/test_shim.py | 22 +++----- .../src/opentelemetry/ext/wsgi/__init__.py | 9 ++-- .../src/opentelemetry/ext/wsgi/propagation.py | 52 ------------------- .../propagation/tracecontexthttptextformat.py | 24 ++++----- .../src/opentelemetry/propagation/__init__.py | 9 ++++ 6 files changed, 33 insertions(+), 87 deletions(-) delete mode 100644 ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/propagation.py 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 80f1b3fb03..4885ae0a1f 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,6 @@ from flask import request as flask_request import opentelemetry.ext.wsgi as otel_wsgi -from opentelemetry.ext.wsgi.propagation import WSGIExtractor from opentelemetry import propagation, trace from opentelemetry.trace.propagation.context import from_context from opentelemetry.util import time_ns @@ -58,7 +57,8 @@ def _before_flask_request(): span_name = flask_request.endpoint or otel_wsgi.get_default_span_name( environ ) - WSGIExtractor.extract( + + propagation.extract( environ, get_from_carrier=otel_wsgi.get_header_from_environ ) diff --git a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py index b2aa8265e5..3d3469ca7c 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -19,27 +19,19 @@ import opentelemetry.ext.opentracing_shim as opentracingshim from opentelemetry import propagation, trace -from opentelemetry.trace.propagation.context import ( - from_context, - with_span_context, -) +from opentelemetry.context.propagation import get_as_list, set_in_dict from opentelemetry.context.propagation.httptextformat import ( HTTPExtractor, HTTPInjector, ) +from opentelemetry.trace.propagation.context import ( + from_context, + with_span_context, +) from opentelemetry.ext.opentracing_shim import util from opentelemetry.sdk.trace import Tracer -def _getter(dict_object, key): - value = dict_object.get(key) - return [value] if value is not None else [] - - -def _setter(dict_object, key, value): - dict_object[key] = value - - class TestShim(unittest.TestCase): # pylint: disable=too-many-public-methods @@ -558,7 +550,7 @@ class MockHTTPExtractor(HTTPExtractor): """Mock extractor for testing purposes.""" @classmethod - def extract(cls, carrier, context=None, get_from_carrier=_getter): + def extract(cls, carrier, context=None, get_from_carrier=get_as_list): trace_id_list = get_from_carrier(carrier, _TRACE_ID_KEY) span_id_list = get_from_carrier(carrier, _SPAN_ID_KEY) @@ -576,7 +568,7 @@ class MockHTTPInjector(HTTPInjector): """Mock injector for testing purposes.""" @classmethod - def inject(cls, carrier, context=None, set_in_carrier=_setter): + def inject(cls, carrier, context=None, set_in_carrier=set_in_dict): sc = from_context(context) set_in_carrier(carrier, _TRACE_ID_KEY, str(sc.trace_id)) set_in_carrier(carrier, _SPAN_ID_KEY, str(sc.span_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 bc199364fe..1739e2623a 100644 --- a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py +++ b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py @@ -22,13 +22,12 @@ import typing import wsgiref.util as wsgiref_util -from opentelemetry import trace +from opentelemetry import propagation, trace from opentelemetry.ext.wsgi.version import __version__ # noqa from opentelemetry.trace.status import Status, StatusCanonicalCode from opentelemetry.trace.propagation.context import from_context -from .propagation import WSGIExtractor _HTTP_VERSION_PREFIX = "HTTP/" @@ -187,10 +186,8 @@ def __call__(self, environ, start_response): """ tracer = trace.tracer() - # TODO: fix the return value here, expect a context - WSGIExtractor.extract( - environ, get_from_carrier=get_header_from_environ - ) + propagation.extract(environ, get_from_carrier=get_header_from_environ) + parent_span = from_context() span_name = get_default_span_name(environ) diff --git a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/propagation.py b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/propagation.py deleted file mode 100644 index 144a773738..0000000000 --- a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/propagation.py +++ /dev/null @@ -1,52 +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 typing - -from opentelemetry.context.propagation import ( - Getter, - HTTPExtractor, -) - -import opentelemetry.trace as trace -from opentelemetry.trace.propagation.context import Context, with_span_context - -_T = typing.TypeVar("_T") - - -class WSGIExtractor(HTTPExtractor): - """ TODO """ - - @classmethod - def extract( - cls, - carrier, - context: typing.Optional[Context] = None, - get_from_carrier: typing.Optional[Getter[_T]] = None, - ): - """ TODO """ - trace_id = get_from_carrier(carrier, "TRACE_ID") - span_id = get_from_carrier(carrier, "SPAN_ID") - options = get_from_carrier(carrier, "OPTIONS") - if not trace_id or not span_id: - return with_span_context(trace.INVALID_SPAN_CONTEXT) - - return with_span_context( - 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(), - ), - ) diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py index b78d85cdd6..ed7a782d8f 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py @@ -86,11 +86,11 @@ def extract( header = get_from_carrier(carrier, TRACEPARENT_HEADER_NAME) if not header: - return trace.INVALID_SPAN_CONTEXT + return with_span_context(trace.INVALID_SPAN_CONTEXT) match = re.search(cls._TRACEPARENT_HEADER_FORMAT_RE, header[0]) if not match: - return trace.INVALID_SPAN_CONTEXT + return with_span_context(trace.INVALID_SPAN_CONTEXT) version = match.group(1) trace_id = match.group(2) @@ -98,26 +98,26 @@ def extract( trace_options = match.group(4) if trace_id == "0" * 32 or span_id == "0" * 16: - return trace.INVALID_SPAN_CONTEXT + return with_span_context(trace.INVALID_SPAN_CONTEXT) if version == "00": if match.group(5): - return trace.INVALID_SPAN_CONTEXT + return with_span_context(trace.INVALID_SPAN_CONTEXT) if version == "ff": - return trace.INVALID_SPAN_CONTEXT + return with_span_context(trace.INVALID_SPAN_CONTEXT) tracestate_headers = get_from_carrier(carrier, TRACESTATE_HEADER_NAME) tracestate = _parse_tracestate(tracestate_headers) - span_context = trace.SpanContext( - trace_id=int(trace_id, 16), - span_id=int(span_id, 16), - trace_options=trace.TraceOptions(trace_options), - trace_state=tracestate, + return with_span_context( + trace.SpanContext( + trace_id=int(trace_id, 16), + span_id=int(span_id, 16), + trace_options=trace.TraceOptions(trace_options), + trace_state=tracestate, + ) ) - return with_span_context(span_context) - class TraceContextHTTPInjector(HTTPInjector): """Injects using w3c TraceContext's headers. diff --git a/opentelemetry-api/src/opentelemetry/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/propagation/__init__.py index 228fbbce23..c3b45cf9b3 100644 --- a/opentelemetry-api/src/opentelemetry/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagation/__init__.py @@ -31,6 +31,7 @@ def extract( extractors: typing.Optional[ typing.List[httptextformat.HTTPExtractor] ] = None, + get_from_carrier: typing.Optional[typing.Callable] = None, ) -> typing.Optional[Context]: """Load the parent SpanContext from values in the carrier. @@ -53,7 +54,15 @@ def extract( extractors = get_http_extractors() for extractor in extractors: + # TODO: improve this + if get_from_carrier: + return extractor.extract( + context=context, + carrier=carrier, + get_from_carrier=get_from_carrier, + ) return extractor.extract(context=context, carrier=carrier) + return None From d82e4c5fd40431ecd21fdf878140fcc177d94757 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 18 Dec 2019 09:00:25 -0800 Subject: [PATCH 33/71] changing to copy for now, still need better mechanism here Signed-off-by: Alex Boten --- opentelemetry-api/src/opentelemetry/context/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 10606e0dca..c6ea496793 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -195,7 +195,7 @@ def current(cls) -> "Context": @classmethod def snapshot(cls) -> "Context": snapshot = Context() - snapshot.contents = copy.deepcopy(cls.current().contents) + snapshot.contents = cls.current().contents.copy() return snapshot @classmethod From 89274558575f3279d292ca2438f78bbc094f45e7 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 18 Dec 2019 09:05:30 -0800 Subject: [PATCH 34/71] enable wsgi/request in example Signed-off-by: Alex Boten --- .../context_propagation_example.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py index d3ad51763c..0c1ee47759 100644 --- a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py +++ b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py @@ -23,8 +23,10 @@ import requests from flask import request +import opentelemetry.ext.http_requests from opentelemetry import propagation, trace from opentelemetry.distributedcontext import CorrelationContextManager +from opentelemetry.ext.wsgi import OpenTelemetryMiddleware from opentelemetry.sdk.context.propagation import b3_format from opentelemetry.sdk.trace import Tracer from opentelemetry.sdk.trace.export import ( @@ -43,8 +45,8 @@ def configure_opentelemetry(flask_app: flask.Flask): propagation.set_http_extractors([b3_extractor]) propagation.set_http_injectors([b3_injector]) - # opentelemetry.ext.http_requests.enable(trace.tracer()) - # flask_app.wsgi_app = OpenTelemetryMiddleware(flask_app.wsgi_app) + opentelemetry.ext.http_requests.enable(trace.tracer()) + flask_app.wsgi_app = OpenTelemetryMiddleware(flask_app.wsgi_app) def fetch_from_service_b() -> str: @@ -80,7 +82,7 @@ def hello(): if version == "2.0": return fetch_from_service_c() - return fetch_from_service_b() + return fetch_from_service_b() if __name__ == "__main__": From a4b7d0c1b5cf6e7f54b8d649c3167d1d8a3dbc36 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 18 Dec 2019 09:25:03 -0800 Subject: [PATCH 35/71] fix tracecontext tests Signed-off-by: Alex Boten --- .../test_tracecontexthttptextformat.py | 111 ++++++++++-------- 1 file changed, 60 insertions(+), 51 deletions(-) diff --git a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py index 0a79c06c87..8a7c237a27 100644 --- a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py @@ -46,7 +46,7 @@ def test_no_traceparent_header(self): If no traceparent header is received, the vendor creates a new trace-id and parent-id that represents the current request. """ output = {} # type:typing.Dict[str, typing.List[str]] - span_context = EXTRACT.extract(output, self.ctx) + span_context = from_context(EXTRACT.extract(output, self.ctx)) self.assertTrue(isinstance(span_context, trace.SpanContext)) def test_headers_with_tracestate(self): @@ -58,14 +58,15 @@ def test_headers_with_tracestate(self): span_id=format(self.SPAN_ID, "016x"), ) tracestate_value = "foo=1,bar=2,baz=3" - ctx = EXTRACT.extract( - { - "traceparent": [traceparent_value], - "tracestate": [tracestate_value], - }, - self.ctx, + span_context = from_context( + EXTRACT.extract( + { + "traceparent": [traceparent_value], + "tracestate": [tracestate_value], + }, + self.ctx, + ) ) - span_context = from_context(ctx) self.assertEqual(span_context.trace_id, self.TRACE_ID) self.assertEqual(span_context.span_id, self.SPAN_ID) self.assertEqual( @@ -73,7 +74,7 @@ def test_headers_with_tracestate(self): ) output = {} # type:typing.Dict[str, str] - INJECT.inject(output, ctx, dict.__setitem__) + INJECT.inject(output, set_in_carrier=dict.__setitem__) self.assertEqual(output["traceparent"], traceparent_value) for pair in ["foo=1", "bar=2", "baz=3"]: self.assertIn(pair, output["tracestate"]) @@ -95,14 +96,16 @@ def test_invalid_trace_id(self): If the vendor failed to parse traceparent, it MUST NOT attempt to parse tracestate. Note that the opposite is not true: failure to parse tracestate MUST NOT affect the parsing of traceparent. """ - span_context = EXTRACT.extract( - { - "traceparent": [ - "00-00000000000000000000000000000000-1234567890123456-00" - ], - "tracestate": ["foo=1,bar=2,foo=3"], - }, - self.ctx, + span_context = from_context( + EXTRACT.extract( + { + "traceparent": [ + "00-00000000000000000000000000000000-1234567890123456-00" + ], + "tracestate": ["foo=1,bar=2,foo=3"], + }, + self.ctx, + ) ) self.assertEqual(span_context, trace.INVALID_SPAN_CONTEXT) @@ -121,14 +124,16 @@ def test_invalid_parent_id(self): If the vendor failed to parse traceparent, it MUST NOT attempt to parse tracestate. Note that the opposite is not true: failure to parse tracestate MUST NOT affect the parsing of traceparent. """ - span_context = EXTRACT.extract( - { - "traceparent": [ - "00-00000000000000000000000000000000-0000000000000000-00" - ], - "tracestate": ["foo=1,bar=2,foo=3"], - }, - self.ctx, + span_context = from_context( + EXTRACT.extract( + { + "traceparent": [ + "00-00000000000000000000000000000000-0000000000000000-00" + ], + "tracestate": ["foo=1,bar=2,foo=3"], + }, + self.ctx, + ) ) self.assertEqual(span_context, trace.INVALID_SPAN_CONTEXT) @@ -156,14 +161,16 @@ def test_format_not_supported(self): If the version cannot be parsed, return an invalid trace header. """ - span_context = EXTRACT.extract( - { - "traceparent": [ - "00-12345678901234567890123456789012-1234567890123456-00-residue" - ], - "tracestate": ["foo=1,bar=2,foo=3"], - }, - self.ctx, + span_context = from_context( + EXTRACT.extract( + { + "traceparent": [ + "00-12345678901234567890123456789012-1234567890123456-00-residue" + ], + "tracestate": ["foo=1,bar=2,foo=3"], + }, + self.ctx, + ) ) self.assertEqual(span_context, trace.INVALID_SPAN_CONTEXT) @@ -177,29 +184,31 @@ def test_propagate_invalid_context(self): def test_tracestate_empty_header(self): """Test tracestate with an additional empty header (should be ignored)""" - ctx = EXTRACT.extract( - { - "traceparent": [ - "00-12345678901234567890123456789012-1234567890123456-00" - ], - "tracestate": ["foo=1", ""], - }, - self.ctx, + span_context = from_context( + EXTRACT.extract( + { + "traceparent": [ + "00-12345678901234567890123456789012-1234567890123456-00" + ], + "tracestate": ["foo=1", ""], + }, + self.ctx, + ) ) - span_context = from_context(ctx) self.assertEqual(span_context.trace_state["foo"], "1") def test_tracestate_header_with_trailing_comma(self): """Do not propagate invalid trace context. """ - ctx = EXTRACT.extract( - { - "traceparent": [ - "00-12345678901234567890123456789012-1234567890123456-00" - ], - "tracestate": ["foo=1,"], - }, - self.ctx, + span_context = from_context( + EXTRACT.extract( + { + "traceparent": [ + "00-12345678901234567890123456789012-1234567890123456-00" + ], + "tracestate": ["foo=1,"], + }, + self.ctx, + ) ) - span_context = from_context(ctx) self.assertEqual(span_context.trace_state["foo"], "1") From 3b8bf5db573d9d436d5adb09d1111dc909fd4a4e Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 18 Dec 2019 10:41:04 -0800 Subject: [PATCH 36/71] move distributedcontext to correlationcontext Signed-off-by: Alex Boten --- .../context_propagation_example.py | 12 +- .../__init__.py | 44 +++--- .../propagation/__init__.py | 8 +- .../propagation/binaryformat.py | 9 +- .../propagation/context/__init__.py | 30 +++++ .../propagation/httptextformat.py | 29 ++-- .../py.typed | 0 .../__init__.py | 0 .../test_distributed_context.py | 127 ++++++++++++++++++ .../test_distributed_context.py | 119 ---------------- .../__init__.py | 4 +- .../propagation/__init__.py | 0 .../__init__.py | 0 .../test_distributed_context.py | 6 +- 14 files changed, 225 insertions(+), 163 deletions(-) rename opentelemetry-api/src/opentelemetry/{distributedcontext => correlationcontext}/__init__.py (78%) rename opentelemetry-api/src/opentelemetry/{distributedcontext => correlationcontext}/propagation/__init__.py (81%) rename opentelemetry-api/src/opentelemetry/{distributedcontext => correlationcontext}/propagation/binaryformat.py (88%) create mode 100644 opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py rename opentelemetry-api/src/opentelemetry/{distributedcontext => correlationcontext}/propagation/httptextformat.py (87%) rename opentelemetry-api/src/opentelemetry/{distributedcontext => correlationcontext}/py.typed (100%) rename opentelemetry-api/tests/{distributedcontext => correlationcontext}/__init__.py (100%) create mode 100644 opentelemetry-api/tests/correlationcontext/test_distributed_context.py delete mode 100644 opentelemetry-api/tests/distributedcontext/test_distributed_context.py rename opentelemetry-sdk/src/opentelemetry/sdk/{distributedcontext => correlationcontext}/__init__.py (94%) delete mode 100644 opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/propagation/__init__.py rename opentelemetry-sdk/tests/{distributedcontext => correlationcontext}/__init__.py (100%) rename opentelemetry-sdk/tests/{distributedcontext => correlationcontext}/test_distributed_context.py (89%) diff --git a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py index 0c1ee47759..b0754e9fdd 100644 --- a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py +++ b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py @@ -25,7 +25,7 @@ import opentelemetry.ext.http_requests from opentelemetry import propagation, trace -from opentelemetry.distributedcontext import CorrelationContextManager +from opentelemetry.correlationcontext import CorrelationContextManager from opentelemetry.ext.wsgi import OpenTelemetryMiddleware from opentelemetry.sdk.context.propagation import b3_format from opentelemetry.sdk.trace import Tracer @@ -40,10 +40,14 @@ def configure_opentelemetry(flask_app: flask.Flask): # Global initialization (b3_extractor, b3_injector) = b3_format.http_propagator() + ( + correlation_extractor, + correlation_injector, + ) = CorrelationContextManager.http_propagator() # propagation.set_http_extractors([b3_extractor, baggage_extractor]) # propagation.set_http_injectors([b3_injector, baggage_injector]) - propagation.set_http_extractors([b3_extractor]) - propagation.set_http_injectors([b3_injector]) + propagation.set_http_extractors([b3_extractor, correlation_extractor]) + propagation.set_http_injectors([b3_injector, correlation_injector]) opentelemetry.ext.http_requests.enable(trace.tracer()) flask_app.wsgi_app = OpenTelemetryMiddleware(flask_app.wsgi_app) @@ -78,7 +82,7 @@ def hello(): # extract a baggage header with tracer.start_as_current_span("service-span"): with tracer.start_as_current_span("external-req-span"): - version = CorrelationContextManager.value("version") + version = CorrelationContextManager.correlation("version") if version == "2.0": return fetch_from_service_c() diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py similarity index 78% rename from opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py rename to opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py index 99fe361834..beb21ac635 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py @@ -16,7 +16,13 @@ import string import typing -from opentelemetry.context import BaseRuntimeContext, Context +from opentelemetry.context import Context +from opentelemetry.context.propagation import ( + HTTPExtractor, + HTTPInjector, +) + +from .propagation import CorrelationHTTPExtractor, CorrelationHTTPInjector PRINTABLE = frozenset( itertools.chain( @@ -24,7 +30,7 @@ ) ) - +# TODO: are Entry* classes still needed here? class EntryMetadata: """A class representing metadata of a CorrelationContext entry @@ -79,6 +85,7 @@ def __init__( self.value = value +# TODO: is CorrelationContext still needed here? class CorrelationContext: """A container for distributed context entries""" @@ -89,7 +96,7 @@ def __init__(self, entries: typing.Iterable[Entry]) -> None: @classmethod def set_value( - cls, context: BaseRuntimeContext, entry_list: typing.Iterable[Entry] + cls, context: Context, entry_list: typing.Iterable[Entry] ) -> None: distributed_context = getattr(context, cls.KEY, {}) for entry in entry_list: @@ -97,14 +104,14 @@ def set_value( @classmethod def get_entries( - cls, context: BaseRuntimeContext + cls, context: typing.Optional[Context] = None ) -> typing.Iterable[Entry]: """Returns an immutable iterator to entries.""" return getattr(context, cls.KEY, {}).values() @classmethod def get_entry_value( - cls, context: BaseRuntimeContext, key: EntryKey + cls, key: EntryKey, context: typing.Optional[Context] = None, ) -> typing.Optional[EntryValue]: """Returns the entry associated with a key or None @@ -118,21 +125,24 @@ def get_entry_value( class CorrelationContextManager: - def current_context(self) -> typing.Optional[CorrelationContext]: - """ TODO """ + @classmethod + def set_correlation(cls, key: str, value: "object") -> Context: + return Context.set_value(key, value) - def http_text_format(self): - """ TODO """ + @classmethod + def correlation( + cls, key: str, context: typing.Optional[Context] = None + ) -> "object": + return Context.value(key, context=context) - def use_context( - self, context: CorrelationContext - ) -> typing.Iterator[CorrelationContext]: - """ TODO """ + @classmethod + def remove_correlation(cls): + pass @classmethod - def set_value(cls, key, value): - return Context.set_value(key, value) + def clear_correlation(cls): + pass @classmethod - def value(cls, key, context=None): - return Context.value(key, context=context) + def http_propagator(cls) -> typing.Tuple[HTTPExtractor, HTTPInjector]: + return (CorrelationHTTPExtractor, CorrelationHTTPInjector) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/__init__.py similarity index 81% rename from opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py rename to opentelemetry-api/src/opentelemetry/correlationcontext/propagation/__init__.py index 1635a19a29..f720b0318e 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/__init__.py @@ -13,9 +13,13 @@ # limitations under the License. from .binaryformat import BinaryFormat -from .httptextformat import HTTPTextFormat +from .httptextformat import CorrelationHTTPExtractor, CorrelationHTTPInjector -__all__ = ["BinaryFormat", "HTTPTextFormat"] +__all__ = [ + "BinaryFormat", + "CorrelationHTTPExtractor", + "CorrelationHTTPInjector", +] class ContextKeys: diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/binaryformat.py b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/binaryformat.py similarity index 88% rename from opentelemetry-api/src/opentelemetry/distributedcontext/propagation/binaryformat.py rename to opentelemetry-api/src/opentelemetry/correlationcontext/propagation/binaryformat.py index f0a4a1e4ad..47af045c07 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/binaryformat.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/binaryformat.py @@ -15,9 +15,10 @@ import abc import typing -from opentelemetry.distributedcontext import CorrelationContext +from opentelemetry.context import Context +# TODO: can this be removed until it is needed? class BinaryFormat(abc.ABC): """API for serialization of span context into binary formats. @@ -27,7 +28,7 @@ class BinaryFormat(abc.ABC): @staticmethod @abc.abstractmethod - def to_bytes(context: CorrelationContext) -> bytes: + def to_bytes(context: Context) -> bytes: """Creates a byte representation of a CorrelationContext. to_bytes should read values from a CorrelationContext and return a data @@ -43,9 +44,7 @@ def to_bytes(context: CorrelationContext) -> bytes: @staticmethod @abc.abstractmethod - def from_bytes( - byte_representation: bytes, - ) -> typing.Optional[CorrelationContext]: + def from_bytes(byte_representation: bytes,) -> typing.Optional[Context]: """Return a CorrelationContext that was represented by bytes. from_bytes should return back a CorrelationContext that was constructed diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py new file mode 100644 index 0000000000..c14f96be70 --- /dev/null +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py @@ -0,0 +1,30 @@ +# 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 typing import Optional + +from opentelemetry.context import Context +from opentelemetry.correlationcontext import CorrelationContext +from opentelemetry.correlationcontext.propagation import ContextKeys + + +def from_context(ctx: Optional[Context] = None) -> CorrelationContext: + return Context.value(ContextKeys.span_context_key(), context=ctx) + + +def with_correlation_context( + correlation_context: CorrelationContext, +) -> Context: + return Context.set_value( + ContextKeys.span_context_key(), correlation_context + ) diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/httptextformat.py similarity index 87% rename from opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py rename to opentelemetry-api/src/opentelemetry/correlationcontext/propagation/httptextformat.py index f4c7e6838f..8ccb76a242 100644 --- a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/httptextformat.py @@ -12,16 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -import abc import typing -from opentelemetry.distributedcontext import CorrelationContext +from opentelemetry.context import Context +from opentelemetry.context.propagation import HTTPExtractor, HTTPInjector Setter = typing.Callable[[object, str, str], None] Getter = typing.Callable[[object, str], typing.List[str]] -class HTTPTextFormat(abc.ABC): +class CorrelationHTTPExtractor(HTTPExtractor): """API for propagation of span context via headers. This class provides an interface that enables extracting and injecting @@ -66,10 +66,13 @@ def example_route(): https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/api-propagators.md """ - @abc.abstractmethod + @classmethod def extract( - self, get_from_carrier: Getter, carrier: object - ) -> CorrelationContext: + cls, + carrier, + context: typing.Optional[Context] = None, + get_from_carrier: typing.Optional[Getter] = None, + ) -> Context: """Create a CorrelationContext from values in the carrier. The extract function should retrieve values from the carrier @@ -89,12 +92,16 @@ def extract( """ - @abc.abstractmethod + +class CorrelationHTTPInjector(HTTPInjector): + """ TODO """ + + @classmethod def inject( - self, - context: CorrelationContext, - set_in_carrier: Setter, - carrier: object, + cls, + carrier, + context: typing.Optional[Context] = None, + set_in_carrier: typing.Optional[Setter] = None, ) -> None: """Inject values from a CorrelationContext into a carrier. diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/py.typed b/opentelemetry-api/src/opentelemetry/correlationcontext/py.typed similarity index 100% rename from opentelemetry-api/src/opentelemetry/distributedcontext/py.typed rename to opentelemetry-api/src/opentelemetry/correlationcontext/py.typed diff --git a/opentelemetry-api/tests/distributedcontext/__init__.py b/opentelemetry-api/tests/correlationcontext/__init__.py similarity index 100% rename from opentelemetry-api/tests/distributedcontext/__init__.py rename to opentelemetry-api/tests/correlationcontext/__init__.py diff --git a/opentelemetry-api/tests/correlationcontext/test_distributed_context.py b/opentelemetry-api/tests/correlationcontext/test_distributed_context.py new file mode 100644 index 0000000000..e1d9fb18f5 --- /dev/null +++ b/opentelemetry-api/tests/correlationcontext/test_distributed_context.py @@ -0,0 +1,127 @@ +# 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 unittest + +from opentelemetry import correlationcontext +from opentelemetry.correlationcontext import CorrelationContext +from opentelemetry.correlationcontext.propagation.context import ( + from_context, + with_correlation_context, +) + + +class TestEntryMetadata(unittest.TestCase): + def test_entry_ttl_no_propagation(self): + metadata = correlationcontext.EntryMetadata( + correlationcontext.EntryMetadata.NO_PROPAGATION + ) + self.assertEqual(metadata.entry_ttl, 0) + + def test_entry_ttl_unlimited_propagation(self): + metadata = correlationcontext.EntryMetadata( + correlationcontext.EntryMetadata.UNLIMITED_PROPAGATION + ) + self.assertEqual(metadata.entry_ttl, -1) + + +class TestEntryKey(unittest.TestCase): + def test_create_empty(self): + with self.assertRaises(ValueError): + correlationcontext.EntryKey.create("") + + def test_create_too_long(self): + with self.assertRaises(ValueError): + correlationcontext.EntryKey.create("a" * 256) + + def test_create_invalid_character(self): + with self.assertRaises(ValueError): + correlationcontext.EntryKey.create("\x00") + + def test_create_valid(self): + key = correlationcontext.EntryKey.create("ok") + self.assertEqual(key, "ok") + + def test_key_new(self): + key = correlationcontext.EntryKey("ok") + self.assertEqual(key, "ok") + + +class TestEntryValue(unittest.TestCase): + def test_create_invalid_character(self): + with self.assertRaises(ValueError): + correlationcontext.EntryValue.create("\x00") + + def test_create_valid(self): + key = correlationcontext.EntryValue.create("ok") + self.assertEqual(key, "ok") + + def test_key_new(self): + key = correlationcontext.EntryValue("ok") + self.assertEqual(key, "ok") + + +# TODO:replace these +# class TestCorrelationContext(unittest.TestCase): +# def setUp(self): +# self.entry = correlationcontext.Entry( +# correlationcontext.EntryMetadata( +# correlationcontext.EntryMetadata.NO_PROPAGATION +# ), +# correlationcontext.EntryKey("key"), +# correlationcontext.EntryValue("value"), +# ) +# self.context = with_correlation_context( +# CorrelationContext(entries=[self.entry]) +# ) + +# def test_get_entries(self): +# self.assertIn( +# self.entry, from_context(self.context).get_entries(), +# ) + +# def test_get_entry_value_present(self): +# value = correlationcontext.CorrelationContext.get_entry_value( +# self.context, self.entry.key +# ) +# self.assertIs(value, self.entry.value) + +# def test_get_entry_value_missing(self): +# key = correlationcontext.EntryKey("missing") +# value = correlationcontext.CorrelationContext.get_entry_value( +# self.context, key +# ) +# self.assertIsNone(value) + + +# TODO:replace these +# class TestCorrelationContextManager(unittest.TestCase): +# def setUp(self): +# self.manager = correlationcontext.CorrelationContextManager() + +# def test_current_context(self): +# self.assertIsNone(self.manager.current_context()) + +# def test_use_context(self): +# expected = correlationcontext.CorrelationContext( +# ( +# correlationcontext.Entry( +# correlationcontext.EntryMetadata(0), +# correlationcontext.EntryKey("0"), +# correlationcontext.EntryValue(""), +# ), +# ) +# ) +# with self.manager.use_context(expected) as output: +# self.assertIs(output, expected) diff --git a/opentelemetry-api/tests/distributedcontext/test_distributed_context.py b/opentelemetry-api/tests/distributedcontext/test_distributed_context.py deleted file mode 100644 index ce3b26a793..0000000000 --- a/opentelemetry-api/tests/distributedcontext/test_distributed_context.py +++ /dev/null @@ -1,119 +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 unittest - -from opentelemetry import distributedcontext - - -class TestEntryMetadata(unittest.TestCase): - def test_entry_ttl_no_propagation(self): - metadata = distributedcontext.EntryMetadata( - distributedcontext.EntryMetadata.NO_PROPAGATION - ) - self.assertEqual(metadata.entry_ttl, 0) - - def test_entry_ttl_unlimited_propagation(self): - metadata = distributedcontext.EntryMetadata( - distributedcontext.EntryMetadata.UNLIMITED_PROPAGATION - ) - self.assertEqual(metadata.entry_ttl, -1) - - -class TestEntryKey(unittest.TestCase): - def test_create_empty(self): - with self.assertRaises(ValueError): - distributedcontext.EntryKey.create("") - - def test_create_too_long(self): - with self.assertRaises(ValueError): - distributedcontext.EntryKey.create("a" * 256) - - def test_create_invalid_character(self): - with self.assertRaises(ValueError): - distributedcontext.EntryKey.create("\x00") - - def test_create_valid(self): - key = distributedcontext.EntryKey.create("ok") - self.assertEqual(key, "ok") - - def test_key_new(self): - key = distributedcontext.EntryKey("ok") - self.assertEqual(key, "ok") - - -class TestEntryValue(unittest.TestCase): - def test_create_invalid_character(self): - with self.assertRaises(ValueError): - distributedcontext.EntryValue.create("\x00") - - def test_create_valid(self): - key = distributedcontext.EntryValue.create("ok") - self.assertEqual(key, "ok") - - def test_key_new(self): - key = distributedcontext.EntryValue("ok") - self.assertEqual(key, "ok") - - -class TestCorrelationContext(unittest.TestCase): - def setUp(self): - entry = self.entry = distributedcontext.Entry( - distributedcontext.EntryMetadata( - distributedcontext.EntryMetadata.NO_PROPAGATION - ), - distributedcontext.EntryKey("key"), - distributedcontext.EntryValue("value"), - ) - self.context = distributedcontext.CorrelationContext((entry,)) - - def test_get_entries(self): - self.assertIn( - self.entry, - distributedcontext.CorrelationContext.get_entries(self.context), - ) - - def test_get_entry_value_present(self): - value = distributedcontext.CorrelationContext.get_entry_value( - self.context, self.entry.key - ) - self.assertIs(value, self.entry.value) - - def test_get_entry_value_missing(self): - key = distributedcontext.EntryKey("missing") - value = distributedcontext.CorrelationContext.get_entry_value( - self.context, key - ) - self.assertIsNone(value) - - -class TestCorrelationContextManager(unittest.TestCase): - def setUp(self): - self.manager = distributedcontext.CorrelationContextManager() - - def test_current_context(self): - self.assertIsNone(self.manager.current_context()) - - def test_use_context(self): - expected = distributedcontext.CorrelationContext( - ( - distributedcontext.Entry( - distributedcontext.EntryMetadata(0), - distributedcontext.EntryKey("0"), - distributedcontext.EntryValue(""), - ), - ) - ) - with self.manager.use_context(expected) as output: - self.assertIs(output, expected) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py similarity index 94% rename from opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py rename to opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py index 62fa463776..aac209cbf0 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py @@ -15,12 +15,12 @@ import typing from contextlib import contextmanager -from opentelemetry import distributedcontext as dctx_api +from opentelemetry import correlationcontext as dctx_api from opentelemetry.context import Context class CorrelationContextManager(dctx_api.CorrelationContextManager): - """See `opentelemetry.distributedcontext.CorrelationContextManager` + """See `opentelemetry.correlationcontext.CorrelationContextManager` Args: name: The name of the context manager diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/propagation/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/distributedcontext/propagation/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/opentelemetry-sdk/tests/distributedcontext/__init__.py b/opentelemetry-sdk/tests/correlationcontext/__init__.py similarity index 100% rename from opentelemetry-sdk/tests/distributedcontext/__init__.py rename to opentelemetry-sdk/tests/correlationcontext/__init__.py diff --git a/opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py b/opentelemetry-sdk/tests/correlationcontext/test_distributed_context.py similarity index 89% rename from opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py rename to opentelemetry-sdk/tests/correlationcontext/test_distributed_context.py index bb174ccd0a..28c94e86de 100644 --- a/opentelemetry-sdk/tests/distributedcontext/test_distributed_context.py +++ b/opentelemetry-sdk/tests/correlationcontext/test_distributed_context.py @@ -14,13 +14,13 @@ import unittest -from opentelemetry import distributedcontext as dctx_api -from opentelemetry.sdk import distributedcontext +from opentelemetry import correlationcontext as dctx_api +from opentelemetry.sdk import correlationcontext class TestCorrelationContextManager(unittest.TestCase): def setUp(self): - self.manager = distributedcontext.CorrelationContextManager() + self.manager = correlationcontext.CorrelationContextManager() def test_use_context(self): # Context is None initially From 9efb6e4ee7986b3393d586985761e62cc8b84c33 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 18 Dec 2019 10:47:40 -0800 Subject: [PATCH 37/71] lint fixes Signed-off-by: Alex Boten --- ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py | 4 ++-- .../src/opentelemetry/ext/wsgi/__init__.py | 4 +--- opentelemetry-api/src/opentelemetry/context/__init__.py | 1 - opentelemetry-api/src/opentelemetry/context/base_context.py | 1 - .../context/propagation/tracecontexthttptextformat.py | 5 ++--- .../src/opentelemetry/correlationcontext/__init__.py | 6 ++---- .../tests/correlationcontext/test_distributed_context.py | 5 ----- .../src/opentelemetry/sdk/context/propagation/b3_format.py | 3 ++- opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py | 1 + .../tests/context/propagation/test_b3_format.py | 5 +---- 10 files changed, 11 insertions(+), 24 deletions(-) diff --git a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py index 3d3469ca7c..0e6aaaae05 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -24,12 +24,12 @@ HTTPExtractor, HTTPInjector, ) +from opentelemetry.ext.opentracing_shim import util +from opentelemetry.sdk.trace import Tracer from opentelemetry.trace.propagation.context import ( from_context, with_span_context, ) -from opentelemetry.ext.opentracing_shim import util -from opentelemetry.sdk.trace import Tracer class TestShim(unittest.TestCase): 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 1739e2623a..6d8475f498 100644 --- a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py +++ b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py @@ -23,11 +23,9 @@ import wsgiref.util as wsgiref_util from opentelemetry import propagation, trace - from opentelemetry.ext.wsgi.version import __version__ # noqa -from opentelemetry.trace.status import Status, StatusCanonicalCode from opentelemetry.trace.propagation.context import from_context - +from opentelemetry.trace.status import Status, StatusCanonicalCode _HTTP_VERSION_PREFIX = "HTTP/" diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index c6ea496793..685a6544f8 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -141,7 +141,6 @@ async def main(): import copy import typing - from .base_context import BaseRuntimeContext __all__ = ["BaseRuntimeContext", "Context"] diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py index 2d9f8b9503..99d6869dd5 100644 --- a/opentelemetry-api/src/opentelemetry/context/base_context.py +++ b/opentelemetry-api/src/opentelemetry/context/base_context.py @@ -13,7 +13,6 @@ # limitations under the License. import threading -import types as python_types import typing from contextlib import contextmanager diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py index ed7a782d8f..6c515438b7 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py @@ -18,14 +18,13 @@ import opentelemetry.trace as trace from opentelemetry.context import Context from opentelemetry.context.propagation import ( - get_as_list, Getter, HTTPExtractor, HTTPInjector, - set_in_dict, Setter, + get_as_list, + set_in_dict, ) - from opentelemetry.trace.propagation.context import ( from_context, with_span_context, diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py index beb21ac635..0af4343473 100644 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py @@ -17,10 +17,7 @@ import typing from opentelemetry.context import Context -from opentelemetry.context.propagation import ( - HTTPExtractor, - HTTPInjector, -) +from opentelemetry.context.propagation import HTTPExtractor, HTTPInjector from .propagation import CorrelationHTTPExtractor, CorrelationHTTPInjector @@ -30,6 +27,7 @@ ) ) + # TODO: are Entry* classes still needed here? class EntryMetadata: """A class representing metadata of a CorrelationContext entry diff --git a/opentelemetry-api/tests/correlationcontext/test_distributed_context.py b/opentelemetry-api/tests/correlationcontext/test_distributed_context.py index e1d9fb18f5..00d3b43a42 100644 --- a/opentelemetry-api/tests/correlationcontext/test_distributed_context.py +++ b/opentelemetry-api/tests/correlationcontext/test_distributed_context.py @@ -15,11 +15,6 @@ import unittest from opentelemetry import correlationcontext -from opentelemetry.correlationcontext import CorrelationContext -from opentelemetry.correlationcontext.propagation.context import ( - from_context, - with_correlation_context, -) class TestEntryMetadata(unittest.TestCase): 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 ddc96e6bd4..0212bc6c10 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py @@ -20,8 +20,9 @@ HTTPExtractor, HTTPInjector, Setter, + get_as_list, + set_in_dict, ) -from opentelemetry.context.propagation import get_as_list, set_in_dict from opentelemetry.trace.propagation.context import ( Context, from_context, diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 985b4bb13a..86ff4d9e7a 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -314,6 +314,7 @@ def __init__( sampler: sampling.Sampler = trace_api.sampling.ALWAYS_ON, shutdown_on_exit: bool = True, ) -> None: + # pylint: disable=unused-argument self._active_span_processor = MultiSpanProcessor() self.sampler = sampler self._atexit_handler = None diff --git a/opentelemetry-sdk/tests/context/propagation/test_b3_format.py b/opentelemetry-sdk/tests/context/propagation/test_b3_format.py index e6710acba2..bcb2f4d392 100644 --- a/opentelemetry-sdk/tests/context/propagation/test_b3_format.py +++ b/opentelemetry-sdk/tests/context/propagation/test_b3_format.py @@ -14,13 +14,10 @@ import unittest -from opentelemetry.trace.propagation.context import ( - from_context, - with_span_context, -) 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.context import from_context INJECTOR = b3_format.B3Injector EXTRACTOR = b3_format.B3Extractor From 5272bed00dffd38146c37cf5608c41f257f9a303 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 18 Dec 2019 12:59:56 -0800 Subject: [PATCH 38/71] add default injector/extractor. move tracecontext propagator to sdk Signed-off-by: Alex Boten --- .../tests/test_flask_integration.py | 21 ++++ .../tests/test_wsgi_middleware.py | 21 ++++ .../context/propagation/__init__.py | 25 ++++- .../context/propagation/httptextformat.py | 106 ++++++++++++++++++ .../src/opentelemetry/propagation/__init__.py | 10 +- .../propagation/tracecontexthttptextformat.py | 5 + .../test_tracecontexthttptextformat.py | 2 +- 7 files changed, 178 insertions(+), 12 deletions(-) rename {opentelemetry-api/src/opentelemetry => opentelemetry-sdk/src/opentelemetry/sdk}/context/propagation/tracecontexthttptextformat.py (97%) rename {opentelemetry-api => opentelemetry-sdk}/tests/context/propagation/test_tracecontexthttptextformat.py (98%) diff --git a/ext/opentelemetry-ext-flask/tests/test_flask_integration.py b/ext/opentelemetry-ext-flask/tests/test_flask_integration.py index b943a25f22..d6d1cf1c58 100644 --- a/ext/opentelemetry-ext-flask/tests/test_flask_integration.py +++ b/ext/opentelemetry-ext-flask/tests/test_flask_integration.py @@ -21,10 +21,31 @@ import opentelemetry.ext.flask as otel_flask from opentelemetry import trace as trace_api +from opentelemetry import propagation from opentelemetry.ext.testutil.wsgitestutil import WsgiTestBase +from opentelemetry.sdk.context.propagation.tracecontexthttptextformat import ( + http_propagator, +) class TestFlaskIntegration(WsgiTestBase): + @classmethod + def setUpClass(cls): + """ Set preferred propagators """ + extractor, injector = http_propagator() + # Save current propagator to be restored on teardown. + cls._previous_injectors = propagation.get_http_injectors() + cls._previous_extractors = propagation.get_http_extractors() + + propagation.set_http_extractors([extractor]) + propagation.set_http_injectors([injector]) + + @classmethod + def tearDownClass(cls): + """ Restore previous propagator """ + propagation.set_http_extractors(cls._previous_extractors) + propagation.set_http_injectors(cls._previous_injectors) + def setUp(self): super().setUp() diff --git a/ext/opentelemetry-ext-wsgi/tests/test_wsgi_middleware.py b/ext/opentelemetry-ext-wsgi/tests/test_wsgi_middleware.py index a78b4d19f0..b2ef9a6e5b 100644 --- a/ext/opentelemetry-ext-wsgi/tests/test_wsgi_middleware.py +++ b/ext/opentelemetry-ext-wsgi/tests/test_wsgi_middleware.py @@ -20,7 +20,11 @@ import opentelemetry.ext.wsgi as otel_wsgi from opentelemetry import trace as trace_api +from opentelemetry import propagation from opentelemetry.ext.testutil.wsgitestutil import WsgiTestBase +from opentelemetry.sdk.context.propagation.tracecontexthttptextformat import ( + http_propagator, +) class Response: @@ -74,6 +78,23 @@ def error_wsgi(environ, start_response): class TestWsgiApplication(WsgiTestBase): + @classmethod + def setUpClass(cls): + """ Set preferred propagators """ + extractor, injector = http_propagator() + # Save current propagator to be restored on teardown. + cls._previous_injectors = propagation.get_http_injectors() + cls._previous_extractors = propagation.get_http_extractors() + + propagation.set_http_extractors([extractor]) + propagation.set_http_injectors([injector]) + + @classmethod + def tearDownClass(cls): + """ Restore previous propagator """ + propagation.set_http_extractors(cls._previous_extractors) + propagation.set_http_injectors(cls._previous_injectors) + def validate_response(self, response, error=None): while True: try: diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py index 16f2ee9b58..e53b2ae1d7 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py @@ -16,12 +16,25 @@ from opentelemetry.context import Context from .binaryformat import BinaryFormat -from .httptextformat import Getter, HTTPExtractor, HTTPInjector, Setter - -# from opentelemetry.context.propagation import Carrier, Getter, Setter - - -__all__ = ["BinaryFormat", "Getter", "HTTPExtractor", "HTTPInjector", "Setter"] +from .httptextformat import ( + Getter, + DefaultHTTPExtractor, + DefaultHTTPInjector, + HTTPExtractor, + HTTPInjector, + Setter, +) + + +__all__ = [ + "BinaryFormat", + "Getter", + "DefaultHTTPExtractor", + "DefaultHTTPInjector", + "HTTPExtractor", + "HTTPInjector", + "Setter", +] def get_as_list(dict_object: dict, key: str) -> typing.List[str]: diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py index b49ee8c16d..3c2be01ac7 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py @@ -125,3 +125,109 @@ def inject( know how to set header values on the carrier. """ + + +class DefaultHTTPExtractor(HTTPExtractor): + """API for propagation of span context via headers. + + TODO: update docs to reflect split into extractor/injector + + 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 HTTPExtractor + + 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 + """ + + @classmethod + def extract( + cls, + carrier: _T, + context: typing.Optional[Context] = None, + get_from_carrier: typing.Optional[Getter[_T]] = None, + ) -> Context: + """Create a SpanContext 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. + + 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 + must be paired with an appropriate get_from_carrier + which understands how to extract a value from it. + Returns: + A SpanContext with configuration found in the carrier. + + """ + if context: + return context + return Context.current() + + +class DefaultHTTPInjector(HTTPInjector): + @classmethod + def inject( + cls, + carrier: _T, + context: typing.Optional[Context] = None, + set_in_carrier: typing.Optional[Setter[_T]] = None, + ) -> None: + """Inject values from a SpanContext 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 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. + + """ + return None diff --git a/opentelemetry-api/src/opentelemetry/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/propagation/__init__.py index c3b45cf9b3..b2833ff748 100644 --- a/opentelemetry-api/src/opentelemetry/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagation/__init__.py @@ -17,9 +17,9 @@ import opentelemetry.context.propagation.httptextformat as httptextformat import opentelemetry.trace as trace from opentelemetry.context import Context -from opentelemetry.context.propagation.tracecontexthttptextformat import ( - TraceContextHTTPExtractor, - TraceContextHTTPInjector, +from opentelemetry.context.propagation import ( + DefaultHTTPExtractor, + DefaultHTTPInjector, ) _T = typing.TypeVar("_T") @@ -97,11 +97,11 @@ def inject( _HTTP_TEXT_INJECTORS = [ - TraceContextHTTPInjector + DefaultHTTPInjector ] # typing.List[httptextformat.HTTPInjector] _HTTP_TEXT_EXTRACTORS = [ - TraceContextHTTPExtractor + DefaultHTTPExtractor ] # typing.List[httptextformat.HTTPExtractor] diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py similarity index 97% rename from opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py rename to opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py index 6c515438b7..6b654e1081 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py @@ -63,6 +63,11 @@ TRACESTATE_HEADER_NAME = "tracestate" +def http_propagator() -> typing.Tuple[HTTPExtractor, HTTPInjector]: + """ TODO """ + return TraceContextHTTPExtractor, TraceContextHTTPInjector + + class TraceContextHTTPExtractor(HTTPExtractor): """Extracts using w3c TraceContext's headers. """ diff --git a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py similarity index 98% rename from opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py rename to opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py index 8a7c237a27..a994fc6767 100644 --- a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py @@ -17,7 +17,7 @@ from opentelemetry import trace from opentelemetry.context import Context -from opentelemetry.context.propagation.tracecontexthttptextformat import ( +from opentelemetry.sdk.context.propagation.tracecontexthttptextformat import ( TraceContextHTTPExtractor, TraceContextHTTPInjector, ) From ea0905c472a9c8a0cdf02d8098800e3df49e6cdb Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 18 Dec 2019 13:09:57 -0800 Subject: [PATCH 39/71] use default propagator to avoid installing sdk Signed-off-by: Alex Boten --- .../tests/test_flask_integration.py | 27 +++---------------- .../tests/test_wsgi_middleware.py | 23 +--------------- 2 files changed, 4 insertions(+), 46 deletions(-) diff --git a/ext/opentelemetry-ext-flask/tests/test_flask_integration.py b/ext/opentelemetry-ext-flask/tests/test_flask_integration.py index d6d1cf1c58..096867d927 100644 --- a/ext/opentelemetry-ext-flask/tests/test_flask_integration.py +++ b/ext/opentelemetry-ext-flask/tests/test_flask_integration.py @@ -21,31 +21,10 @@ import opentelemetry.ext.flask as otel_flask from opentelemetry import trace as trace_api -from opentelemetry import propagation from opentelemetry.ext.testutil.wsgitestutil import WsgiTestBase -from opentelemetry.sdk.context.propagation.tracecontexthttptextformat import ( - http_propagator, -) class TestFlaskIntegration(WsgiTestBase): - @classmethod - def setUpClass(cls): - """ Set preferred propagators """ - extractor, injector = http_propagator() - # Save current propagator to be restored on teardown. - cls._previous_injectors = propagation.get_http_injectors() - cls._previous_extractors = propagation.get_http_extractors() - - propagation.set_http_extractors([extractor]) - propagation.set_http_injectors([injector]) - - @classmethod - def tearDownClass(cls): - """ Restore previous propagator """ - propagation.set_http_extractors(cls._previous_extractors) - propagation.set_http_injectors(cls._previous_injectors) - def setUp(self): super().setUp() @@ -76,7 +55,7 @@ def test_simple(self): self.start_span.assert_called_with( "hello_endpoint", - trace_api.INVALID_SPAN_CONTEXT, + None, kind=trace_api.SpanKind.SERVER, attributes={ "component": "http", @@ -106,7 +85,7 @@ def test_404(self): self.start_span.assert_called_with( "/bye", - trace_api.INVALID_SPAN_CONTEXT, + None, kind=trace_api.SpanKind.SERVER, attributes={ "component": "http", @@ -137,7 +116,7 @@ def test_internal_error(self): self.start_span.assert_called_with( "hello_endpoint", - trace_api.INVALID_SPAN_CONTEXT, + None, kind=trace_api.SpanKind.SERVER, attributes={ "component": "http", diff --git a/ext/opentelemetry-ext-wsgi/tests/test_wsgi_middleware.py b/ext/opentelemetry-ext-wsgi/tests/test_wsgi_middleware.py index b2ef9a6e5b..b2522b28fb 100644 --- a/ext/opentelemetry-ext-wsgi/tests/test_wsgi_middleware.py +++ b/ext/opentelemetry-ext-wsgi/tests/test_wsgi_middleware.py @@ -20,11 +20,7 @@ import opentelemetry.ext.wsgi as otel_wsgi from opentelemetry import trace as trace_api -from opentelemetry import propagation from opentelemetry.ext.testutil.wsgitestutil import WsgiTestBase -from opentelemetry.sdk.context.propagation.tracecontexthttptextformat import ( - http_propagator, -) class Response: @@ -78,23 +74,6 @@ def error_wsgi(environ, start_response): class TestWsgiApplication(WsgiTestBase): - @classmethod - def setUpClass(cls): - """ Set preferred propagators """ - extractor, injector = http_propagator() - # Save current propagator to be restored on teardown. - cls._previous_injectors = propagation.get_http_injectors() - cls._previous_extractors = propagation.get_http_extractors() - - propagation.set_http_extractors([extractor]) - propagation.set_http_injectors([injector]) - - @classmethod - def tearDownClass(cls): - """ Restore previous propagator """ - propagation.set_http_extractors(cls._previous_extractors) - propagation.set_http_injectors(cls._previous_injectors) - def validate_response(self, response, error=None): while True: try: @@ -119,7 +98,7 @@ def validate_response(self, response, error=None): # Verify that start_span has been called self.start_span.assert_called_with( "/", - trace_api.INVALID_SPAN_CONTEXT, + None, kind=trace_api.SpanKind.SERVER, attributes={ "component": "http", From 4c2c4de6ff1ec9d6f8ef4b9663db4f4d27f07e01 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 18 Dec 2019 15:00:52 -0800 Subject: [PATCH 40/71] moving context implementation to sdk Signed-off-by: Alex Boten --- .../src/opentelemetry/context/__init__.py | 67 +----------- .../context/propagation/__init__.py | 3 +- .../src/opentelemetry/sdk/context/__init__.py | 101 ++++++++++++++++++ .../sdk}/context/async_context.py | 0 .../sdk}/context/base_context.py | 0 .../sdk/context/propagation/b3_format.py | 5 +- .../propagation/tracecontexthttptextformat.py | 4 +- .../sdk}/context/thread_local_context.py | 0 .../src/opentelemetry/sdk/trace/__init__.py | 8 +- .../sdk/trace/export/__init__.py | 2 +- .../trace/propagation/context/__init__.py | 2 +- .../context/propagation/test_b3_format.py | 2 +- .../test_tracecontexthttptextformat.py | 4 +- opentelemetry-sdk/tests/trace/test_trace.py | 2 +- 14 files changed, 120 insertions(+), 80 deletions(-) rename {opentelemetry-api/src/opentelemetry => opentelemetry-sdk/src/opentelemetry/sdk}/context/async_context.py (100%) rename {opentelemetry-api/src/opentelemetry => opentelemetry-sdk/src/opentelemetry/sdk}/context/base_context.py (100%) rename {opentelemetry-api/src/opentelemetry => opentelemetry-sdk/src/opentelemetry/sdk}/context/thread_local_context.py (100%) rename {opentelemetry-api/src/opentelemetry => opentelemetry-sdk/src/opentelemetry/sdk}/trace/propagation/context/__init__.py (96%) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 685a6544f8..9a0cea76ac 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -137,85 +137,28 @@ async def main(): if __name__ == '__main__': asyncio.run(main()) """ - -import copy import typing -from .base_context import BaseRuntimeContext - -__all__ = ["BaseRuntimeContext", "Context"] - -try: - from .async_context import AsyncRuntimeContext - - _CONTEXT = AsyncRuntimeContext() # type: BaseRuntimeContext -except ImportError: - from .thread_local_context import ThreadLocalRuntimeContext - - _CONTEXT = ThreadLocalRuntimeContext() - class Context: - def __init__(self): - self.contents = {} - self.slot_name = "{}".format(id(self)) - self._slot = _CONTEXT.register_slot(self.slot_name) - self._slot.set(self) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - pass - - def get(self, key): - return self.contents.get(key) - @classmethod def value( cls, key: str, context: typing.Optional["Context"] = None ) -> "object": - if context is None: - return cls.current().contents.get(key) - return context.contents.get(key) + """ TODO """ @classmethod def set_value(cls, key: str, value: "object") -> "Context": - cls.current().contents[key] = value - return cls.snapshot() + """ TODO """ @classmethod def current(cls) -> "Context": - if _CONTEXT.current_context is None: - ctx = Context() - cls.set_current(ctx) - return getattr(_CONTEXT, _CONTEXT.current_context.get()) + """ TODO """ @classmethod def snapshot(cls) -> "Context": - snapshot = Context() - snapshot.contents = cls.current().contents.copy() - return snapshot + """ TODO """ @classmethod def set_current(cls, ctx: "Context"): - if _CONTEXT.current_context is None: - _CONTEXT.current_context = _CONTEXT.register_slot( - # change the key here - "__current_prop_context__" - ) - _CONTEXT.current_context.set(ctx.slot_name) - - @classmethod - def use(cls, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: - return _CONTEXT.use(**kwargs) - - @classmethod - def register_slot( - cls, name: str, default: "object" = None - ) -> "BaseRuntimeContext.Slot": - return _CONTEXT.register_slot(name, default) - - @classmethod - def suppress_instrumentation(cls) -> "object": - return _CONTEXT.suppress_instrumentation + """ TODO """ diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py index e53b2ae1d7..78c36a3005 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py @@ -17,15 +17,14 @@ from .binaryformat import BinaryFormat from .httptextformat import ( - Getter, DefaultHTTPExtractor, DefaultHTTPInjector, + Getter, HTTPExtractor, HTTPInjector, Setter, ) - __all__ = [ "BinaryFormat", "Getter", diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py index e69de29bb2..0b02ed3860 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py @@ -0,0 +1,101 @@ +# 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 copy +import types +import typing + +from .base_context import BaseRuntimeContext + +__all__ = ["BaseRuntimeContext", "Context"] + +try: + from .async_context import AsyncRuntimeContext + + _CONTEXT = AsyncRuntimeContext() # type: BaseRuntimeContext +except ImportError: + from .thread_local_context import ThreadLocalRuntimeContext + + _CONTEXT = ThreadLocalRuntimeContext() + + +class Context: + def __init__(self): + self.contents = {} + self.slot_name = "{}".format(id(self)) + self._slot = _CONTEXT.register_slot(self.slot_name) + self._slot.set(self) + + def __enter__(self) -> "Context": + return self + + def __exit__( + self, + exc_type: typing.Optional[typing.Type[BaseException]], + exc_val: typing.Optional[BaseException], + exc_tb: typing.Optional[types.TracebackType], + ) -> None: + pass + + def get(self, key: str) -> "object": + return self.contents.get(key) + + @classmethod + def value( + cls, key: str, context: typing.Optional["Context"] = None + ) -> "object": + if context is None: + return cls.current().contents.get(key) + return context.contents.get(key) + + @classmethod + def set_value(cls, key: str, value: "object") -> "Context": + cls.current().contents[key] = value + return cls.snapshot() + + @classmethod + def current(cls) -> "Context": + if _CONTEXT.current_context is None: + ctx = Context() + cls.set_current(ctx) + return getattr(_CONTEXT, _CONTEXT.current_context.get()) + + @classmethod + def snapshot(cls) -> "Context": + snapshot = Context() + snapshot.contents = cls.current().contents.copy() + return snapshot + + @classmethod + def set_current(cls, ctx: "Context"): + if _CONTEXT.current_context is None: + _CONTEXT.current_context = _CONTEXT.register_slot( + # change the key here + "__current_prop_context__" + ) + _CONTEXT.current_context.set(ctx.slot_name) + + @classmethod + def use(cls, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: + return _CONTEXT.use(**kwargs) + + @classmethod + def register_slot( + cls, name: str, default: "object" = None + ) -> "BaseRuntimeContext.Slot": + return _CONTEXT.register_slot(name, default) + + @classmethod + def suppress_instrumentation(cls) -> "object": + return _CONTEXT.suppress_instrumentation diff --git a/opentelemetry-api/src/opentelemetry/context/async_context.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/async_context.py similarity index 100% rename from opentelemetry-api/src/opentelemetry/context/async_context.py rename to opentelemetry-sdk/src/opentelemetry/sdk/context/async_context.py diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/base_context.py similarity index 100% rename from opentelemetry-api/src/opentelemetry/context/base_context.py rename to opentelemetry-sdk/src/opentelemetry/sdk/context/base_context.py 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 0212bc6c10..99f1bace3a 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py @@ -23,7 +23,7 @@ get_as_list, set_in_dict, ) -from opentelemetry.trace.propagation.context import ( +from opentelemetry.sdk.trace.propagation.context import ( Context, from_context, with_span_context, @@ -148,9 +148,6 @@ 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]: if items is None: return None diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py index 6b654e1081..8882a7ecdf 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py @@ -16,7 +16,6 @@ import typing import opentelemetry.trace as trace -from opentelemetry.context import Context from opentelemetry.context.propagation import ( Getter, HTTPExtractor, @@ -25,7 +24,8 @@ get_as_list, set_in_dict, ) -from opentelemetry.trace.propagation.context import ( +from opentelemetry.sdk.context import Context +from opentelemetry.sdk.trace.propagation.context import ( from_context, with_span_context, ) diff --git a/opentelemetry-api/src/opentelemetry/context/thread_local_context.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/thread_local_context.py similarity index 100% rename from opentelemetry-api/src/opentelemetry/context/thread_local_context.py rename to opentelemetry-sdk/src/opentelemetry/sdk/context/thread_local_context.py diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 86ff4d9e7a..961c3b26a5 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -21,17 +21,17 @@ from typing import Iterator, Optional, Sequence, Tuple from opentelemetry import trace as trace_api -from opentelemetry.context import Context from opentelemetry.sdk import util -from opentelemetry.sdk.util import BoundedDict, BoundedList -from opentelemetry.trace import sampling -from opentelemetry.trace.propagation.context import ( +from opentelemetry.sdk.context import Context +from opentelemetry.sdk.trace.propagation.context import ( ContextKeys, from_context, span_from_context, with_span, with_span_context, ) +from opentelemetry.sdk.util import BoundedDict, BoundedList +from opentelemetry.trace import sampling from opentelemetry.util import time_ns, types logger = logging.getLogger(__name__) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py index 442b2b2bac..2495de5a43 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py @@ -18,7 +18,7 @@ import typing from enum import Enum -from opentelemetry.context import Context +from opentelemetry.sdk.context import Context from opentelemetry.util import time_ns from .. import Span, SpanProcessor diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/propagation/context/__init__.py similarity index 96% rename from opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py rename to opentelemetry-sdk/src/opentelemetry/sdk/trace/propagation/context/__init__.py index 8c9dcf25e2..716abebed6 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/propagation/context/__init__.py @@ -13,7 +13,7 @@ # limitations under the License. from typing import Optional -from opentelemetry.context import Context +from opentelemetry.sdk.context import Context from opentelemetry.trace import Span, SpanContext from opentelemetry.trace.propagation import ContextKeys diff --git a/opentelemetry-sdk/tests/context/propagation/test_b3_format.py b/opentelemetry-sdk/tests/context/propagation/test_b3_format.py index bcb2f4d392..dee799ec4a 100644 --- a/opentelemetry-sdk/tests/context/propagation/test_b3_format.py +++ b/opentelemetry-sdk/tests/context/propagation/test_b3_format.py @@ -17,7 +17,7 @@ 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.context import from_context +from opentelemetry.sdk.trace.propagation.context import from_context INJECTOR = b3_format.B3Injector EXTRACTOR = b3_format.B3Extractor diff --git a/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py index a994fc6767..718486e021 100644 --- a/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py @@ -16,12 +16,12 @@ import unittest from opentelemetry import trace -from opentelemetry.context import Context +from opentelemetry.sdk.context import Context from opentelemetry.sdk.context.propagation.tracecontexthttptextformat import ( TraceContextHTTPExtractor, TraceContextHTTPInjector, ) -from opentelemetry.trace.propagation.context import ( +from opentelemetry.sdk.trace.propagation.context import ( from_context, with_span_context, ) diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index e5a41e7ccd..3a87d4999c 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -18,8 +18,8 @@ from unittest import mock from opentelemetry import trace as trace_api -from opentelemetry.context import Context from opentelemetry.sdk import trace +from opentelemetry.sdk.context import Context from opentelemetry.trace import sampling from opentelemetry.util import time_ns From 1447d7f56c036b61f6f64af680287ecd10554cc4 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 18 Dec 2019 16:44:54 -0800 Subject: [PATCH 41/71] tests fixed after context move Signed-off-by: Alex Boten --- .../src/opentelemetry/ext/flask/__init__.py | 5 +- .../ext/http_requests/__init__.py | 2 +- .../ext/opentracing_shim/__init__.py | 19 +++--- .../tests/test_shim.py | 58 ++++++++++++++----- .../src/opentelemetry/ext/wsgi/__init__.py | 5 +- .../src/opentelemetry/context/__init__.py | 47 +++++++++++++++ .../src/opentelemetry/propagation/__init__.py | 5 +- .../src/opentelemetry/sdk/context/__init__.py | 3 - .../sdk/trace/export/__init__.py | 4 +- 9 files changed, 111 insertions(+), 37 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 4885ae0a1f..bc8b44e19f 100644 --- a/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py +++ b/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py @@ -7,7 +7,8 @@ import opentelemetry.ext.wsgi as otel_wsgi from opentelemetry import propagation, trace -from opentelemetry.trace.propagation.context import from_context +from opentelemetry.context import Context +from opentelemetry.trace.propagation import ContextKeys from opentelemetry.util import time_ns logger = logging.getLogger(__name__) @@ -62,7 +63,7 @@ def _before_flask_request(): environ, get_from_carrier=otel_wsgi.get_header_from_environ ) - parent_span = from_context() + parent_span = Context.value(ContextKeys.span_context_key()) tracer = trace.tracer() attributes = otel_wsgi.collect_request_attributes(environ) 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 0601994b94..6f54841734 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 @@ -51,7 +51,7 @@ def enable(tracer): @functools.wraps(wrapped) def instrumented_request(self, method, url, *args, **kwargs): - if Context.suppress_instrumentation(): + if Context.value("suppress_instrumentation"): return wrapped(self, method, url, *args, **kwargs) # See 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 8ac39ef85c..af50f90e4e 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 @@ -85,12 +85,9 @@ import opentelemetry.trace as trace_api from opentelemetry import propagation -from opentelemetry.context import Context +from opentelemetry.context import context as ctx from opentelemetry.ext.opentracing_shim import util -from opentelemetry.trace.propagation.context import ( - from_context, - with_span_context, -) +from opentelemetry.trace.propagation import ContextKeys logger = logging.getLogger(__name__) @@ -653,8 +650,8 @@ def start_span( start_time=start_time_ns, ) - context = SpanContextShim(span.get_context()) - return SpanShim(self, context, span) + sc = SpanContextShim(span.get_context()) + return SpanShim(self, sc, span) def inject(self, span_context, format, carrier): """Implements the ``inject`` method from the base class.""" @@ -668,8 +665,10 @@ def inject(self, span_context, format, carrier): if format not in self._supported_formats: raise opentracing.UnsupportedFormatException - ctx = with_span_context(span_context.unwrap()) - propagation.inject(carrier, context=ctx) + context = ctx().set_value( + ContextKeys.span_context_key(), span_context.unwrap() + ) + propagation.inject(carrier, context=context) def extract(self, format, carrier): """Implements the ``extract`` method from the base class.""" @@ -684,6 +683,6 @@ def extract(self, format, carrier): raise opentracing.UnsupportedFormatException propagation.extract(carrier) - otel_context = from_context() + otel_context = ctx().value(ContextKeys.span_context_key()) 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 0e6aaaae05..8fb82e1bc8 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -19,6 +19,8 @@ import opentelemetry.ext.opentracing_shim as opentracingshim from opentelemetry import propagation, trace +from opentelemetry.context import Context, set_preferred_context_implementation +from opentelemetry.context import context as ctx from opentelemetry.context.propagation import get_as_list, set_in_dict from opentelemetry.context.propagation.httptextformat import ( HTTPExtractor, @@ -26,15 +28,37 @@ ) from opentelemetry.ext.opentracing_shim import util from opentelemetry.sdk.trace import Tracer -from opentelemetry.trace.propagation.context import ( - from_context, - with_span_context, -) +from opentelemetry.trace.propagation import ContextKeys + + +class MockContext(Context): + values = {} + + @classmethod + def value(cls, key: str, context=None) -> "object": + print("getting things", key) + return cls.values.get(key) + + @classmethod + def set_value(cls, key: str, value: "object") -> "Context": + print("setting things", key, value) + cls.values[key] = value + + @classmethod + def current(cls) -> "Context": + """ TODO """ + + @classmethod + def snapshot(cls) -> "Context": + """ TODO """ + + @classmethod + def set_current(cls, context: "Context"): + """ TODO """ class TestShim(unittest.TestCase): # pylint: disable=too-many-public-methods - def setUp(self): """Create an OpenTelemetry tracer and a shim before every test case.""" @@ -47,6 +71,7 @@ def setUpClass(cls): every test method. """ + set_preferred_context_implementation(lambda T: MockContext()) trace.set_preferred_tracer_implementation(lambda T: Tracer()) # Save current propagator to be restored on teardown. @@ -518,9 +543,9 @@ def test_extract_http_headers(self): _SPAN_ID_KEY: 7478, } - ctx = self.shim.extract(opentracing.Format.HTTP_HEADERS, carrier) - self.assertEqual(ctx.unwrap().trace_id, 1220) - self.assertEqual(ctx.unwrap().span_id, 7478) + context = self.shim.extract(opentracing.Format.HTTP_HEADERS, carrier) + self.assertEqual(context.unwrap().trace_id, 1220) + self.assertEqual(context.unwrap().span_id, 7478) def test_extract_text_map(self): """Test `extract()` method for Format.TEXT_MAP.""" @@ -530,9 +555,9 @@ def test_extract_text_map(self): _SPAN_ID_KEY: 7478, } - ctx = self.shim.extract(opentracing.Format.TEXT_MAP, carrier) - self.assertEqual(ctx.unwrap().trace_id, 1220) - self.assertEqual(ctx.unwrap().span_id, 7478) + context = self.shim.extract(opentracing.Format.TEXT_MAP, carrier) + self.assertEqual(context.unwrap().trace_id, 1220) + self.assertEqual(context.unwrap().span_id, 7478) def test_extract_binary(self): """Test `extract()` method for Format.BINARY.""" @@ -555,12 +580,15 @@ def extract(cls, carrier, context=None, get_from_carrier=get_as_list): span_id_list = get_from_carrier(carrier, _SPAN_ID_KEY) if not trace_id_list or not span_id_list: - return with_span_context(trace.INVALID_SPAN_CONTEXT) + return ctx().set_value( + ContextKeys.span_context_key(), trace.INVALID_SPAN_CONTEXT + ) - return with_span_context( + return ctx().set_value( + ContextKeys.span_context_key(), trace.SpanContext( trace_id=int(trace_id_list[0]), span_id=int(span_id_list[0]) - ) + ), ) @@ -569,6 +597,6 @@ class MockHTTPInjector(HTTPInjector): @classmethod def inject(cls, carrier, context=None, set_in_carrier=set_in_dict): - sc = from_context(context) + sc = ctx().value(ContextKeys.span_context_key()) set_in_carrier(carrier, _TRACE_ID_KEY, str(sc.trace_id)) set_in_carrier(carrier, _SPAN_ID_KEY, str(sc.span_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 6d8475f498..ed71711f08 100644 --- a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py +++ b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py @@ -23,8 +23,9 @@ import wsgiref.util as wsgiref_util from opentelemetry import propagation, trace +from opentelemetry.context import Context from opentelemetry.ext.wsgi.version import __version__ # noqa -from opentelemetry.trace.propagation.context import from_context +from opentelemetry.trace.propagation import ContextKeys from opentelemetry.trace.status import Status, StatusCanonicalCode _HTTP_VERSION_PREFIX = "HTTP/" @@ -186,7 +187,7 @@ def __call__(self, environ, start_response): tracer = trace.tracer() propagation.extract(environ, get_from_carrier=get_header_from_environ) - parent_span = from_context() + parent_span = Context.value(ContextKeys.span_context_key()) span_name = get_default_span_name(environ) span = tracer.start_span( diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 9a0cea76ac..3cc7a8b8ae 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -138,6 +138,7 @@ async def main(): asyncio.run(main()) """ import typing +from opentelemetry.util import loader class Context: @@ -162,3 +163,49 @@ def snapshot(cls) -> "Context": @classmethod def set_current(cls, ctx: "Context"): """ TODO """ + + +# Once https://github.com/python/mypy/issues/7092 is resolved, +# the following type definition should be replaced with +# from opentelemetry.util.loader import ImplementationFactory +ImplementationFactory = typing.Callable[ + [typing.Type[Context]], typing.Optional[Context] +] + +_CONTEXT = None # type: typing.Optional[Context] +_CONTEXT_FACTORY = None # type: typing.Optional[ImplementationFactory] + + +def context() -> Context: + """Gets the current global :class:`~.Context` object. + + If there isn't one set yet, a default will be loaded. + """ + global _CONTEXT, _CONTEXT_FACTORY # pylint:disable=global-statement + + if _CONTEXT is None: + # pylint:disable=protected-access + _CONTEXT = loader._load_impl(Context, _CONTEXT_FACTORY) + del _CONTEXT_FACTORY + + return _CONTEXT + + +def set_preferred_context_implementation( + factory: ImplementationFactory, +) -> None: + """Set the factory to be used to create the context. + + See :mod:`opentelemetry.util.loader` for details. + + This function may not be called after a context is already loaded. + + Args: + factory: Callback that should create a new :class:`Context` instance. + """ + global _CONTEXT_FACTORY # pylint:disable=global-statement + + if _CONTEXT: + raise RuntimeError("Context already loaded.") + + _CONTEXT_FACTORY = factory diff --git a/opentelemetry-api/src/opentelemetry/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/propagation/__init__.py index b2833ff748..736e729333 100644 --- a/opentelemetry-api/src/opentelemetry/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagation/__init__.py @@ -17,6 +17,7 @@ import opentelemetry.context.propagation.httptextformat as httptextformat import opentelemetry.trace as trace from opentelemetry.context import Context +from opentelemetry.context import context as ctx from opentelemetry.context.propagation import ( DefaultHTTPExtractor, DefaultHTTPInjector, @@ -49,7 +50,7 @@ def extract( which understands how to extract a value from it. """ if context is None: - context = Context.current() + context = ctx().current() if extractors is None: extractors = get_http_extractors() @@ -88,7 +89,7 @@ def inject( should know how to set header values on the carrier. """ if context is None: - context = Context.current() + context = ctx().current() if injectors is None: injectors = get_http_injectors() diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py index 0b02ed3860..c59a4f40b8 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py @@ -96,6 +96,3 @@ def register_slot( ) -> "BaseRuntimeContext.Slot": return _CONTEXT.register_slot(name, default) - @classmethod - def suppress_instrumentation(cls) -> "object": - return _CONTEXT.suppress_instrumentation diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py index 2495de5a43..8954bd93f6 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py @@ -73,7 +73,7 @@ def on_start(self, span: Span) -> None: pass def on_end(self, span: Span) -> None: - with Context.use(suppress_instrumentation=True): + with Context.set_value("suppress_instrumentation", True): try: self.span_exporter.export((span,)) # pylint: disable=broad-except @@ -182,7 +182,7 @@ def export(self) -> None: while idx < self.max_export_batch_size and self.queue: self.spans_list[idx] = self.queue.pop() idx += 1 - with Context.use(suppress_instrumentation=True): + with Context.set_value("suppress_instrumentation", True): try: # Ignore type b/c the Optional[None]+slicing is too "clever" # for mypy From 1cd03c536256622d10a5806d24f1cd65825b620b Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 18 Dec 2019 17:45:06 -0800 Subject: [PATCH 42/71] lint fixes Signed-off-by: Alex Boten --- .../src/opentelemetry/ext/opentracing_shim/__init__.py | 2 +- ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py | 7 +++++-- opentelemetry-api/src/opentelemetry/context/__init__.py | 5 +++-- .../src/opentelemetry/propagation/__init__.py | 3 +-- .../src/opentelemetry/sdk/context/__init__.py | 5 ++--- 5 files changed, 12 insertions(+), 10 deletions(-) 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 af50f90e4e..6ad0e6fc76 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 @@ -85,7 +85,7 @@ import opentelemetry.trace as trace_api from opentelemetry import propagation -from opentelemetry.context import context as ctx +from opentelemetry.context import ctx from opentelemetry.ext.opentracing_shim import util from opentelemetry.trace.propagation import ContextKeys diff --git a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py index 8fb82e1bc8..d2ccabeb81 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -19,8 +19,11 @@ import opentelemetry.ext.opentracing_shim as opentracingshim from opentelemetry import propagation, trace -from opentelemetry.context import Context, set_preferred_context_implementation -from opentelemetry.context import context as ctx +from opentelemetry.context import ( + Context, + ctx, + set_preferred_context_implementation, +) from opentelemetry.context.propagation import get_as_list, set_in_dict from opentelemetry.context.propagation.httptextformat import ( HTTPExtractor, diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 3cc7a8b8ae..ddb4137b05 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -138,6 +138,7 @@ async def main(): asyncio.run(main()) """ import typing + from opentelemetry.util import loader @@ -161,7 +162,7 @@ def snapshot(cls) -> "Context": """ TODO """ @classmethod - def set_current(cls, ctx: "Context"): + def set_current(cls, context: "Context"): """ TODO """ @@ -176,7 +177,7 @@ def set_current(cls, ctx: "Context"): _CONTEXT_FACTORY = None # type: typing.Optional[ImplementationFactory] -def context() -> Context: +def ctx() -> Context: """Gets the current global :class:`~.Context` object. If there isn't one set yet, a default will be loaded. diff --git a/opentelemetry-api/src/opentelemetry/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/propagation/__init__.py index 736e729333..595b2d8c98 100644 --- a/opentelemetry-api/src/opentelemetry/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagation/__init__.py @@ -16,8 +16,7 @@ import opentelemetry.context.propagation.httptextformat as httptextformat import opentelemetry.trace as trace -from opentelemetry.context import Context -from opentelemetry.context import context as ctx +from opentelemetry.context import Context, ctx from opentelemetry.context.propagation import ( DefaultHTTPExtractor, DefaultHTTPInjector, diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py index c59a4f40b8..37f48ae16e 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py @@ -78,13 +78,13 @@ def snapshot(cls) -> "Context": return snapshot @classmethod - def set_current(cls, ctx: "Context"): + def set_current(cls, context: "Context"): if _CONTEXT.current_context is None: _CONTEXT.current_context = _CONTEXT.register_slot( # change the key here "__current_prop_context__" ) - _CONTEXT.current_context.set(ctx.slot_name) + _CONTEXT.current_context.set(context.slot_name) @classmethod def use(cls, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: @@ -95,4 +95,3 @@ def register_slot( cls, name: str, default: "object" = None ) -> "BaseRuntimeContext.Slot": return _CONTEXT.register_slot(name, default) - From 7c9597cb66fae3df11639f0677aa734dcaed16ed Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Thu, 19 Dec 2019 13:26:28 -0800 Subject: [PATCH 43/71] Revert "lint fixes" This reverts commit 1cd03c536256622d10a5806d24f1cd65825b620b. --- .../src/opentelemetry/ext/opentracing_shim/__init__.py | 2 +- ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py | 7 ++----- opentelemetry-api/src/opentelemetry/context/__init__.py | 5 ++--- .../src/opentelemetry/propagation/__init__.py | 3 ++- .../src/opentelemetry/sdk/context/__init__.py | 5 +++-- 5 files changed, 10 insertions(+), 12 deletions(-) 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 6ad0e6fc76..af50f90e4e 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 @@ -85,7 +85,7 @@ import opentelemetry.trace as trace_api from opentelemetry import propagation -from opentelemetry.context import ctx +from opentelemetry.context import context as ctx from opentelemetry.ext.opentracing_shim import util from opentelemetry.trace.propagation import ContextKeys diff --git a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py index d2ccabeb81..8fb82e1bc8 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -19,11 +19,8 @@ import opentelemetry.ext.opentracing_shim as opentracingshim from opentelemetry import propagation, trace -from opentelemetry.context import ( - Context, - ctx, - set_preferred_context_implementation, -) +from opentelemetry.context import Context, set_preferred_context_implementation +from opentelemetry.context import context as ctx from opentelemetry.context.propagation import get_as_list, set_in_dict from opentelemetry.context.propagation.httptextformat import ( HTTPExtractor, diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index ddb4137b05..3cc7a8b8ae 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -138,7 +138,6 @@ async def main(): asyncio.run(main()) """ import typing - from opentelemetry.util import loader @@ -162,7 +161,7 @@ def snapshot(cls) -> "Context": """ TODO """ @classmethod - def set_current(cls, context: "Context"): + def set_current(cls, ctx: "Context"): """ TODO """ @@ -177,7 +176,7 @@ def set_current(cls, context: "Context"): _CONTEXT_FACTORY = None # type: typing.Optional[ImplementationFactory] -def ctx() -> Context: +def context() -> Context: """Gets the current global :class:`~.Context` object. If there isn't one set yet, a default will be loaded. diff --git a/opentelemetry-api/src/opentelemetry/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/propagation/__init__.py index 595b2d8c98..736e729333 100644 --- a/opentelemetry-api/src/opentelemetry/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagation/__init__.py @@ -16,7 +16,8 @@ import opentelemetry.context.propagation.httptextformat as httptextformat import opentelemetry.trace as trace -from opentelemetry.context import Context, ctx +from opentelemetry.context import Context +from opentelemetry.context import context as ctx from opentelemetry.context.propagation import ( DefaultHTTPExtractor, DefaultHTTPInjector, diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py index 37f48ae16e..c59a4f40b8 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py @@ -78,13 +78,13 @@ def snapshot(cls) -> "Context": return snapshot @classmethod - def set_current(cls, context: "Context"): + def set_current(cls, ctx: "Context"): if _CONTEXT.current_context is None: _CONTEXT.current_context = _CONTEXT.register_slot( # change the key here "__current_prop_context__" ) - _CONTEXT.current_context.set(context.slot_name) + _CONTEXT.current_context.set(ctx.slot_name) @classmethod def use(cls, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: @@ -95,3 +95,4 @@ def register_slot( cls, name: str, default: "object" = None ) -> "BaseRuntimeContext.Slot": return _CONTEXT.register_slot(name, default) + From 4ca46d9ccad1b0cdcd942fb2bbebdd019b0f57ba Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Thu, 19 Dec 2019 13:26:42 -0800 Subject: [PATCH 44/71] Revert "tests fixed after context move" This reverts commit 1447d7f56c036b61f6f64af680287ecd10554cc4. --- .../src/opentelemetry/ext/flask/__init__.py | 5 +- .../ext/http_requests/__init__.py | 2 +- .../ext/opentracing_shim/__init__.py | 19 +++--- .../tests/test_shim.py | 58 +++++-------------- .../src/opentelemetry/ext/wsgi/__init__.py | 5 +- .../src/opentelemetry/context/__init__.py | 47 --------------- .../src/opentelemetry/propagation/__init__.py | 5 +- .../src/opentelemetry/sdk/context/__init__.py | 3 + .../sdk/trace/export/__init__.py | 4 +- 9 files changed, 37 insertions(+), 111 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 bc8b44e19f..4885ae0a1f 100644 --- a/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py +++ b/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py @@ -7,8 +7,7 @@ import opentelemetry.ext.wsgi as otel_wsgi from opentelemetry import propagation, trace -from opentelemetry.context import Context -from opentelemetry.trace.propagation import ContextKeys +from opentelemetry.trace.propagation.context import from_context from opentelemetry.util import time_ns logger = logging.getLogger(__name__) @@ -63,7 +62,7 @@ def _before_flask_request(): environ, get_from_carrier=otel_wsgi.get_header_from_environ ) - parent_span = Context.value(ContextKeys.span_context_key()) + parent_span = from_context() tracer = trace.tracer() attributes = otel_wsgi.collect_request_attributes(environ) 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 6f54841734..0601994b94 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 @@ -51,7 +51,7 @@ def enable(tracer): @functools.wraps(wrapped) def instrumented_request(self, method, url, *args, **kwargs): - if Context.value("suppress_instrumentation"): + if Context.suppress_instrumentation(): return wrapped(self, method, url, *args, **kwargs) # See 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 af50f90e4e..8ac39ef85c 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 @@ -85,9 +85,12 @@ import opentelemetry.trace as trace_api from opentelemetry import propagation -from opentelemetry.context import context as ctx +from opentelemetry.context import Context from opentelemetry.ext.opentracing_shim import util -from opentelemetry.trace.propagation import ContextKeys +from opentelemetry.trace.propagation.context import ( + from_context, + with_span_context, +) logger = logging.getLogger(__name__) @@ -650,8 +653,8 @@ def start_span( start_time=start_time_ns, ) - sc = SpanContextShim(span.get_context()) - return SpanShim(self, sc, span) + context = SpanContextShim(span.get_context()) + return SpanShim(self, context, span) def inject(self, span_context, format, carrier): """Implements the ``inject`` method from the base class.""" @@ -665,10 +668,8 @@ def inject(self, span_context, format, carrier): if format not in self._supported_formats: raise opentracing.UnsupportedFormatException - context = ctx().set_value( - ContextKeys.span_context_key(), span_context.unwrap() - ) - propagation.inject(carrier, context=context) + ctx = with_span_context(span_context.unwrap()) + propagation.inject(carrier, context=ctx) def extract(self, format, carrier): """Implements the ``extract`` method from the base class.""" @@ -683,6 +684,6 @@ def extract(self, format, carrier): raise opentracing.UnsupportedFormatException propagation.extract(carrier) - otel_context = ctx().value(ContextKeys.span_context_key()) + otel_context = from_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 8fb82e1bc8..0e6aaaae05 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -19,8 +19,6 @@ import opentelemetry.ext.opentracing_shim as opentracingshim from opentelemetry import propagation, trace -from opentelemetry.context import Context, set_preferred_context_implementation -from opentelemetry.context import context as ctx from opentelemetry.context.propagation import get_as_list, set_in_dict from opentelemetry.context.propagation.httptextformat import ( HTTPExtractor, @@ -28,37 +26,15 @@ ) from opentelemetry.ext.opentracing_shim import util from opentelemetry.sdk.trace import Tracer -from opentelemetry.trace.propagation import ContextKeys - - -class MockContext(Context): - values = {} - - @classmethod - def value(cls, key: str, context=None) -> "object": - print("getting things", key) - return cls.values.get(key) - - @classmethod - def set_value(cls, key: str, value: "object") -> "Context": - print("setting things", key, value) - cls.values[key] = value - - @classmethod - def current(cls) -> "Context": - """ TODO """ - - @classmethod - def snapshot(cls) -> "Context": - """ TODO """ - - @classmethod - def set_current(cls, context: "Context"): - """ TODO """ +from opentelemetry.trace.propagation.context import ( + from_context, + with_span_context, +) class TestShim(unittest.TestCase): # pylint: disable=too-many-public-methods + def setUp(self): """Create an OpenTelemetry tracer and a shim before every test case.""" @@ -71,7 +47,6 @@ def setUpClass(cls): every test method. """ - set_preferred_context_implementation(lambda T: MockContext()) trace.set_preferred_tracer_implementation(lambda T: Tracer()) # Save current propagator to be restored on teardown. @@ -543,9 +518,9 @@ def test_extract_http_headers(self): _SPAN_ID_KEY: 7478, } - context = self.shim.extract(opentracing.Format.HTTP_HEADERS, carrier) - self.assertEqual(context.unwrap().trace_id, 1220) - self.assertEqual(context.unwrap().span_id, 7478) + ctx = self.shim.extract(opentracing.Format.HTTP_HEADERS, carrier) + self.assertEqual(ctx.unwrap().trace_id, 1220) + self.assertEqual(ctx.unwrap().span_id, 7478) def test_extract_text_map(self): """Test `extract()` method for Format.TEXT_MAP.""" @@ -555,9 +530,9 @@ def test_extract_text_map(self): _SPAN_ID_KEY: 7478, } - context = self.shim.extract(opentracing.Format.TEXT_MAP, carrier) - self.assertEqual(context.unwrap().trace_id, 1220) - self.assertEqual(context.unwrap().span_id, 7478) + ctx = self.shim.extract(opentracing.Format.TEXT_MAP, carrier) + self.assertEqual(ctx.unwrap().trace_id, 1220) + self.assertEqual(ctx.unwrap().span_id, 7478) def test_extract_binary(self): """Test `extract()` method for Format.BINARY.""" @@ -580,15 +555,12 @@ def extract(cls, carrier, context=None, get_from_carrier=get_as_list): span_id_list = get_from_carrier(carrier, _SPAN_ID_KEY) if not trace_id_list or not span_id_list: - return ctx().set_value( - ContextKeys.span_context_key(), trace.INVALID_SPAN_CONTEXT - ) + return with_span_context(trace.INVALID_SPAN_CONTEXT) - return ctx().set_value( - ContextKeys.span_context_key(), + return with_span_context( trace.SpanContext( trace_id=int(trace_id_list[0]), span_id=int(span_id_list[0]) - ), + ) ) @@ -597,6 +569,6 @@ class MockHTTPInjector(HTTPInjector): @classmethod def inject(cls, carrier, context=None, set_in_carrier=set_in_dict): - sc = ctx().value(ContextKeys.span_context_key()) + sc = from_context(context) set_in_carrier(carrier, _TRACE_ID_KEY, str(sc.trace_id)) set_in_carrier(carrier, _SPAN_ID_KEY, str(sc.span_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 ed71711f08..6d8475f498 100644 --- a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py +++ b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py @@ -23,9 +23,8 @@ import wsgiref.util as wsgiref_util from opentelemetry import propagation, trace -from opentelemetry.context import Context from opentelemetry.ext.wsgi.version import __version__ # noqa -from opentelemetry.trace.propagation import ContextKeys +from opentelemetry.trace.propagation.context import from_context from opentelemetry.trace.status import Status, StatusCanonicalCode _HTTP_VERSION_PREFIX = "HTTP/" @@ -187,7 +186,7 @@ def __call__(self, environ, start_response): tracer = trace.tracer() propagation.extract(environ, get_from_carrier=get_header_from_environ) - parent_span = Context.value(ContextKeys.span_context_key()) + parent_span = from_context() span_name = get_default_span_name(environ) span = tracer.start_span( diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 3cc7a8b8ae..9a0cea76ac 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -138,7 +138,6 @@ async def main(): asyncio.run(main()) """ import typing -from opentelemetry.util import loader class Context: @@ -163,49 +162,3 @@ def snapshot(cls) -> "Context": @classmethod def set_current(cls, ctx: "Context"): """ TODO """ - - -# Once https://github.com/python/mypy/issues/7092 is resolved, -# the following type definition should be replaced with -# from opentelemetry.util.loader import ImplementationFactory -ImplementationFactory = typing.Callable[ - [typing.Type[Context]], typing.Optional[Context] -] - -_CONTEXT = None # type: typing.Optional[Context] -_CONTEXT_FACTORY = None # type: typing.Optional[ImplementationFactory] - - -def context() -> Context: - """Gets the current global :class:`~.Context` object. - - If there isn't one set yet, a default will be loaded. - """ - global _CONTEXT, _CONTEXT_FACTORY # pylint:disable=global-statement - - if _CONTEXT is None: - # pylint:disable=protected-access - _CONTEXT = loader._load_impl(Context, _CONTEXT_FACTORY) - del _CONTEXT_FACTORY - - return _CONTEXT - - -def set_preferred_context_implementation( - factory: ImplementationFactory, -) -> None: - """Set the factory to be used to create the context. - - See :mod:`opentelemetry.util.loader` for details. - - This function may not be called after a context is already loaded. - - Args: - factory: Callback that should create a new :class:`Context` instance. - """ - global _CONTEXT_FACTORY # pylint:disable=global-statement - - if _CONTEXT: - raise RuntimeError("Context already loaded.") - - _CONTEXT_FACTORY = factory diff --git a/opentelemetry-api/src/opentelemetry/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/propagation/__init__.py index 736e729333..b2833ff748 100644 --- a/opentelemetry-api/src/opentelemetry/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagation/__init__.py @@ -17,7 +17,6 @@ import opentelemetry.context.propagation.httptextformat as httptextformat import opentelemetry.trace as trace from opentelemetry.context import Context -from opentelemetry.context import context as ctx from opentelemetry.context.propagation import ( DefaultHTTPExtractor, DefaultHTTPInjector, @@ -50,7 +49,7 @@ def extract( which understands how to extract a value from it. """ if context is None: - context = ctx().current() + context = Context.current() if extractors is None: extractors = get_http_extractors() @@ -89,7 +88,7 @@ def inject( should know how to set header values on the carrier. """ if context is None: - context = ctx().current() + context = Context.current() if injectors is None: injectors = get_http_injectors() diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py index c59a4f40b8..0b02ed3860 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py @@ -96,3 +96,6 @@ def register_slot( ) -> "BaseRuntimeContext.Slot": return _CONTEXT.register_slot(name, default) + @classmethod + def suppress_instrumentation(cls) -> "object": + return _CONTEXT.suppress_instrumentation diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py index 8954bd93f6..2495de5a43 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py @@ -73,7 +73,7 @@ def on_start(self, span: Span) -> None: pass def on_end(self, span: Span) -> None: - with Context.set_value("suppress_instrumentation", True): + with Context.use(suppress_instrumentation=True): try: self.span_exporter.export((span,)) # pylint: disable=broad-except @@ -182,7 +182,7 @@ def export(self) -> None: while idx < self.max_export_batch_size and self.queue: self.spans_list[idx] = self.queue.pop() idx += 1 - with Context.set_value("suppress_instrumentation", True): + with Context.use(suppress_instrumentation=True): try: # Ignore type b/c the Optional[None]+slicing is too "clever" # for mypy From 48c2a7dbeac412bd7b1198b68943498b14f6ada4 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Thu, 19 Dec 2019 13:27:42 -0800 Subject: [PATCH 45/71] Revert "moving context implementation to sdk" This reverts commit 4c2c4de6ff1ec9d6f8ef4b9663db4f4d27f07e01. --- .../src/opentelemetry/context/__init__.py | 67 +++++++++++- .../opentelemetry}/context/async_context.py | 0 .../opentelemetry}/context/base_context.py | 0 .../context/propagation/__init__.py | 3 +- .../context/thread_local_context.py | 0 .../trace/propagation/context/__init__.py | 2 +- .../src/opentelemetry/sdk/context/__init__.py | 101 ------------------ .../sdk/context/propagation/b3_format.py | 5 +- .../propagation/tracecontexthttptextformat.py | 4 +- .../src/opentelemetry/sdk/trace/__init__.py | 8 +- .../sdk/trace/export/__init__.py | 2 +- .../context/propagation/test_b3_format.py | 2 +- .../test_tracecontexthttptextformat.py | 4 +- opentelemetry-sdk/tests/trace/test_trace.py | 2 +- 14 files changed, 80 insertions(+), 120 deletions(-) rename {opentelemetry-sdk/src/opentelemetry/sdk => opentelemetry-api/src/opentelemetry}/context/async_context.py (100%) rename {opentelemetry-sdk/src/opentelemetry/sdk => opentelemetry-api/src/opentelemetry}/context/base_context.py (100%) rename {opentelemetry-sdk/src/opentelemetry/sdk => opentelemetry-api/src/opentelemetry}/context/thread_local_context.py (100%) rename {opentelemetry-sdk/src/opentelemetry/sdk => opentelemetry-api/src/opentelemetry}/trace/propagation/context/__init__.py (96%) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 9a0cea76ac..685a6544f8 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -137,28 +137,85 @@ async def main(): if __name__ == '__main__': asyncio.run(main()) """ + +import copy import typing +from .base_context import BaseRuntimeContext + +__all__ = ["BaseRuntimeContext", "Context"] + +try: + from .async_context import AsyncRuntimeContext + + _CONTEXT = AsyncRuntimeContext() # type: BaseRuntimeContext +except ImportError: + from .thread_local_context import ThreadLocalRuntimeContext + + _CONTEXT = ThreadLocalRuntimeContext() + class Context: + def __init__(self): + self.contents = {} + self.slot_name = "{}".format(id(self)) + self._slot = _CONTEXT.register_slot(self.slot_name) + self._slot.set(self) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + + def get(self, key): + return self.contents.get(key) + @classmethod def value( cls, key: str, context: typing.Optional["Context"] = None ) -> "object": - """ TODO """ + if context is None: + return cls.current().contents.get(key) + return context.contents.get(key) @classmethod def set_value(cls, key: str, value: "object") -> "Context": - """ TODO """ + cls.current().contents[key] = value + return cls.snapshot() @classmethod def current(cls) -> "Context": - """ TODO """ + if _CONTEXT.current_context is None: + ctx = Context() + cls.set_current(ctx) + return getattr(_CONTEXT, _CONTEXT.current_context.get()) @classmethod def snapshot(cls) -> "Context": - """ TODO """ + snapshot = Context() + snapshot.contents = cls.current().contents.copy() + return snapshot @classmethod def set_current(cls, ctx: "Context"): - """ TODO """ + if _CONTEXT.current_context is None: + _CONTEXT.current_context = _CONTEXT.register_slot( + # change the key here + "__current_prop_context__" + ) + _CONTEXT.current_context.set(ctx.slot_name) + + @classmethod + def use(cls, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: + return _CONTEXT.use(**kwargs) + + @classmethod + def register_slot( + cls, name: str, default: "object" = None + ) -> "BaseRuntimeContext.Slot": + return _CONTEXT.register_slot(name, default) + + @classmethod + def suppress_instrumentation(cls) -> "object": + return _CONTEXT.suppress_instrumentation diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/async_context.py b/opentelemetry-api/src/opentelemetry/context/async_context.py similarity index 100% rename from opentelemetry-sdk/src/opentelemetry/sdk/context/async_context.py rename to opentelemetry-api/src/opentelemetry/context/async_context.py diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py similarity index 100% rename from opentelemetry-sdk/src/opentelemetry/sdk/context/base_context.py rename to opentelemetry-api/src/opentelemetry/context/base_context.py diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py index 78c36a3005..e53b2ae1d7 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py @@ -17,14 +17,15 @@ from .binaryformat import BinaryFormat from .httptextformat import ( + Getter, DefaultHTTPExtractor, DefaultHTTPInjector, - Getter, HTTPExtractor, HTTPInjector, Setter, ) + __all__ = [ "BinaryFormat", "Getter", diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/thread_local_context.py b/opentelemetry-api/src/opentelemetry/context/thread_local_context.py similarity index 100% rename from opentelemetry-sdk/src/opentelemetry/sdk/context/thread_local_context.py rename to opentelemetry-api/src/opentelemetry/context/thread_local_context.py diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py similarity index 96% rename from opentelemetry-sdk/src/opentelemetry/sdk/trace/propagation/context/__init__.py rename to opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py index 716abebed6..8c9dcf25e2 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py @@ -13,7 +13,7 @@ # limitations under the License. from typing import Optional -from opentelemetry.sdk.context import Context +from opentelemetry.context import Context from opentelemetry.trace import Span, SpanContext from opentelemetry.trace.propagation import ContextKeys diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py index 0b02ed3860..e69de29bb2 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/__init__.py @@ -1,101 +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 copy -import types -import typing - -from .base_context import BaseRuntimeContext - -__all__ = ["BaseRuntimeContext", "Context"] - -try: - from .async_context import AsyncRuntimeContext - - _CONTEXT = AsyncRuntimeContext() # type: BaseRuntimeContext -except ImportError: - from .thread_local_context import ThreadLocalRuntimeContext - - _CONTEXT = ThreadLocalRuntimeContext() - - -class Context: - def __init__(self): - self.contents = {} - self.slot_name = "{}".format(id(self)) - self._slot = _CONTEXT.register_slot(self.slot_name) - self._slot.set(self) - - def __enter__(self) -> "Context": - return self - - def __exit__( - self, - exc_type: typing.Optional[typing.Type[BaseException]], - exc_val: typing.Optional[BaseException], - exc_tb: typing.Optional[types.TracebackType], - ) -> None: - pass - - def get(self, key: str) -> "object": - return self.contents.get(key) - - @classmethod - def value( - cls, key: str, context: typing.Optional["Context"] = None - ) -> "object": - if context is None: - return cls.current().contents.get(key) - return context.contents.get(key) - - @classmethod - def set_value(cls, key: str, value: "object") -> "Context": - cls.current().contents[key] = value - return cls.snapshot() - - @classmethod - def current(cls) -> "Context": - if _CONTEXT.current_context is None: - ctx = Context() - cls.set_current(ctx) - return getattr(_CONTEXT, _CONTEXT.current_context.get()) - - @classmethod - def snapshot(cls) -> "Context": - snapshot = Context() - snapshot.contents = cls.current().contents.copy() - return snapshot - - @classmethod - def set_current(cls, ctx: "Context"): - if _CONTEXT.current_context is None: - _CONTEXT.current_context = _CONTEXT.register_slot( - # change the key here - "__current_prop_context__" - ) - _CONTEXT.current_context.set(ctx.slot_name) - - @classmethod - def use(cls, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: - return _CONTEXT.use(**kwargs) - - @classmethod - def register_slot( - cls, name: str, default: "object" = None - ) -> "BaseRuntimeContext.Slot": - return _CONTEXT.register_slot(name, default) - - @classmethod - def suppress_instrumentation(cls) -> "object": - return _CONTEXT.suppress_instrumentation 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 99f1bace3a..0212bc6c10 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py @@ -23,7 +23,7 @@ get_as_list, set_in_dict, ) -from opentelemetry.sdk.trace.propagation.context import ( +from opentelemetry.trace.propagation.context import ( Context, from_context, with_span_context, @@ -148,6 +148,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]: if items is None: return None diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py index 8882a7ecdf..6b654e1081 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py @@ -16,6 +16,7 @@ import typing import opentelemetry.trace as trace +from opentelemetry.context import Context from opentelemetry.context.propagation import ( Getter, HTTPExtractor, @@ -24,8 +25,7 @@ get_as_list, set_in_dict, ) -from opentelemetry.sdk.context import Context -from opentelemetry.sdk.trace.propagation.context import ( +from opentelemetry.trace.propagation.context import ( from_context, with_span_context, ) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 961c3b26a5..86ff4d9e7a 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -21,17 +21,17 @@ from typing import Iterator, Optional, Sequence, Tuple from opentelemetry import trace as trace_api +from opentelemetry.context import Context from opentelemetry.sdk import util -from opentelemetry.sdk.context import Context -from opentelemetry.sdk.trace.propagation.context import ( +from opentelemetry.sdk.util import BoundedDict, BoundedList +from opentelemetry.trace import sampling +from opentelemetry.trace.propagation.context import ( ContextKeys, from_context, span_from_context, with_span, with_span_context, ) -from opentelemetry.sdk.util import BoundedDict, BoundedList -from opentelemetry.trace import sampling from opentelemetry.util import time_ns, types logger = logging.getLogger(__name__) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py index 2495de5a43..442b2b2bac 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py @@ -18,7 +18,7 @@ import typing from enum import Enum -from opentelemetry.sdk.context import Context +from opentelemetry.context import Context from opentelemetry.util import time_ns from .. import Span, SpanProcessor diff --git a/opentelemetry-sdk/tests/context/propagation/test_b3_format.py b/opentelemetry-sdk/tests/context/propagation/test_b3_format.py index dee799ec4a..bcb2f4d392 100644 --- a/opentelemetry-sdk/tests/context/propagation/test_b3_format.py +++ b/opentelemetry-sdk/tests/context/propagation/test_b3_format.py @@ -17,7 +17,7 @@ import opentelemetry.sdk.context.propagation.b3_format as b3_format import opentelemetry.sdk.trace as trace import opentelemetry.trace as trace_api -from opentelemetry.sdk.trace.propagation.context import from_context +from opentelemetry.trace.propagation.context import from_context INJECTOR = b3_format.B3Injector EXTRACTOR = b3_format.B3Extractor diff --git a/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py index 718486e021..a994fc6767 100644 --- a/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py @@ -16,12 +16,12 @@ import unittest from opentelemetry import trace -from opentelemetry.sdk.context import Context +from opentelemetry.context import Context from opentelemetry.sdk.context.propagation.tracecontexthttptextformat import ( TraceContextHTTPExtractor, TraceContextHTTPInjector, ) -from opentelemetry.sdk.trace.propagation.context import ( +from opentelemetry.trace.propagation.context import ( from_context, with_span_context, ) diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index 3a87d4999c..e5a41e7ccd 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -18,8 +18,8 @@ from unittest import mock from opentelemetry import trace as trace_api +from opentelemetry.context import Context from opentelemetry.sdk import trace -from opentelemetry.sdk.context import Context from opentelemetry.trace import sampling from opentelemetry.util import time_ns From c7130a1888945aac77ddfacc20b821adb79c04ef Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Thu, 19 Dec 2019 13:29:31 -0800 Subject: [PATCH 46/71] lint fix Signed-off-by: Alex Boten --- .../src/opentelemetry/context/propagation/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py index e53b2ae1d7..78c36a3005 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py @@ -17,15 +17,14 @@ from .binaryformat import BinaryFormat from .httptextformat import ( - Getter, DefaultHTTPExtractor, DefaultHTTPInjector, + Getter, HTTPExtractor, HTTPInjector, Setter, ) - __all__ = [ "BinaryFormat", "Getter", From 516972306d87b4969e918d4b38154f045f3dfbaf Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Thu, 19 Dec 2019 13:54:54 -0800 Subject: [PATCH 47/71] fix tracecontext tests Signed-off-by: Alex Boten --- .../opentelemetry/trace/propagation/context/__init__.py | 3 +++ tests/w3c_tracecontext_validation_server.py | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py index 8c9dcf25e2..73a552c193 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py @@ -19,6 +19,9 @@ def from_context(ctx: Optional[Context] = None) -> SpanContext: + span = span_from_context(ctx) + if span: + return span.get_context() return Context.value(ContextKeys.span_context_key(), context=ctx) diff --git a/tests/w3c_tracecontext_validation_server.py b/tests/w3c_tracecontext_validation_server.py index a26141f14c..cff89d7f03 100644 --- a/tests/w3c_tracecontext_validation_server.py +++ b/tests/w3c_tracecontext_validation_server.py @@ -23,15 +23,20 @@ import flask import requests -from opentelemetry import trace +from opentelemetry import propagation, trace from opentelemetry.ext import http_requests from opentelemetry.ext.wsgi import OpenTelemetryMiddleware +from opentelemetry.sdk.context.propagation import tracecontexthttptextformat from opentelemetry.sdk.trace import Tracer from opentelemetry.sdk.trace.export import ( ConsoleSpanExporter, SimpleExportSpanProcessor, ) +(w3c_extractor, w3c_injector) = tracecontexthttptextformat.http_propagator() +propagation.set_http_extractors([w3c_extractor]) +propagation.set_http_injectors([w3c_injector]) + # The preferred tracer implementation must be set, as the opentelemetry-api # defines the interface with a no-op implementation. trace.set_preferred_tracer_implementation(lambda T: Tracer()) From 673224c10b58e3abe64a723c4580b48af5ab51a0 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Thu, 19 Dec 2019 14:39:29 -0800 Subject: [PATCH 48/71] mypy cleanup Signed-off-by: Alex Boten --- .../src/opentelemetry/context/__init__.py | 18 ++++++++++++------ .../context/propagation/__init__.py | 8 ++++++-- .../correlationcontext/__init__.py | 6 +++--- .../correlationcontext/propagation/__init__.py | 2 +- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 685a6544f8..5906267a3e 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -139,6 +139,7 @@ async def main(): """ import copy +import types import typing from .base_context import BaseRuntimeContext @@ -156,19 +157,24 @@ async def main(): class Context: - def __init__(self): - self.contents = {} + def __init__(self) -> None: + self.contents = {} # type: typing.Dict[str, object] self.slot_name = "{}".format(id(self)) self._slot = _CONTEXT.register_slot(self.slot_name) self._slot.set(self) - def __enter__(self): + def __enter__(self) -> "Context": return self - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__( + self, + exc_type: typing.Optional[typing.Type[BaseException]], + exc_val: typing.Optional[BaseException], + exc_tb: typing.Optional[types.TracebackType], + ) -> None: pass - def get(self, key): + def get(self, key: str) -> "object": return self.contents.get(key) @classmethod @@ -198,7 +204,7 @@ def snapshot(cls) -> "Context": return snapshot @classmethod - def set_current(cls, ctx: "Context"): + def set_current(cls, ctx: "Context") -> None: if _CONTEXT.current_context is None: _CONTEXT.current_context = _CONTEXT.register_slot( # change the key here diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py index 78c36a3005..fc97770d8e 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py @@ -36,7 +36,9 @@ ] -def get_as_list(dict_object: dict, key: str) -> typing.List[str]: +def get_as_list( + dict_object: typing.Dict[str, str], key: str +) -> typing.List[str]: value = dict_object.get(key) if value is None: return [] @@ -45,5 +47,7 @@ def get_as_list(dict_object: dict, key: str) -> typing.List[str]: return [value] -def set_in_dict(dict_object: dict, key: str, value: str) -> None: +def set_in_dict( + dict_object: typing.Dict[str, str], key: str, value: str +) -> None: dict_object[key] = value diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py index 0af4343473..82bbf726f2 100644 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py @@ -130,15 +130,15 @@ def set_correlation(cls, key: str, value: "object") -> Context: @classmethod def correlation( cls, key: str, context: typing.Optional[Context] = None - ) -> "object": + ) -> typing.Any: return Context.value(key, context=context) @classmethod - def remove_correlation(cls): + def remove_correlation(cls) -> Context: pass @classmethod - def clear_correlation(cls): + def clear_correlation(cls) -> Context: pass @classmethod diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/__init__.py index f720b0318e..7e75c5143e 100644 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/__init__.py @@ -28,6 +28,6 @@ class ContextKeys: KEY = "correlation-context" @classmethod - def span_context_key(cls): + def span_context_key(cls) -> str: """ TODO """ return cls.KEY From 66d67b8fa947d14515192de032a69460f2f50ea7 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Thu, 19 Dec 2019 16:39:24 -0800 Subject: [PATCH 49/71] fix context prop example --- .../context_propagation_example.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py index 15ce158eff..a0a250836b 100644 --- a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py +++ b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py @@ -76,8 +76,10 @@ def fetch_from_service_c() -> str: @app.route("/") def hello(): - tracer = trace.tracer_source() - tracer.add_span_processor(BatchExportSpanProcessor(ConsoleSpanExporter())) + tracer = trace.tracer_source().get_tracer(__name__) + trace.tracer_source().add_span_processor( + BatchExportSpanProcessor(ConsoleSpanExporter()) + ) with propagation.extract(request.headers): # extract a baggage header with tracer.start_as_current_span("service-span"): From 4442a043a5c62089bb37791ae917b056a3c697a7 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Thu, 19 Dec 2019 17:28:27 -0800 Subject: [PATCH 50/71] mypy cleanup. adding some ignores for now, will review later Signed-off-by: Alex Boten --- .../src/opentelemetry/context/__init__.py | 6 +-- .../context/propagation/__init__.py | 2 + .../context/propagation/httptextformat.py | 22 +++++------ .../correlationcontext/__init__.py | 38 +------------------ .../propagation/context/__init__.py | 2 +- .../propagation/httptextformat.py | 19 ++++++---- .../src/opentelemetry/propagation/__init__.py | 19 +++++----- .../trace/propagation/context/__init__.py | 6 +-- .../test_distributed_context.py | 4 +- 9 files changed, 45 insertions(+), 73 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 5906267a3e..4008b8cc3b 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -195,7 +195,7 @@ def current(cls) -> "Context": if _CONTEXT.current_context is None: ctx = Context() cls.set_current(ctx) - return getattr(_CONTEXT, _CONTEXT.current_context.get()) + return getattr(_CONTEXT, _CONTEXT.current_context.get()) # type: ignore @classmethod def snapshot(cls) -> "Context": @@ -210,11 +210,11 @@ def set_current(cls, ctx: "Context") -> None: # change the key here "__current_prop_context__" ) - _CONTEXT.current_context.set(ctx.slot_name) + _CONTEXT.current_context.set(ctx.slot_name) # type: ignore @classmethod def use(cls, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: - return _CONTEXT.use(**kwargs) + return _CONTEXT.use(**kwargs) # type: ignore @classmethod def register_slot( diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py index fc97770d8e..88a9d61870 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py @@ -17,6 +17,7 @@ from .binaryformat import BinaryFormat from .httptextformat import ( + ContextT, DefaultHTTPExtractor, DefaultHTTPInjector, Getter, @@ -27,6 +28,7 @@ __all__ = [ "BinaryFormat", + "ContextT", "Getter", "DefaultHTTPExtractor", "DefaultHTTPInjector", diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py index 3c2be01ac7..4de37933ec 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py @@ -17,10 +17,10 @@ from opentelemetry.context import Context -_T = typing.TypeVar("_T") +ContextT = typing.TypeVar("ContextT") -Setter = typing.Callable[[_T, str, str], None] -Getter = typing.Callable[[_T, str], typing.List[str]] +Setter = typing.Callable[[ContextT, str, str], None] +Getter = typing.Callable[[ContextT, str], typing.List[str]] class HTTPExtractor(abc.ABC): @@ -76,9 +76,9 @@ def example_route(): @abc.abstractmethod def extract( cls, - carrier: _T, + carrier: ContextT, context: typing.Optional[Context] = None, - get_from_carrier: typing.Optional[Getter[_T]] = None, + get_from_carrier: typing.Optional[Getter[ContextT]] = None, ) -> Context: """Create a SpanContext from values in the carrier. @@ -105,9 +105,9 @@ class HTTPInjector(abc.ABC): @abc.abstractmethod def inject( cls, - carrier: _T, + carrier: ContextT, context: typing.Optional[Context] = None, - set_in_carrier: typing.Optional[Setter[_T]] = None, + set_in_carrier: typing.Optional[Setter[ContextT]] = None, ) -> None: """Inject values from a SpanContext into a carrier. @@ -179,9 +179,9 @@ def example_route(): @classmethod def extract( cls, - carrier: _T, + carrier: ContextT, context: typing.Optional[Context] = None, - get_from_carrier: typing.Optional[Getter[_T]] = None, + get_from_carrier: typing.Optional[Getter[ContextT]] = None, ) -> Context: """Create a SpanContext from values in the carrier. @@ -210,9 +210,9 @@ class DefaultHTTPInjector(HTTPInjector): @classmethod def inject( cls, - carrier: _T, + carrier: ContextT, context: typing.Optional[Context] = None, - set_in_carrier: typing.Optional[Setter[_T]] = None, + set_in_carrier: typing.Optional[Setter[ContextT]] = None, ) -> None: """Inject values from a SpanContext into a carrier. diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py index 82bbf726f2..fcc2173fbc 100644 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py @@ -87,40 +87,6 @@ def __init__( class CorrelationContext: """A container for distributed context entries""" - KEY = "CorrelationContext" - - def __init__(self, entries: typing.Iterable[Entry]) -> None: - self._container = {entry.key: entry for entry in entries} - - @classmethod - def set_value( - cls, context: Context, entry_list: typing.Iterable[Entry] - ) -> None: - distributed_context = getattr(context, cls.KEY, {}) - for entry in entry_list: - distributed_context[entry.key] = entry - - @classmethod - def get_entries( - cls, context: typing.Optional[Context] = None - ) -> typing.Iterable[Entry]: - """Returns an immutable iterator to entries.""" - return getattr(context, cls.KEY, {}).values() - - @classmethod - def get_entry_value( - cls, key: EntryKey, context: typing.Optional[Context] = None, - ) -> typing.Optional[EntryValue]: - """Returns the entry associated with a key or None - - Args: - key: the key with which to perform a lookup - """ - container = getattr(context, cls.KEY, {}) - if key in container: - return container[key].value - return None - class CorrelationContextManager: @classmethod @@ -130,7 +96,7 @@ def set_correlation(cls, key: str, value: "object") -> Context: @classmethod def correlation( cls, key: str, context: typing.Optional[Context] = None - ) -> typing.Any: + ) -> "object": return Context.value(key, context=context) @classmethod @@ -143,4 +109,4 @@ def clear_correlation(cls) -> Context: @classmethod def http_propagator(cls) -> typing.Tuple[HTTPExtractor, HTTPInjector]: - return (CorrelationHTTPExtractor, CorrelationHTTPInjector) + return (CorrelationHTTPExtractor, CorrelationHTTPInjector) # type: ignore diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py index c14f96be70..52cc768599 100644 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py @@ -19,7 +19,7 @@ def from_context(ctx: Optional[Context] = None) -> CorrelationContext: - return Context.value(ContextKeys.span_context_key(), context=ctx) + return Context.value(ContextKeys.span_context_key(), context=ctx) # type: ignore def with_correlation_context( diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/httptextformat.py index 8ccb76a242..28e30431a9 100644 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/httptextformat.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/httptextformat.py @@ -15,10 +15,13 @@ import typing from opentelemetry.context import Context -from opentelemetry.context.propagation import HTTPExtractor, HTTPInjector - -Setter = typing.Callable[[object, str, str], None] -Getter = typing.Callable[[object, str], typing.List[str]] +from opentelemetry.context.propagation import ( + ContextT, + Getter, + HTTPExtractor, + HTTPInjector, + Setter, +) class CorrelationHTTPExtractor(HTTPExtractor): @@ -69,9 +72,9 @@ def example_route(): @classmethod def extract( cls, - carrier, + carrier: ContextT, context: typing.Optional[Context] = None, - get_from_carrier: typing.Optional[Getter] = None, + get_from_carrier: typing.Optional[Getter[ContextT]] = None, ) -> Context: """Create a CorrelationContext from values in the carrier. @@ -99,9 +102,9 @@ class CorrelationHTTPInjector(HTTPInjector): @classmethod def inject( cls, - carrier, + carrier: ContextT, context: typing.Optional[Context] = None, - set_in_carrier: typing.Optional[Setter] = None, + set_in_carrier: typing.Optional[Setter[ContextT]] = None, ) -> None: """Inject values from a CorrelationContext into a carrier. diff --git a/opentelemetry-api/src/opentelemetry/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/propagation/__init__.py index b2833ff748..f67bd2beb6 100644 --- a/opentelemetry-api/src/opentelemetry/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagation/__init__.py @@ -18,20 +18,21 @@ import opentelemetry.trace as trace from opentelemetry.context import Context from opentelemetry.context.propagation import ( + ContextT, DefaultHTTPExtractor, DefaultHTTPInjector, + Getter, + Setter, ) -_T = typing.TypeVar("_T") - def extract( - carrier: _T, + carrier: ContextT, context: typing.Optional[Context] = None, extractors: typing.Optional[ typing.List[httptextformat.HTTPExtractor] ] = None, - get_from_carrier: typing.Optional[typing.Callable] = None, + get_from_carrier: typing.Optional[Getter[ContextT]] = None, ) -> typing.Optional[Context]: """Load the parent SpanContext from values in the carrier. @@ -67,7 +68,7 @@ def extract( def inject( - carrier: _T, + carrier: ContextT, injectors: typing.Optional[ typing.List[httptextformat.HTTPInjector] ] = None, @@ -109,19 +110,19 @@ def set_http_extractors( extractor_list: typing.List[httptextformat.HTTPExtractor], ) -> None: global _HTTP_TEXT_EXTRACTORS # pylint:disable=global-statement - _HTTP_TEXT_EXTRACTORS = extractor_list + _HTTP_TEXT_EXTRACTORS = extractor_list # type: ignore def set_http_injectors( injector_list: typing.List[httptextformat.HTTPInjector], ) -> None: global _HTTP_TEXT_INJECTORS # pylint:disable=global-statement - _HTTP_TEXT_INJECTORS = injector_list + _HTTP_TEXT_INJECTORS = injector_list # type: ignore def get_http_extractors() -> typing.List[httptextformat.HTTPExtractor]: - return _HTTP_TEXT_EXTRACTORS + return _HTTP_TEXT_EXTRACTORS # type: ignore def get_http_injectors() -> typing.List[httptextformat.HTTPInjector]: - return _HTTP_TEXT_INJECTORS + return _HTTP_TEXT_INJECTORS # type: ignore diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py index 73a552c193..bb99b9746a 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py @@ -19,10 +19,10 @@ def from_context(ctx: Optional[Context] = None) -> SpanContext: - span = span_from_context(ctx) + span = span_from_context(context=ctx) if span: return span.get_context() - return Context.value(ContextKeys.span_context_key(), context=ctx) + return Context.value(ContextKeys.span_context_key(), context=ctx) # type: ignore def with_span_context(span_context: SpanContext) -> Context: @@ -30,7 +30,7 @@ def with_span_context(span_context: SpanContext) -> Context: def span_from_context(context: Optional[Context] = None) -> Span: - return Context.value(ContextKeys.span_key(), context=context) + return Context.value(ContextKeys.span_key(), context=context) # type: ignore def with_span(span: Span) -> Context: diff --git a/opentelemetry-sdk/tests/correlationcontext/test_distributed_context.py b/opentelemetry-sdk/tests/correlationcontext/test_distributed_context.py index 28c94e86de..adfca5b25b 100644 --- a/opentelemetry-sdk/tests/correlationcontext/test_distributed_context.py +++ b/opentelemetry-sdk/tests/correlationcontext/test_distributed_context.py @@ -27,13 +27,13 @@ def test_use_context(self): self.assertIsNone(self.manager.current_context()) # Start initial context - dctx = dctx_api.CorrelationContext(()) + dctx = dctx_api.CorrelationContext() with self.manager.use_context(dctx) as current: self.assertIs(current, dctx) self.assertIs(self.manager.current_context(), dctx) # Context is overridden - nested_dctx = dctx_api.CorrelationContext(()) + nested_dctx = dctx_api.CorrelationContext() with self.manager.use_context(nested_dctx) as current: self.assertIs(current, nested_dctx) self.assertIs(self.manager.current_context(), nested_dctx) From 17bb4b174a3062b39293bc89ade491febd7b5f7c Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Thu, 19 Dec 2019 22:33:04 -0800 Subject: [PATCH 51/71] adding context merge method --- .../src/opentelemetry/context/__init__.py | 6 +++ opentelemetry-api/tests/context/__init__.py | 13 ++++++ .../tests/context/test_context.py | 42 +++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 opentelemetry-api/tests/context/test_context.py diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 4008b8cc3b..10eacb0aec 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -225,3 +225,9 @@ def register_slot( @classmethod def suppress_instrumentation(cls) -> "object": return _CONTEXT.suppress_instrumentation + + +def merge_context_correlation(source: Context, dest: Context) -> Context: + for key, value in source.contents.items(): + dest.contents[key] = value + return dest diff --git a/opentelemetry-api/tests/context/__init__.py b/opentelemetry-api/tests/context/__init__.py index e69de29bb2..d853a7bcf6 100644 --- a/opentelemetry-api/tests/context/__init__.py +++ b/opentelemetry-api/tests/context/__init__.py @@ -0,0 +1,13 @@ +# 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/tests/context/test_context.py b/opentelemetry-api/tests/context/test_context.py new file mode 100644 index 0000000000..32233c91b8 --- /dev/null +++ b/opentelemetry-api/tests/context/test_context.py @@ -0,0 +1,42 @@ +# 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 unittest +from opentelemetry.context import Context, merge_context_correlation + + +class TestContext(unittest.TestCase): + def test_merge(self): + src_ctx = Context() + src_ctx.contents = { + "name": "first", + "somebool": True, + "key": "value", + "otherkey": "othervalue", + } + dst_ctx = Context() + dst_ctx.contents = { + "name": "second", + "somebool": False, + "anotherkey": "anothervalue", + } + Context.set_current(merge_context_correlation(src_ctx, dst_ctx)) + expected = { + "name": "first", + "somebool": True, + "key": "value", + "otherkey": "othervalue", + "anotherkey": "anothervalue", + } + self.assertEqual(expected, Context.current().contents) From b82dc78eae704488d1e7b411d355b0e2a9529243 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 8 Jan 2020 14:52:19 -0800 Subject: [PATCH 52/71] adding documentation Signed-off-by: Alex Boten --- .../src/opentelemetry/propagation/__init__.py | 16 ++++++++++++++++ .../opentelemetry/trace/propagation/__init__.py | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/propagation/__init__.py index f67bd2beb6..475b32d146 100644 --- a/opentelemetry-api/src/opentelemetry/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagation/__init__.py @@ -109,6 +109,10 @@ def inject( def set_http_extractors( extractor_list: typing.List[httptextformat.HTTPExtractor], ) -> None: + """ + To update the global extractor, the Propagation API provides a + function which takes an extractor. + """ global _HTTP_TEXT_EXTRACTORS # pylint:disable=global-statement _HTTP_TEXT_EXTRACTORS = extractor_list # type: ignore @@ -116,13 +120,25 @@ def set_http_extractors( def set_http_injectors( injector_list: typing.List[httptextformat.HTTPInjector], ) -> None: + """ + To update the global injector, the Propagation API provides a + function which takes an injector. + """ global _HTTP_TEXT_INJECTORS # pylint:disable=global-statement _HTTP_TEXT_INJECTORS = injector_list # type: ignore def get_http_extractors() -> typing.List[httptextformat.HTTPExtractor]: + """ + To access the global extractor, the Propagation API provides + a function which returns an extractor. + """ return _HTTP_TEXT_EXTRACTORS # type: ignore def get_http_injectors() -> typing.List[httptextformat.HTTPInjector]: + """ + To access the global injector, the Propagation API provides a + function which returns an injector. + """ return _HTTP_TEXT_INJECTORS # type: ignore diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py index 67de4cbc62..67a74dbac5 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py @@ -21,10 +21,10 @@ class ContextKeys: @classmethod def span_context_key(cls) -> str: - """ TODO """ + """ Returns key for a SpanContext """ return cls.EXTRACT_SPAN_CONTEXT_KEY @classmethod def span_key(cls) -> str: - """ TODO """ + """ Returns key for a Span """ return cls.SPAN_KEY From b850f993d28eb3d29eb3f5769072fa691056ffd2 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Thu, 9 Jan 2020 10:14:04 -0800 Subject: [PATCH 53/71] Adding tests for concurrency, fixing context Utilizing the underlying storage to ensure compatibility with contextvars and thread local storage. Signed-off-by: Alex Boten Signed-off-by: Alex Boten --- .../src/opentelemetry/context/__init__.py | 76 +++++---- .../tests/context/test_context.py | 42 ----- .../tests/context/test_context.py | 158 ++++++++++++++++++ 3 files changed, 204 insertions(+), 72 deletions(-) delete mode 100644 opentelemetry-api/tests/context/test_context.py create mode 100644 opentelemetry-sdk/tests/context/test_context.py diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 10eacb0aec..3dd671a1a9 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -158,21 +158,10 @@ async def main(): class Context: def __init__(self) -> None: - self.contents = {} # type: typing.Dict[str, object] self.slot_name = "{}".format(id(self)) self._slot = _CONTEXT.register_slot(self.slot_name) self._slot.set(self) - - def __enter__(self) -> "Context": - return self - - def __exit__( - self, - exc_type: typing.Optional[typing.Type[BaseException]], - exc_val: typing.Optional[BaseException], - exc_tb: typing.Optional[types.TracebackType], - ) -> None: - pass + self.contents = {} def get(self, key: str) -> "object": return self.contents.get(key) @@ -181,36 +170,54 @@ def get(self, key: str) -> "object": def value( cls, key: str, context: typing.Optional["Context"] = None ) -> "object": + """ + To access the local state of an concern, the Context API + provides a function which takes a context and a key as input, + and returns a value. + """ if context is None: - return cls.current().contents.get(key) - return context.contents.get(key) + if cls.current(): + return cls.current().get(key) + return None + return context.get(key) @classmethod def set_value(cls, key: str, value: "object") -> "Context": - cls.current().contents[key] = value + """ + To record the local state of a cross-cutting concern, the + Context API provides a function which takes a context, a + key, and a value as input, and returns an updated context + which contains the new value. + + Args: + key: + value: + """ + setattr(_CONTEXT, key, value) return cls.snapshot() @classmethod def current(cls) -> "Context": - if _CONTEXT.current_context is None: - ctx = Context() - cls.set_current(ctx) - return getattr(_CONTEXT, _CONTEXT.current_context.get()) # type: ignore + """ + To access the context associated with program execution, + the Context API provides a function which takes no arguments + and returns a Context. + """ + ctx = Context() + ctx.contents = _CONTEXT.snapshot() + return ctx @classmethod def snapshot(cls) -> "Context": - snapshot = Context() - snapshot.contents = cls.current().contents.copy() - return snapshot + return _CONTEXT.snapshot() @classmethod def set_current(cls, ctx: "Context") -> None: - if _CONTEXT.current_context is None: - _CONTEXT.current_context = _CONTEXT.register_slot( - # change the key here - "__current_prop_context__" - ) - _CONTEXT.current_context.set(ctx.slot_name) # type: ignore + """ + To associate a context with program execution, the Context + API provides a function which takes a Context. + """ + _CONTEXT.apply(ctx.contents) @classmethod def use(cls, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: @@ -226,8 +233,17 @@ def register_slot( def suppress_instrumentation(cls) -> "object": return _CONTEXT.suppress_instrumentation + @classmethod + def with_current_context( + cls, func: typing.Callable[..., "object"] + ) -> typing.Callable[..., "object"]: + return _CONTEXT.with_current_context(func) + + def apply(self, ctx: "Context") -> "Context": + for key in ctx.contents: + self.contents[key] = ctx.contents[key] + def merge_context_correlation(source: Context, dest: Context) -> Context: - for key, value in source.contents.items(): - dest.contents[key] = value + dest.apply(source) return dest diff --git a/opentelemetry-api/tests/context/test_context.py b/opentelemetry-api/tests/context/test_context.py deleted file mode 100644 index 32233c91b8..0000000000 --- a/opentelemetry-api/tests/context/test_context.py +++ /dev/null @@ -1,42 +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 unittest -from opentelemetry.context import Context, merge_context_correlation - - -class TestContext(unittest.TestCase): - def test_merge(self): - src_ctx = Context() - src_ctx.contents = { - "name": "first", - "somebool": True, - "key": "value", - "otherkey": "othervalue", - } - dst_ctx = Context() - dst_ctx.contents = { - "name": "second", - "somebool": False, - "anotherkey": "anothervalue", - } - Context.set_current(merge_context_correlation(src_ctx, dst_ctx)) - expected = { - "name": "first", - "somebool": True, - "key": "value", - "otherkey": "othervalue", - "anotherkey": "anothervalue", - } - self.assertEqual(expected, Context.current().contents) diff --git a/opentelemetry-sdk/tests/context/test_context.py b/opentelemetry-sdk/tests/context/test_context.py new file mode 100644 index 0000000000..704284ca46 --- /dev/null +++ b/opentelemetry-sdk/tests/context/test_context.py @@ -0,0 +1,158 @@ +# 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 concurrent.futures +import contextvars +from multiprocessing.dummy import Pool as ThreadPool + +import unittest + +from opentelemetry import trace as trace_api +from opentelemetry.sdk import trace +from opentelemetry.sdk.trace import export +from opentelemetry.sdk.trace.export.in_memory_span_exporter import ( + InMemorySpanExporter, +) + +from opentelemetry.context import Context, merge_context_correlation +from opentelemetry.trace import tracer_source + + +class TestContext(unittest.TestCase): + URLS = [ + "test_span1", + "test_span2", + "test_span3", + "test_span4", + "test_span5", + ] + + def do_some_work(self, name): + # try: + with self.tracer.start_as_current_span(name) as span: + pass + # except Exception as err: + # print(err) + + def setUp(self): + self.tracer_source = trace.TracerSource() + self.tracer = self.tracer_source.get_tracer(__name__) + self.memory_exporter = InMemorySpanExporter() + span_processor = export.SimpleExportSpanProcessor(self.memory_exporter) + self.tracer_source.add_span_processor(span_processor) + + def do_work(self): + Context.set_value("say-something", "bar") + + def test_context(self): + assert Context.value("say-something") is None + empty_context = Context.current() + Context.set_value("say-something", "foo") + assert Context.value("say-something") == "foo" + second_context = Context.current() + + self.do_work() + assert Context.value("say-something") == "bar" + third_context = Context.current() + + assert empty_context.get("say-something") is None + assert second_context.get("say-something") == "foo" + assert third_context.get("say-something") == "bar" + + def test_merge(self): + self.maxDiff = None + Context.set_value("name", "first") + Context.set_value("somebool", True) + Context.set_value("key", "value") + Context.set_value("otherkey", "othervalue") + src_ctx = Context.current() + + Context.set_value("name", "second") + Context.set_value("somebool", False) + Context.set_value("anotherkey", "anothervalue") + dst_ctx = Context.current() + + Context.set_current(merge_context_correlation(src_ctx, dst_ctx)) + self.assertEqual(Context.current().contents.get("name"), "first") + self.assertTrue(Context.current().contents.get("somebool")) + self.assertEqual(Context.current().contents.get("key"), "value") + self.assertEqual( + Context.current().contents.get("otherkey"), "othervalue" + ) + self.assertEqual( + Context.current().contents.get("anotherkey"), "anothervalue" + ) + + def test_propagation(self): + pass + + def test_with_futures(self): + with self.tracer.start_as_current_span("futures_test"): + with concurrent.futures.ThreadPoolExecutor( + max_workers=5 + ) as executor: + # Start the load operations and mark each future with its URL + future_to_url = { + executor.submit( + contextvars.copy_context().run, self.do_some_work, url, + ): url + for url in self.URLS + } + for future in concurrent.futures.as_completed(future_to_url): + url = future_to_url[future] + try: + data = future.result() + except Exception as exc: + print("%r generated an exception: %s" % (url, exc)) + else: + data_len = 0 + if data: + data_len = len(data) + print("%r page is %d bytes" % (url, data_len)) + + span_list = self.memory_exporter.get_finished_spans() + spans_names_list = [span.name for span in span_list] + self.assertListEqual( + [ + "test_span1", + "test_span2", + "test_span3", + "test_span4", + "test_span5", + "futures_test", + ], + spans_names_list, + ) + + def test_with_threads(self): + with self.tracer.start_as_current_span("threads_test"): + pool = ThreadPool(5) # create a thread pool + pool.map( + Context.with_current_context(self.do_some_work), self.URLS, + ) + pool.close() + pool.join() + span_list = self.memory_exporter.get_finished_spans() + spans_names_list = [span.name for span in span_list] + self.assertListEqual( + [ + "test_span1", + "test_span2", + "test_span3", + "test_span4", + "test_span5", + "threads_test", + ], + spans_names_list, + ) From 33a5b78dbe1d6c63c5acd87d907e137187859402 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Sun, 12 Jan 2020 22:39:24 -0800 Subject: [PATCH 54/71] Clean up tests and context Signed-off-by: Alex Boten --- .../src/opentelemetry/context/__init__.py | 5 +- .../tests/context/test_context.py | 60 +++++++------------ 2 files changed, 22 insertions(+), 43 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 3dd671a1a9..373e8ed0d4 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -158,9 +158,6 @@ async def main(): class Context: def __init__(self) -> None: - self.slot_name = "{}".format(id(self)) - self._slot = _CONTEXT.register_slot(self.slot_name) - self._slot.set(self) self.contents = {} def get(self, key: str) -> "object": @@ -190,7 +187,7 @@ def set_value(cls, key: str, value: "object") -> "Context": which contains the new value. Args: - key: + key: value: """ setattr(_CONTEXT, key, value) diff --git a/opentelemetry-sdk/tests/context/test_context.py b/opentelemetry-sdk/tests/context/test_context.py index 704284ca46..861add46ed 100644 --- a/opentelemetry-sdk/tests/context/test_context.py +++ b/opentelemetry-sdk/tests/context/test_context.py @@ -14,23 +14,23 @@ import concurrent.futures import contextvars -from multiprocessing.dummy import Pool as ThreadPool - import unittest +from multiprocessing.dummy import Pool as ThreadPool -from opentelemetry import trace as trace_api +from opentelemetry.context import Context, merge_context_correlation from opentelemetry.sdk import trace from opentelemetry.sdk.trace import export from opentelemetry.sdk.trace.export.in_memory_span_exporter import ( InMemorySpanExporter, ) -from opentelemetry.context import Context, merge_context_correlation -from opentelemetry.trace import tracer_source + +def do_work(): + Context.set_value("say-something", "bar") class TestContext(unittest.TestCase): - URLS = [ + spans = [ "test_span1", "test_span2", "test_span3", @@ -39,11 +39,8 @@ class TestContext(unittest.TestCase): ] def do_some_work(self, name): - # try: - with self.tracer.start_as_current_span(name) as span: + with self.tracer.start_as_current_span(name): pass - # except Exception as err: - # print(err) def setUp(self): self.tracer_source = trace.TracerSource() @@ -52,26 +49,22 @@ def setUp(self): span_processor = export.SimpleExportSpanProcessor(self.memory_exporter) self.tracer_source.add_span_processor(span_processor) - def do_work(self): - Context.set_value("say-something", "bar") - def test_context(self): - assert Context.value("say-something") is None + self.assertIsNone(Context.value("say-something")) empty_context = Context.current() Context.set_value("say-something", "foo") - assert Context.value("say-something") == "foo" + self.assertEqual(Context.value("say-something"), "foo") second_context = Context.current() - self.do_work() - assert Context.value("say-something") == "bar" + do_work() + self.assertEqual(Context.value("say-something"), "bar") third_context = Context.current() - assert empty_context.get("say-something") is None - assert second_context.get("say-something") == "foo" - assert third_context.get("say-something") == "bar" + self.assertIsNone(empty_context.get("say-something")) + self.assertEqual(second_context.get("say-something"), "foo") + self.assertEqual(third_context.get("say-something"), "bar") def test_merge(self): - self.maxDiff = None Context.set_value("name", "first") Context.set_value("somebool", True) Context.set_value("key", "value") @@ -102,24 +95,13 @@ def test_with_futures(self): with concurrent.futures.ThreadPoolExecutor( max_workers=5 ) as executor: - # Start the load operations and mark each future with its URL - future_to_url = { + # Start the load operations + for span in self.spans: executor.submit( - contextvars.copy_context().run, self.do_some_work, url, - ): url - for url in self.URLS - } - for future in concurrent.futures.as_completed(future_to_url): - url = future_to_url[future] - try: - data = future.result() - except Exception as exc: - print("%r generated an exception: %s" % (url, exc)) - else: - data_len = 0 - if data: - data_len = len(data) - print("%r page is %d bytes" % (url, data_len)) + contextvars.copy_context().run, + self.do_some_work, + span, + ) span_list = self.memory_exporter.get_finished_spans() spans_names_list = [span.name for span in span_list] @@ -139,7 +121,7 @@ def test_with_threads(self): with self.tracer.start_as_current_span("threads_test"): pool = ThreadPool(5) # create a thread pool pool.map( - Context.with_current_context(self.do_some_work), self.URLS, + Context.with_current_context(self.do_some_work), self.spans, ) pool.close() pool.join() From be91061a4d03c3f2d7b9bc03cbc1b4eb3d60b0c6 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 13 Jan 2020 15:24:17 -0800 Subject: [PATCH 55/71] rename from_context to span_context_from_context Signed-off-by: Alex Boten --- .../src/opentelemetry/ext/flask/__init__.py | 4 ++-- .../ext/opentracing_shim/__init__.py | 4 ++-- .../tests/test_shim.py | 4 ++-- .../src/opentelemetry/ext/wsgi/__init__.py | 4 ++-- .../propagation/context/__init__.py | 4 +++- .../trace/propagation/context/__init__.py | 2 +- .../test_distributed_context.py | 2 +- .../sdk/context/propagation/b3_format.py | 4 ++-- .../propagation/tracecontexthttptextformat.py | 4 ++-- .../src/opentelemetry/sdk/trace/__init__.py | 4 ++-- .../context/propagation/test_b3_format.py | 8 ++++---- .../test_tracecontexthttptextformat.py | 18 ++++++++++-------- 12 files changed, 33 insertions(+), 29 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 6ac34978ec..25f9865b5e 100644 --- a/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py +++ b/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py @@ -8,7 +8,7 @@ import opentelemetry.ext.wsgi as otel_wsgi from opentelemetry import propagation, trace from opentelemetry.ext.flask.version import __version__ -from opentelemetry.trace.propagation.context import from_context +from opentelemetry.trace.propagation.context import span_context_from_context from opentelemetry.util import time_ns logger = logging.getLogger(__name__) @@ -63,7 +63,7 @@ def _before_flask_request(): environ, get_from_carrier=otel_wsgi.get_header_from_environ ) - parent_span = from_context() + parent_span = span_context_from_context() tracer = trace.tracer_source().get_tracer(__name__, __version__) attributes = otel_wsgi.collect_request_attributes(environ) 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 b29465498e..5b65ed2d34 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 @@ -91,7 +91,7 @@ from opentelemetry.ext.opentracing_shim import util from opentelemetry.ext.opentracing_shim.version import __version__ from opentelemetry.trace.propagation.context import ( - from_context, + span_context_from_context, with_span_context, ) @@ -691,6 +691,6 @@ def extract(self, format, carrier): raise opentracing.UnsupportedFormatException propagation.extract(carrier) - otel_context = from_context() + otel_context = span_context_from_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 696d793c56..e319f4511d 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -27,7 +27,7 @@ from opentelemetry.ext.opentracing_shim import util from opentelemetry.sdk.trace import TracerSource from opentelemetry.trace.propagation.context import ( - from_context, + span_context_from_context, with_span_context, ) @@ -570,6 +570,6 @@ class MockHTTPInjector(HTTPInjector): @classmethod def inject(cls, carrier, context=None, set_in_carrier=set_in_dict): - sc = from_context(context) + sc = span_context_from_context(context) set_in_carrier(carrier, _TRACE_ID_KEY, str(sc.trace_id)) set_in_carrier(carrier, _SPAN_ID_KEY, str(sc.span_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 3e9023acd5..53cc337798 100644 --- a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py +++ b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py @@ -24,7 +24,7 @@ from opentelemetry import propagation, trace from opentelemetry.ext.wsgi.version import __version__ -from opentelemetry.trace.propagation.context import from_context +from opentelemetry.trace.propagation.context import span_context_from_context from opentelemetry.trace.status import Status, StatusCanonicalCode _HTTP_VERSION_PREFIX = "HTTP/" @@ -186,7 +186,7 @@ def __call__(self, environ, start_response): propagation.extract(environ, get_from_carrier=get_header_from_environ) - parent_span = from_context() + parent_span = span_context_from_context() span_name = get_default_span_name(environ) span = self.tracer.start_span( diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py index 52cc768599..b0b2b2a302 100644 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py @@ -18,7 +18,9 @@ from opentelemetry.correlationcontext.propagation import ContextKeys -def from_context(ctx: Optional[Context] = None) -> CorrelationContext: +def correlation_context_from_context( + ctx: Optional[Context] = None, +) -> CorrelationContext: return Context.value(ContextKeys.span_context_key(), context=ctx) # type: ignore diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py index bb99b9746a..11a0c9fba3 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py @@ -18,7 +18,7 @@ from opentelemetry.trace.propagation import ContextKeys -def from_context(ctx: Optional[Context] = None) -> SpanContext: +def span_context_from_context(ctx: Optional[Context] = None) -> SpanContext: span = span_from_context(context=ctx) if span: return span.get_context() diff --git a/opentelemetry-api/tests/correlationcontext/test_distributed_context.py b/opentelemetry-api/tests/correlationcontext/test_distributed_context.py index 00d3b43a42..0ea05e2c9b 100644 --- a/opentelemetry-api/tests/correlationcontext/test_distributed_context.py +++ b/opentelemetry-api/tests/correlationcontext/test_distributed_context.py @@ -83,7 +83,7 @@ def test_key_new(self): # def test_get_entries(self): # self.assertIn( -# self.entry, from_context(self.context).get_entries(), +# self.entry, correlation_context_from_context(self.context).get_entries(), # ) # def test_get_entry_value_present(self): 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 0212bc6c10..695076fbc6 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py @@ -25,7 +25,7 @@ ) from opentelemetry.trace.propagation.context import ( Context, - from_context, + span_context_from_context, with_span_context, ) @@ -131,7 +131,7 @@ def inject( context: typing.Optional[Context] = None, set_in_carrier: typing.Optional[Setter[_T]] = set_in_dict, ): - sc = from_context(context) + sc = span_context_from_context(context) sampled = (trace.TraceOptions.SAMPLED & sc.trace_options) != 0 set_in_carrier(carrier, TRACE_ID_KEY, format_trace_id(sc.trace_id)) set_in_carrier(carrier, SPAN_ID_KEY, format_span_id(sc.span_id)) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py index 6b654e1081..3879bc6689 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py @@ -26,7 +26,7 @@ set_in_dict, ) from opentelemetry.trace.propagation.context import ( - from_context, + span_context_from_context, with_span_context, ) @@ -134,7 +134,7 @@ def inject( context: typing.Optional[Context] = None, set_in_carrier: typing.Optional[Setter[_T]] = set_in_dict, ) -> None: - sc = from_context(context) + sc = span_context_from_context(context) if sc is None or sc == trace.INVALID_SPAN_CONTEXT: return diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index c833da631f..d95220c6c8 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -28,7 +28,7 @@ from opentelemetry.trace import SpanContext, sampling from opentelemetry.trace.propagation.context import ( ContextKeys, - from_context, + span_context_from_context, span_from_context, with_span, with_span_context, @@ -433,7 +433,7 @@ def start_span( # pylint: disable=too-many-locals parent_context = parent.get_context() if parent_context is None: - parent_context = from_context(context) + parent_context = span_context_from_context(context) if parent_context is not None and not isinstance( parent_context, trace_api.SpanContext diff --git a/opentelemetry-sdk/tests/context/propagation/test_b3_format.py b/opentelemetry-sdk/tests/context/propagation/test_b3_format.py index bcb2f4d392..646909df1a 100644 --- a/opentelemetry-sdk/tests/context/propagation/test_b3_format.py +++ b/opentelemetry-sdk/tests/context/propagation/test_b3_format.py @@ -17,7 +17,7 @@ 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.context import from_context +from opentelemetry.trace.propagation.context import span_context_from_context INJECTOR = b3_format.B3Injector EXTRACTOR = b3_format.B3Extractor @@ -176,7 +176,7 @@ def test_invalid_single_header(self): invalid SpanContext. """ carrier = {EXTRACTOR.SINGLE_HEADER_KEY: "0-1-2-3-4-5-6-7"} - span_context = from_context(EXTRACTOR.extract(carrier)) + span_context = span_context_from_context(EXTRACTOR.extract(carrier)) self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID) self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID) @@ -186,7 +186,7 @@ def test_missing_trace_id(self): b3_format.SPAN_ID_KEY: self.serialized_span_id, EXTRACTOR.FLAGS_KEY: "1", } - span_context = from_context(EXTRACTOR.extract(carrier)) + span_context = span_context_from_context(EXTRACTOR.extract(carrier)) self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID) def test_missing_span_id(self): @@ -195,5 +195,5 @@ def test_missing_span_id(self): b3_format.TRACE_ID_KEY: self.serialized_trace_id, EXTRACTOR.FLAGS_KEY: "1", } - span_context = from_context(EXTRACTOR.extract(carrier)) + span_context = span_context_from_context(EXTRACTOR.extract(carrier)) self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID) diff --git a/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py index a994fc6767..c9aaec6748 100644 --- a/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py @@ -22,7 +22,7 @@ TraceContextHTTPInjector, ) from opentelemetry.trace.propagation.context import ( - from_context, + span_context_from_context, with_span_context, ) @@ -46,7 +46,9 @@ def test_no_traceparent_header(self): If no traceparent header is received, the vendor creates a new trace-id and parent-id that represents the current request. """ output = {} # type:typing.Dict[str, typing.List[str]] - span_context = from_context(EXTRACT.extract(output, self.ctx)) + span_context = span_context_from_context( + EXTRACT.extract(output, self.ctx) + ) self.assertTrue(isinstance(span_context, trace.SpanContext)) def test_headers_with_tracestate(self): @@ -58,7 +60,7 @@ def test_headers_with_tracestate(self): span_id=format(self.SPAN_ID, "016x"), ) tracestate_value = "foo=1,bar=2,baz=3" - span_context = from_context( + span_context = span_context_from_context( EXTRACT.extract( { "traceparent": [traceparent_value], @@ -96,7 +98,7 @@ def test_invalid_trace_id(self): If the vendor failed to parse traceparent, it MUST NOT attempt to parse tracestate. Note that the opposite is not true: failure to parse tracestate MUST NOT affect the parsing of traceparent. """ - span_context = from_context( + span_context = span_context_from_context( EXTRACT.extract( { "traceparent": [ @@ -124,7 +126,7 @@ def test_invalid_parent_id(self): If the vendor failed to parse traceparent, it MUST NOT attempt to parse tracestate. Note that the opposite is not true: failure to parse tracestate MUST NOT affect the parsing of traceparent. """ - span_context = from_context( + span_context = span_context_from_context( EXTRACT.extract( { "traceparent": [ @@ -161,7 +163,7 @@ def test_format_not_supported(self): If the version cannot be parsed, return an invalid trace header. """ - span_context = from_context( + span_context = span_context_from_context( EXTRACT.extract( { "traceparent": [ @@ -184,7 +186,7 @@ def test_propagate_invalid_context(self): def test_tracestate_empty_header(self): """Test tracestate with an additional empty header (should be ignored)""" - span_context = from_context( + span_context = span_context_from_context( EXTRACT.extract( { "traceparent": [ @@ -200,7 +202,7 @@ def test_tracestate_empty_header(self): def test_tracestate_header_with_trailing_comma(self): """Do not propagate invalid trace context. """ - span_context = from_context( + span_context = span_context_from_context( EXTRACT.extract( { "traceparent": [ From f84c4d3aff264d39dcd92854dd7b4bb1f4442ae7 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 13 Jan 2020 15:40:21 -0800 Subject: [PATCH 56/71] Fix example Signed-off-by: Alex Boten --- .../context_propagation_example.py | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py index a0a250836b..bfe04ffb44 100644 --- a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py +++ b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py @@ -54,21 +54,27 @@ def configure_opentelemetry(flask_app: flask.Flask): def fetch_from_service_b() -> str: - # Inject the contexts to be propagated. Note that there is no direct - # reference to tracing or baggage. - headers = {"Accept": "application/json"} - propagation.inject(headers) - resp = requests.get("https://opentelemetry.io", headers=headers) - return resp.text + with trace.tracer_source().get_tracer(__name__).start_as_current_span( + "fetch_from_service_b" + ): + # Inject the contexts to be propagated. Note that there is no direct + # reference to tracing or baggage. + headers = {"Accept": "text/html"} + propagation.inject(headers) + resp = requests.get("https://opentelemetry.io", headers=headers) + return resp.text def fetch_from_service_c() -> str: - # Inject the contexts to be propagated. Note that there is no direct - # reference to tracing or baggage. - headers = {"Accept": "application/json"} - propagation.inject(headers) - resp = requests.get("https://opentelemetry.io", headers=headers) - return resp.text + with trace.tracer_source().get_tracer(__name__).start_as_current_span( + "fetch_from_service_c" + ): + # Inject the contexts to be propagated. Note that there is no direct + # reference to tracing or baggage. + headers = {"Accept": "application/json"} + propagation.inject(headers) + resp = requests.get("https://opentelemetry.io", headers=headers) + return resp.text app = flask.Flask(__name__) @@ -80,13 +86,14 @@ def hello(): trace.tracer_source().add_span_processor( BatchExportSpanProcessor(ConsoleSpanExporter()) ) - with propagation.extract(request.headers): - # extract a baggage header - with tracer.start_as_current_span("service-span"): - with tracer.start_as_current_span("external-req-span"): - version = CorrelationContextManager.correlation("version") - if version == "2.0": - return fetch_from_service_c() + # extract a baggage header + propagation.extract(request.headers) + + with tracer.start_as_current_span("service-span"): + with tracer.start_as_current_span("external-req-span"): + version = CorrelationContextManager.correlation("version") + if version == "2.0": + return fetch_from_service_c() return fetch_from_service_b() From b1ba228eff968e4623247b06767b335a3ddc660c Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 13 Jan 2020 16:38:27 -0800 Subject: [PATCH 57/71] Context cleanup Signed-off-by: Alex Boten --- .../src/opentelemetry/context/__init__.py | 16 +++------------- .../sdk/correlationcontext/__init__.py | 14 ++++++-------- .../src/opentelemetry/sdk/trace/__init__.py | 7 ++++--- .../test_tracecontexthttptextformat.py | 2 +- 4 files changed, 14 insertions(+), 25 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 373e8ed0d4..ee9611b037 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -160,7 +160,7 @@ class Context: def __init__(self) -> None: self.contents = {} - def get(self, key: str) -> "object": + def get(self, key: str) -> typing.Optional["object"]: return self.contents.get(key) @classmethod @@ -191,7 +191,7 @@ def set_value(cls, key: str, value: "object") -> "Context": value: """ setattr(_CONTEXT, key, value) - return cls.snapshot() + return _CONTEXT.snapshot() @classmethod def current(cls) -> "Context": @@ -204,10 +204,6 @@ def current(cls) -> "Context": ctx.contents = _CONTEXT.snapshot() return ctx - @classmethod - def snapshot(cls) -> "Context": - return _CONTEXT.snapshot() - @classmethod def set_current(cls, ctx: "Context") -> None: """ @@ -220,12 +216,6 @@ def set_current(cls, ctx: "Context") -> None: def use(cls, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: return _CONTEXT.use(**kwargs) # type: ignore - @classmethod - def register_slot( - cls, name: str, default: "object" = None - ) -> "BaseRuntimeContext.Slot": - return _CONTEXT.register_slot(name, default) - @classmethod def suppress_instrumentation(cls) -> "object": return _CONTEXT.suppress_instrumentation @@ -236,7 +226,7 @@ def with_current_context( ) -> typing.Callable[..., "object"]: return _CONTEXT.with_current_context(func) - def apply(self, ctx: "Context") -> "Context": + def apply(self, ctx: "Context") -> None: for key in ctx.contents: self.contents[key] = ctx.contents[key] diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py index aac209cbf0..7415bb3245 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py @@ -28,11 +28,9 @@ class CorrelationContextManager(dctx_api.CorrelationContextManager): def __init__(self, name: str = "") -> None: if name: - slot_name = "CorrelationContext.{}".format(name) + self.slot_name = "CorrelationContext.{}".format(name) else: - slot_name = "CorrelationContext" - - self._current_context = Context.register_slot(slot_name) + self.slot_name = "CorrelationContext" def current_context(self,) -> typing.Optional[dctx_api.CorrelationContext]: """Gets the current CorrelationContext. @@ -40,7 +38,7 @@ def current_context(self,) -> typing.Optional[dctx_api.CorrelationContext]: Returns: A CorrelationContext instance representing the current context. """ - return self._current_context.get() + return Context.value(self.slot_name) @contextmanager def use_context( @@ -56,9 +54,9 @@ def use_context( Args: context: A CorrelationContext instance to make current. """ - snapshot = self._current_context.get() - self._current_context.set(context) + snapshot = Context.value(self.slot_name) + Context.set_value(self.slot_name, context) try: yield context finally: - self._current_context.set(snapshot) + Context.set_value(self.slot_name, snapshot) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index d95220c6c8..4ef1ac9e9f 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -518,7 +518,7 @@ def __init__( ): # TODO: How should multiple TracerSources behave? Should they get their own contexts? # This could be done by adding `str(id(self))` to the slot name. - self._current_span_slot = Context.register_slot("current_span") + self._current_span_name = "current_span" self._active_span_processor = MultiSpanProcessor() self.sampler = sampler self._atexit_handler = None @@ -540,8 +540,9 @@ def get_tracer( ), ) - def get_current_span(self) -> Span: - return self._current_span_slot.get() + def get_current_span(self, context: Optional[Context] = None) -> Span: + """See `opentelemetry.trace.Tracer.get_current_span`.""" + return Context.value(self._current_span_name, context=context) def add_span_processor(self, span_processor: SpanProcessor) -> None: """Registers a new :class:`SpanProcessor` for this `TracerSource`. diff --git a/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py index c9aaec6748..37321214d1 100644 --- a/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py @@ -35,7 +35,7 @@ class TestTraceContextFormat(unittest.TestCase): SPAN_ID = int("1234567890123456", 16) # type:int def setUp(self): - self.ctx = Context.snapshot() + self.ctx = Context.current() def test_no_traceparent_header(self): """When tracecontext headers are not present, a new SpanContext From c3906eae52819d526e353016d3cd2c36edfb1695 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 14 Jan 2020 19:49:14 -0800 Subject: [PATCH 58/71] Revert behaviour of no context to INVALID_SPAN_CONTEXT Signed-off-by: Alex Boten --- .../tests/test_flask_integration.py | 6 +++--- ext/opentelemetry-ext-wsgi/tests/test_wsgi_middleware.py | 2 +- opentelemetry-api/src/opentelemetry/context/__init__.py | 4 +--- .../opentelemetry/trace/propagation/context/__init__.py | 8 ++++++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/ext/opentelemetry-ext-flask/tests/test_flask_integration.py b/ext/opentelemetry-ext-flask/tests/test_flask_integration.py index 096867d927..b943a25f22 100644 --- a/ext/opentelemetry-ext-flask/tests/test_flask_integration.py +++ b/ext/opentelemetry-ext-flask/tests/test_flask_integration.py @@ -55,7 +55,7 @@ def test_simple(self): self.start_span.assert_called_with( "hello_endpoint", - None, + trace_api.INVALID_SPAN_CONTEXT, kind=trace_api.SpanKind.SERVER, attributes={ "component": "http", @@ -85,7 +85,7 @@ def test_404(self): self.start_span.assert_called_with( "/bye", - None, + trace_api.INVALID_SPAN_CONTEXT, kind=trace_api.SpanKind.SERVER, attributes={ "component": "http", @@ -116,7 +116,7 @@ def test_internal_error(self): self.start_span.assert_called_with( "hello_endpoint", - None, + trace_api.INVALID_SPAN_CONTEXT, kind=trace_api.SpanKind.SERVER, attributes={ "component": "http", diff --git a/ext/opentelemetry-ext-wsgi/tests/test_wsgi_middleware.py b/ext/opentelemetry-ext-wsgi/tests/test_wsgi_middleware.py index b2522b28fb..a78b4d19f0 100644 --- a/ext/opentelemetry-ext-wsgi/tests/test_wsgi_middleware.py +++ b/ext/opentelemetry-ext-wsgi/tests/test_wsgi_middleware.py @@ -98,7 +98,7 @@ def validate_response(self, response, error=None): # Verify that start_span has been called self.start_span.assert_called_with( "/", - None, + trace_api.INVALID_SPAN_CONTEXT, kind=trace_api.SpanKind.SERVER, attributes={ "component": "http", diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index ee9611b037..6f107803d8 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -138,8 +138,6 @@ async def main(): asyncio.run(main()) """ -import copy -import types import typing from .base_context import BaseRuntimeContext @@ -191,7 +189,7 @@ def set_value(cls, key: str, value: "object") -> "Context": value: """ setattr(_CONTEXT, key, value) - return _CONTEXT.snapshot() + return cls.current() @classmethod def current(cls) -> "Context": diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py index 11a0c9fba3..e58968b1dd 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py @@ -14,7 +14,7 @@ from typing import Optional from opentelemetry.context import Context -from opentelemetry.trace import Span, SpanContext +from opentelemetry.trace import Span, SpanContext, INVALID_SPAN_CONTEXT from opentelemetry.trace.propagation import ContextKeys @@ -22,7 +22,11 @@ def span_context_from_context(ctx: Optional[Context] = None) -> SpanContext: span = span_from_context(context=ctx) if span: return span.get_context() - return Context.value(ContextKeys.span_context_key(), context=ctx) # type: ignore + sc = Context.value(ContextKeys.span_context_key(), context=ctx) # type: ignore + if sc: + return sc + + return INVALID_SPAN_CONTEXT def with_span_context(span_context: SpanContext) -> Context: From 8d0b14267a6c8e1b6d8b61d20becb687e4869a5a Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 14 Jan 2020 22:23:06 -0800 Subject: [PATCH 59/71] More refactors of Context Signed-off-by: Alex Boten --- .../src/opentelemetry/context/__init__.py | 58 +++++++++++++------ .../src/opentelemetry/context/base_context.py | 29 ---------- .../tests/context/test_context.py | 14 ++--- 3 files changed, 45 insertions(+), 56 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 6f107803d8..fd161a6ee4 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -140,9 +140,11 @@ async def main(): import typing +from contextlib import contextmanager + from .base_context import BaseRuntimeContext -__all__ = ["BaseRuntimeContext", "Context"] +__all__ = ["Context"] try: from .async_context import AsyncRuntimeContext @@ -156,25 +158,26 @@ async def main(): class Context: def __init__(self) -> None: - self.contents = {} + self.snapshot = _CONTEXT.snapshot() def get(self, key: str) -> typing.Optional["object"]: - return self.contents.get(key) + return self.snapshot.get(key) @classmethod def value( cls, key: str, context: typing.Optional["Context"] = None - ) -> "object": + ) -> typing.Optional["object"]: """ To access the local state of an concern, the Context API provides a function which takes a context and a key as input, and returns a value. """ - if context is None: - if cls.current(): - return cls.current().get(key) - return None - return context.get(key) + if context: + return context.get(key) + + if cls.current(): + return cls.current().get(key) + return None @classmethod def set_value(cls, key: str, value: "object") -> "Context": @@ -198,21 +201,25 @@ def current(cls) -> "Context": the Context API provides a function which takes no arguments and returns a Context. """ - ctx = Context() - ctx.contents = _CONTEXT.snapshot() - return ctx + return Context() @classmethod - def set_current(cls, ctx: "Context") -> None: + def set_current(cls, context: "Context") -> None: """ To associate a context with program execution, the Context API provides a function which takes a Context. """ - _CONTEXT.apply(ctx.contents) + _CONTEXT.apply(context.snapshot) @classmethod + @contextmanager def use(cls, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: - return _CONTEXT.use(**kwargs) # type: ignore + snapshot = {key: _CONTEXT[key] for key in kwargs} + for key in kwargs: + _CONTEXT[key] = kwargs[key] + yield + for key in kwargs: + _CONTEXT[key] = snapshot[key] @classmethod def suppress_instrumentation(cls) -> "object": @@ -222,11 +229,26 @@ def suppress_instrumentation(cls) -> "object": def with_current_context( cls, func: typing.Callable[..., "object"] ) -> typing.Callable[..., "object"]: - return _CONTEXT.with_current_context(func) + """Capture the current context and apply it to the provided func. + """ + + caller_context = _CONTEXT.snapshot() + + def call_with_current_context( + *args: "object", **kwargs: "object" + ) -> "object": + try: + backup_context = _CONTEXT.snapshot() + _CONTEXT.apply(caller_context) + return func(*args, **kwargs) + finally: + _CONTEXT.apply(backup_context) + + return call_with_current_context def apply(self, ctx: "Context") -> None: - for key in ctx.contents: - self.contents[key] = ctx.contents[key] + for key in ctx.snapshot: + self.snapshot[key] = ctx.snapshot[key] def merge_context_correlation(source: Context, dest: Context) -> Context: diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py index 99d6869dd5..b59c23e932 100644 --- a/opentelemetry-api/src/opentelemetry/context/base_context.py +++ b/opentelemetry-api/src/opentelemetry/context/base_context.py @@ -14,7 +14,6 @@ import threading import typing -from contextlib import contextmanager def wrap_callable(target: "object") -> typing.Callable[[], object]: @@ -100,31 +99,3 @@ def __getitem__(self, name: str) -> "object": def __setitem__(self, name: str, value: "object") -> None: self.__setattr__(name, value) - @contextmanager # type: ignore - def use(self, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: - snapshot = {key: self[key] for key in kwargs} - for key in kwargs: - self[key] = kwargs[key] - yield - for key in kwargs: - self[key] = snapshot[key] - - def with_current_context( - self, func: typing.Callable[..., "object"] - ) -> typing.Callable[..., "object"]: - """Capture the current context and apply it to the provided func. - """ - - caller_context = self.snapshot() - - def call_with_current_context( - *args: "object", **kwargs: "object" - ) -> "object": - try: - backup_context = self.snapshot() - self.apply(caller_context) - return func(*args, **kwargs) - finally: - self.apply(backup_context) - - return call_with_current_context diff --git a/opentelemetry-sdk/tests/context/test_context.py b/opentelemetry-sdk/tests/context/test_context.py index 861add46ed..b803db23ab 100644 --- a/opentelemetry-sdk/tests/context/test_context.py +++ b/opentelemetry-sdk/tests/context/test_context.py @@ -77,15 +77,11 @@ def test_merge(self): dst_ctx = Context.current() Context.set_current(merge_context_correlation(src_ctx, dst_ctx)) - self.assertEqual(Context.current().contents.get("name"), "first") - self.assertTrue(Context.current().contents.get("somebool")) - self.assertEqual(Context.current().contents.get("key"), "value") - self.assertEqual( - Context.current().contents.get("otherkey"), "othervalue" - ) - self.assertEqual( - Context.current().contents.get("anotherkey"), "anothervalue" - ) + self.assertEqual(Context.current().get("name"), "first") + self.assertTrue(Context.current().get("somebool")) + self.assertEqual(Context.current().get("key"), "value") + self.assertEqual(Context.current().get("otherkey"), "othervalue") + self.assertEqual(Context.current().get("anotherkey"), "anothervalue") def test_propagation(self): pass From 6965c33b497abaa93b5b2d1d1238bf84894bf4ee Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 20 Jan 2020 09:37:42 -0800 Subject: [PATCH 60/71] Removing context correlation propagators Signed-off-by: Alex Boten --- .../context_propagation_example.py | 8 +- .../correlationcontext/__init__.py | 6 - .../propagation/__init__.py | 9 -- .../propagation/binaryformat.py | 61 --------- .../propagation/httptextformat.py | 124 ------------------ 5 files changed, 2 insertions(+), 206 deletions(-) delete mode 100644 opentelemetry-api/src/opentelemetry/correlationcontext/propagation/binaryformat.py delete mode 100644 opentelemetry-api/src/opentelemetry/correlationcontext/propagation/httptextformat.py diff --git a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py index bfe04ffb44..f9129b9ad2 100644 --- a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py +++ b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py @@ -40,14 +40,10 @@ def configure_opentelemetry(flask_app: flask.Flask): # Global initialization (b3_extractor, b3_injector) = b3_format.http_propagator() - ( - correlation_extractor, - correlation_injector, - ) = CorrelationContextManager.http_propagator() # propagation.set_http_extractors([b3_extractor, baggage_extractor]) # propagation.set_http_injectors([b3_injector, baggage_injector]) - propagation.set_http_extractors([b3_extractor, correlation_extractor]) - propagation.set_http_injectors([b3_injector, correlation_injector]) + propagation.set_http_extractors([b3_extractor]) + propagation.set_http_injectors([b3_injector]) opentelemetry.ext.http_requests.enable(trace.tracer_source()) flask_app.wsgi_app = OpenTelemetryMiddleware(flask_app.wsgi_app) diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py index fcc2173fbc..a7090a4c60 100644 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py @@ -19,8 +19,6 @@ from opentelemetry.context import Context from opentelemetry.context.propagation import HTTPExtractor, HTTPInjector -from .propagation import CorrelationHTTPExtractor, CorrelationHTTPInjector - PRINTABLE = frozenset( itertools.chain( string.ascii_letters, string.digits, string.punctuation, " " @@ -106,7 +104,3 @@ def remove_correlation(cls) -> Context: @classmethod def clear_correlation(cls) -> Context: pass - - @classmethod - def http_propagator(cls) -> typing.Tuple[HTTPExtractor, HTTPInjector]: - return (CorrelationHTTPExtractor, CorrelationHTTPInjector) # type: ignore diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/__init__.py index 7e75c5143e..f0fde58380 100644 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/__init__.py @@ -12,15 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .binaryformat import BinaryFormat -from .httptextformat import CorrelationHTTPExtractor, CorrelationHTTPInjector - -__all__ = [ - "BinaryFormat", - "CorrelationHTTPExtractor", - "CorrelationHTTPInjector", -] - class ContextKeys: """ TODO """ diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/binaryformat.py b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/binaryformat.py deleted file mode 100644 index 47af045c07..0000000000 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/binaryformat.py +++ /dev/null @@ -1,61 +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.context import Context - - -# TODO: can this be removed until it is needed? -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: Context) -> bytes: - """Creates a byte representation of a CorrelationContext. - - to_bytes should read values from a CorrelationContext and return a data - format to represent it, in bytes. - - Args: - context: the CorrelationContext to serialize - - Returns: - A bytes representation of the CorrelationContext. - - """ - - @staticmethod - @abc.abstractmethod - def from_bytes(byte_representation: bytes,) -> typing.Optional[Context]: - """Return a CorrelationContext that was represented by bytes. - - from_bytes should return back a CorrelationContext that was constructed - from the data serialized in the byte_representation passed. If it is - not possible to read in a proper CorrelationContext, return None. - - Args: - byte_representation: the bytes to deserialize - - Returns: - A bytes representation of the CorrelationContext if it is valid. - Otherwise return None. - - """ diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/httptextformat.py deleted file mode 100644 index 28e30431a9..0000000000 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/httptextformat.py +++ /dev/null @@ -1,124 +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 typing - -from opentelemetry.context import Context -from opentelemetry.context.propagation import ( - ContextT, - Getter, - HTTPExtractor, - HTTPInjector, - Setter, -) - - -class CorrelationHTTPExtractor(HTTPExtractor): - """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 - """ - - @classmethod - def extract( - cls, - carrier: ContextT, - context: typing.Optional[Context] = None, - get_from_carrier: typing.Optional[Getter[ContextT]] = None, - ) -> Context: - """Create a CorrelationContext 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 - CorrelationContext 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 CorrelationContext. This object - must be paired with an appropriate get_from_carrier - which understands how to extract a value from it. - Returns: - A CorrelationContext with configuration found in the carrier. - - """ - - -class CorrelationHTTPInjector(HTTPInjector): - """ TODO """ - - @classmethod - def inject( - cls, - carrier: ContextT, - context: typing.Optional[Context] = None, - set_in_carrier: typing.Optional[Setter[ContextT]] = None, - ) -> None: - """Inject values from a CorrelationContext 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 CorrelationContext 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. - - """ From c129e820901ceb8e6e9c68428499360a0de36ac6 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 20 Jan 2020 09:38:05 -0800 Subject: [PATCH 61/71] Lint changes Signed-off-by: Alex Boten --- opentelemetry-api/src/opentelemetry/context/__init__.py | 1 - opentelemetry-api/src/opentelemetry/context/base_context.py | 1 - .../src/opentelemetry/trace/propagation/context/__init__.py | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index fd161a6ee4..4d3c82c12c 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -139,7 +139,6 @@ async def main(): """ import typing - from contextlib import contextmanager from .base_context import BaseRuntimeContext diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py index b59c23e932..a0b95dfced 100644 --- a/opentelemetry-api/src/opentelemetry/context/base_context.py +++ b/opentelemetry-api/src/opentelemetry/context/base_context.py @@ -98,4 +98,3 @@ def __getitem__(self, name: str) -> "object": def __setitem__(self, name: str, value: "object") -> None: self.__setattr__(name, value) - diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py index e58968b1dd..793b472504 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py @@ -14,7 +14,7 @@ from typing import Optional from opentelemetry.context import Context -from opentelemetry.trace import Span, SpanContext, INVALID_SPAN_CONTEXT +from opentelemetry.trace import INVALID_SPAN_CONTEXT, Span, SpanContext from opentelemetry.trace.propagation import ContextKeys From 0a6c385aa3e4140e329189ed5926547aac9dcc00 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 20 Jan 2020 10:51:18 -0800 Subject: [PATCH 62/71] Adding test and fixing restore behaviour Signed-off-by: Alex Boten --- .../src/opentelemetry/context/__init__.py | 11 ++++++----- .../src/opentelemetry/context/base_context.py | 6 +++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 4d3c82c12c..a411b48c09 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -157,7 +157,7 @@ async def main(): class Context: def __init__(self) -> None: - self.snapshot = _CONTEXT.snapshot() + self.snapshot = {} def get(self, key: str) -> typing.Optional["object"]: return self.snapshot.get(key) @@ -200,7 +200,9 @@ def current(cls) -> "Context": the Context API provides a function which takes no arguments and returns a Context. """ - return Context() + context = Context() + context.snapshot = _CONTEXT.snapshot() + return context @classmethod def set_current(cls, context: "Context") -> None: @@ -213,12 +215,11 @@ def set_current(cls, context: "Context") -> None: @classmethod @contextmanager def use(cls, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: - snapshot = {key: _CONTEXT[key] for key in kwargs} + snapshot = Context.current() for key in kwargs: _CONTEXT[key] = kwargs[key] yield - for key in kwargs: - _CONTEXT[key] = snapshot[key] + Context.set_current(snapshot) @classmethod def suppress_instrumentation(cls) -> "object": diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py index a0b95dfced..2a4c04db4f 100644 --- a/opentelemetry-api/src/opentelemetry/context/base_context.py +++ b/opentelemetry-api/src/opentelemetry/context/base_context.py @@ -68,7 +68,11 @@ def register_slot( def apply(self, snapshot: typing.Dict[str, "object"]) -> None: """Set the current context from a given snapshot dictionary""" - + keys = self._slots.keys() + for name in keys: + slot = self._slots[name] + slot.clear() + self._slots.clear() for name in snapshot: setattr(self, name, snapshot[name]) From 57ad2d2f5e6dd0aa64589016eaa172c33526be66 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 20 Jan 2020 11:15:19 -0800 Subject: [PATCH 63/71] adding restore test Signed-off-by: Alex Boten --- opentelemetry-sdk/tests/context/test_context.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/opentelemetry-sdk/tests/context/test_context.py b/opentelemetry-sdk/tests/context/test_context.py index b803db23ab..b01c6b33c1 100644 --- a/opentelemetry-sdk/tests/context/test_context.py +++ b/opentelemetry-sdk/tests/context/test_context.py @@ -134,3 +134,17 @@ def test_with_threads(self): ], spans_names_list, ) + + def test_restore_context_on_exit(self): + Context.set_current(Context()) + Context.set_value("a", "xxx") + Context.set_value("b", "yyy") + + self.assertEqual({"a": "xxx", "b": "yyy"}, Context.current().snapshot) + with Context.use(a="foo"): + self.assertEqual( + {"a": "foo", "b": "yyy"}, Context.current().snapshot + ) + Context.set_value("a", "i_want_to_mess_it_but_wont_work") + Context.set_value("b", "i_want_to_mess_it") + self.assertEqual({"a": "xxx", "b": "yyy"}, Context.current().snapshot) From cfdfc62f756c819ae0eaea9c72e18c9deaad142a Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 20 Jan 2020 11:32:37 -0800 Subject: [PATCH 64/71] Pass context to set_value Signed-off-by: Alex Boten --- .../src/opentelemetry/context/__init__.py | 12 ++++++++++- .../correlationcontext/__init__.py | 13 +++++++----- .../propagation/context/__init__.py | 8 ++++---- .../trace/propagation/context/__init__.py | 20 ++++++++++++------- .../tests/context/test_context.py | 10 ++++++++++ 5 files changed, 46 insertions(+), 17 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index a411b48c09..57950a14c8 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -179,7 +179,12 @@ def value( return None @classmethod - def set_value(cls, key: str, value: "object") -> "Context": + def set_value( + cls, + key: str, + value: "object", + context: typing.Optional["Context"] = None, + ) -> "Context": """ To record the local state of a cross-cutting concern, the Context API provides a function which takes a context, a @@ -190,6 +195,11 @@ def set_value(cls, key: str, value: "object") -> "Context": key: value: """ + if context: + new_context = Context() + new_context.apply(context) + new_context.snapshot[key] = value + return new_context setattr(_CONTEXT, key, value) return cls.current() diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py index a7090a4c60..4cb42ccd21 100644 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py @@ -15,6 +15,7 @@ import itertools import string import typing +from typing import Optional from opentelemetry.context import Context from opentelemetry.context.propagation import HTTPExtractor, HTTPInjector @@ -88,19 +89,21 @@ class CorrelationContext: class CorrelationContextManager: @classmethod - def set_correlation(cls, key: str, value: "object") -> Context: - return Context.set_value(key, value) + def set_correlation( + cls, key: str, value: "object", context: Optional[Context] = None + ) -> Context: + return Context.set_value(key, value, context=context) @classmethod def correlation( - cls, key: str, context: typing.Optional[Context] = None + cls, key: str, context: Optional[Context] = None ) -> "object": return Context.value(key, context=context) @classmethod - def remove_correlation(cls) -> Context: + def remove_correlation(cls, context: Optional[Context] = None) -> Context: pass @classmethod - def clear_correlation(cls) -> Context: + def clear_correlation(cls, context: Optional[Context] = None) -> Context: pass diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py index b0b2b2a302..fc9a8180d8 100644 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py @@ -19,14 +19,14 @@ def correlation_context_from_context( - ctx: Optional[Context] = None, + context: Optional[Context] = None, ) -> CorrelationContext: - return Context.value(ContextKeys.span_context_key(), context=ctx) # type: ignore + return Context.value(ContextKeys.span_context_key(), context=context) # type: ignore def with_correlation_context( - correlation_context: CorrelationContext, + correlation_context: CorrelationContext, context: Optional[Context] = None, ) -> Context: return Context.set_value( - ContextKeys.span_context_key(), correlation_context + ContextKeys.span_context_key(), correlation_context, context=context ) diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py index 793b472504..388a669e35 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py @@ -18,24 +18,30 @@ from opentelemetry.trace.propagation import ContextKeys -def span_context_from_context(ctx: Optional[Context] = None) -> SpanContext: - span = span_from_context(context=ctx) +def span_context_from_context( + context: Optional[Context] = None, +) -> SpanContext: + span = span_from_context(context=context) if span: return span.get_context() - sc = Context.value(ContextKeys.span_context_key(), context=ctx) # type: ignore + sc = Context.value(ContextKeys.span_context_key(), context=context) # type: ignore if sc: return sc return INVALID_SPAN_CONTEXT -def with_span_context(span_context: SpanContext) -> Context: - return Context.set_value(ContextKeys.span_context_key(), span_context) +def with_span_context( + span_context: SpanContext, context: Optional[Context] = None +) -> Context: + return Context.set_value( + ContextKeys.span_context_key(), span_context, context=context + ) def span_from_context(context: Optional[Context] = None) -> Span: return Context.value(ContextKeys.span_key(), context=context) # type: ignore -def with_span(span: Span) -> Context: - return Context.set_value(ContextKeys.span_key(), span) +def with_span(span: Span, context: Optional[Context] = None) -> Context: + return Context.set_value(ContextKeys.span_key(), span, context=context) diff --git a/opentelemetry-sdk/tests/context/test_context.py b/opentelemetry-sdk/tests/context/test_context.py index b01c6b33c1..b8a613b335 100644 --- a/opentelemetry-sdk/tests/context/test_context.py +++ b/opentelemetry-sdk/tests/context/test_context.py @@ -148,3 +148,13 @@ def test_restore_context_on_exit(self): Context.set_value("a", "i_want_to_mess_it_but_wont_work") Context.set_value("b", "i_want_to_mess_it") self.assertEqual({"a": "xxx", "b": "yyy"}, Context.current().snapshot) + + def test_set_value(self): + context = Context.set_value("a", "yyy") + context2 = Context.set_value("a", "zzz") + context3 = Context.set_value("a", "---", context) + current = Context.current() + self.assertEqual("yyy", Context.value("a", context=context)) + self.assertEqual("zzz", Context.value("a", context=context2)) + self.assertEqual("---", Context.value("a", context=context3)) + self.assertEqual("zzz", Context.value("a", context=current)) From 257627cfc3e941f3f6c40f995bf5ea56409477a7 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 20 Jan 2020 13:51:43 -0800 Subject: [PATCH 65/71] Rename HTTPInjector HTTPExtractor to Injector Extractor Signed-off-by: Alex Boten --- .../tests/test_shim.py | 8 +- .../context/propagation/__init__.py | 19 +-- .../context/propagation/httptextformat.py | 123 ++++-------------- .../correlationcontext/__init__.py | 2 +- .../src/opentelemetry/propagation/__init__.py | 30 ++--- .../sdk/context/propagation/b3_format.py | 10 +- .../propagation/tracecontexthttptextformat.py | 10 +- 7 files changed, 57 insertions(+), 145 deletions(-) diff --git a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py index e319f4511d..8eeb29cbf3 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -21,8 +21,8 @@ from opentelemetry import propagation, trace from opentelemetry.context.propagation import get_as_list, set_in_dict from opentelemetry.context.propagation.httptextformat import ( - HTTPExtractor, - HTTPInjector, + Extractor, + Injector, ) from opentelemetry.ext.opentracing_shim import util from opentelemetry.sdk.trace import TracerSource @@ -547,7 +547,7 @@ def test_extract_binary(self): _SPAN_ID_KEY = "mock-spanid" -class MockHTTPExtractor(HTTPExtractor): +class MockHTTPExtractor(Extractor): """Mock extractor for testing purposes.""" @classmethod @@ -565,7 +565,7 @@ def extract(cls, carrier, context=None, get_from_carrier=get_as_list): ) -class MockHTTPInjector(HTTPInjector): +class MockHTTPInjector(Injector): """Mock injector for testing purposes.""" @classmethod diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py index 88a9d61870..692d866823 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py @@ -18,25 +18,14 @@ from .binaryformat import BinaryFormat from .httptextformat import ( ContextT, - DefaultHTTPExtractor, - DefaultHTTPInjector, + DefaultExtractor, + DefaultInjector, + Extractor, Getter, - HTTPExtractor, - HTTPInjector, + Injector, Setter, ) -__all__ = [ - "BinaryFormat", - "ContextT", - "Getter", - "DefaultHTTPExtractor", - "DefaultHTTPInjector", - "HTTPExtractor", - "HTTPInjector", - "Setter", -] - def get_as_list( dict_object: typing.Dict[str, str], key: str diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py index 4de37933ec..2a8a53a815 100644 --- a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py +++ b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py @@ -23,7 +23,7 @@ Getter = typing.Callable[[ContextT, str], typing.List[str]] -class HTTPExtractor(abc.ABC): +class Extractor(abc.ABC): """API for propagation of span context via headers. TODO: update docs to reflect split into extractor/injector @@ -38,7 +38,7 @@ class HTTPExtractor(abc.ABC): import flask import requests - from opentelemetry.context.propagation import HTTPExtractor + from opentelemetry.context.propagation import Extractor PROPAGATOR = HTTPTextFormat() @@ -80,27 +80,27 @@ def extract( context: typing.Optional[Context] = None, get_from_carrier: typing.Optional[Getter[ContextT]] = 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. + object using get_from_carrier, use values to populate a + 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: The Context to read values from. + 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. Returns: - A SpanContext with configuration found in the carrier. - + A Context with configuration found in the carrier. """ -class HTTPInjector(abc.ABC): +class Injector(abc.ABC): @classmethod @abc.abstractmethod def inject( @@ -109,7 +109,7 @@ def inject( context: typing.Optional[Context] = None, set_in_carrier: typing.Optional[Setter[ContextT]] = None, ) -> None: - """Inject values from a SpanContext 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 @@ -117,63 +117,19 @@ 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: The Context to read values from. + set_in_carrier: A setter function that can set values + on the carrier. """ -class DefaultHTTPExtractor(HTTPExtractor): - """API for propagation of span context via headers. - - TODO: update docs to reflect split into extractor/injector - - 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 HTTPExtractor - - 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()) - +class DefaultExtractor(Extractor): + """The default Extractor that is used when no Extractor implementation is configured. - .. _Propagation API Specification: - https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/api-propagators.md + All operations are no-ops. """ @classmethod @@ -183,30 +139,17 @@ def extract( context: typing.Optional[Context] = None, get_from_carrier: typing.Optional[Getter[ContextT]] = None, ) -> Context: - """Create a SpanContext 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. - - 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 - must be paired with an appropriate get_from_carrier - which understands how to extract a value from it. - Returns: - A SpanContext with configuration found in the carrier. - - """ if context: return context return Context.current() -class DefaultHTTPInjector(HTTPInjector): +class DefaultInjector(Injector): + """The default Injector that is used when no Injector implementation is configured. + + All operations are no-ops. + """ + @classmethod def inject( cls, @@ -214,20 +157,4 @@ def inject( context: typing.Optional[Context] = None, set_in_carrier: typing.Optional[Setter[ContextT]] = None, ) -> None: - """Inject values from a SpanContext 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 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. - - """ return None diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py index 4cb42ccd21..7fd2f1c670 100644 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py @@ -18,7 +18,7 @@ from typing import Optional from opentelemetry.context import Context -from opentelemetry.context.propagation import HTTPExtractor, HTTPInjector +from opentelemetry.context.propagation import Extractor, Injector PRINTABLE = frozenset( itertools.chain( diff --git a/opentelemetry-api/src/opentelemetry/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/propagation/__init__.py index 475b32d146..421620a256 100644 --- a/opentelemetry-api/src/opentelemetry/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagation/__init__.py @@ -19,8 +19,8 @@ from opentelemetry.context import Context from opentelemetry.context.propagation import ( ContextT, - DefaultHTTPExtractor, - DefaultHTTPInjector, + DefaultExtractor, + DefaultInjector, Getter, Setter, ) @@ -29,14 +29,12 @@ def extract( carrier: ContextT, context: typing.Optional[Context] = None, - extractors: typing.Optional[ - typing.List[httptextformat.HTTPExtractor] - ] = None, + extractors: typing.Optional[typing.List[httptextformat.Extractor]] = None, get_from_carrier: typing.Optional[Getter[ContextT]] = None, ) -> typing.Optional[Context]: """Load the parent SpanContext from values in the carrier. - Using the specified HTTPExtractor, the propagator will + Using the specified Extractor, 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. @@ -69,9 +67,7 @@ def extract( def inject( carrier: ContextT, - injectors: typing.Optional[ - typing.List[httptextformat.HTTPInjector] - ] = None, + injectors: typing.Optional[typing.List[httptextformat.Injector]] = None, context: typing.Optional[Context] = None, ) -> None: """Inject values from the current context into the carrier. @@ -98,16 +94,16 @@ def inject( _HTTP_TEXT_INJECTORS = [ - DefaultHTTPInjector -] # typing.List[httptextformat.HTTPInjector] + DefaultInjector +] # typing.List[httptextformat.Injector] _HTTP_TEXT_EXTRACTORS = [ - DefaultHTTPExtractor -] # typing.List[httptextformat.HTTPExtractor] + DefaultExtractor +] # typing.List[httptextformat.Extractor] def set_http_extractors( - extractor_list: typing.List[httptextformat.HTTPExtractor], + extractor_list: typing.List[httptextformat.Extractor], ) -> None: """ To update the global extractor, the Propagation API provides a @@ -118,7 +114,7 @@ def set_http_extractors( def set_http_injectors( - injector_list: typing.List[httptextformat.HTTPInjector], + injector_list: typing.List[httptextformat.Injector], ) -> None: """ To update the global injector, the Propagation API provides a @@ -128,7 +124,7 @@ def set_http_injectors( _HTTP_TEXT_INJECTORS = injector_list # type: ignore -def get_http_extractors() -> typing.List[httptextformat.HTTPExtractor]: +def get_http_extractors() -> typing.List[httptextformat.Extractor]: """ To access the global extractor, the Propagation API provides a function which returns an extractor. @@ -136,7 +132,7 @@ def get_http_extractors() -> typing.List[httptextformat.HTTPExtractor]: return _HTTP_TEXT_EXTRACTORS # type: ignore -def get_http_injectors() -> typing.List[httptextformat.HTTPInjector]: +def get_http_injectors() -> typing.List[httptextformat.Injector]: """ To access the global injector, the Propagation API provides a function which returns an injector. 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 695076fbc6..c9ca79d86e 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py @@ -16,9 +16,9 @@ import opentelemetry.trace as trace from opentelemetry.context.propagation import ( + Extractor, Getter, - HTTPExtractor, - HTTPInjector, + Injector, Setter, get_as_list, set_in_dict, @@ -36,12 +36,12 @@ SAMPLED_KEY = "x-b3-sampled" -def http_propagator() -> typing.Tuple[HTTPExtractor, HTTPInjector]: +def http_propagator() -> typing.Tuple[Extractor, Injector]: """ TODO """ return B3Extractor, B3Injector -class B3Extractor(HTTPExtractor): +class B3Extractor(Extractor): """Propagator for the B3 HTTP header format. See: https://github.com/openzipkin/b3-propagation @@ -123,7 +123,7 @@ def extract( ) -class B3Injector(HTTPInjector): +class B3Injector(Injector): @classmethod def inject( cls, diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py index 3879bc6689..9167683f3b 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py @@ -18,9 +18,9 @@ import opentelemetry.trace as trace from opentelemetry.context import Context from opentelemetry.context.propagation import ( + Extractor, Getter, - HTTPExtractor, - HTTPInjector, + Injector, Setter, get_as_list, set_in_dict, @@ -63,12 +63,12 @@ TRACESTATE_HEADER_NAME = "tracestate" -def http_propagator() -> typing.Tuple[HTTPExtractor, HTTPInjector]: +def http_propagator() -> typing.Tuple[Extractor, Injector]: """ TODO """ return TraceContextHTTPExtractor, TraceContextHTTPInjector -class TraceContextHTTPExtractor(HTTPExtractor): +class TraceContextHTTPExtractor(Extractor): """Extracts using w3c TraceContext's headers. """ @@ -123,7 +123,7 @@ def extract( ) -class TraceContextHTTPInjector(HTTPInjector): +class TraceContextHTTPInjector(Injector): """Injects using w3c TraceContext's headers. """ From b210df8b7fa6ffaac517ae237c76fbd29460b2c1 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 20 Jan 2020 14:08:20 -0800 Subject: [PATCH 66/71] Moving propagation api into propagation/__init__.py Signed-off-by: Alex Boten --- .../tests/test_shim.py | 7 +- .../context/propagation/__init__.py | 44 ----- .../context/propagation/binaryformat.py | 60 ------ .../context/propagation/httptextformat.py | 160 --------------- .../correlationcontext/__init__.py | 2 +- .../src/opentelemetry/propagation/__init__.py | 184 ++++++++++++++++-- .../sdk/context/propagation/b3_format.py | 2 +- .../propagation/tracecontexthttptextformat.py | 2 +- 8 files changed, 173 insertions(+), 288 deletions(-) delete mode 100644 opentelemetry-api/src/opentelemetry/context/propagation/__init__.py delete mode 100644 opentelemetry-api/src/opentelemetry/context/propagation/binaryformat.py delete mode 100644 opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py diff --git a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py index 8eeb29cbf3..2a90221119 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -19,12 +19,13 @@ import opentelemetry.ext.opentracing_shim as opentracingshim from opentelemetry import propagation, trace -from opentelemetry.context.propagation import get_as_list, set_in_dict -from opentelemetry.context.propagation.httptextformat import ( +from opentelemetry.ext.opentracing_shim import util +from opentelemetry.propagation import ( Extractor, Injector, + get_as_list, + set_in_dict, ) -from opentelemetry.ext.opentracing_shim import util from opentelemetry.sdk.trace import TracerSource from opentelemetry.trace.propagation.context import ( span_context_from_context, 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 692d866823..0000000000 --- a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py +++ /dev/null @@ -1,44 +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 typing - -from opentelemetry.context import Context - -from .binaryformat import BinaryFormat -from .httptextformat import ( - ContextT, - DefaultExtractor, - DefaultInjector, - Extractor, - Getter, - Injector, - Setter, -) - - -def get_as_list( - dict_object: typing.Dict[str, str], key: str -) -> typing.List[str]: - value = dict_object.get(key) - if value is None: - return [] - if isinstance(value, list): - return value - return [value] - - -def set_in_dict( - dict_object: typing.Dict[str, str], key: str, value: str -) -> None: - dict_object[key] = value 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 7f1a65882f..0000000000 --- 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/httptextformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py deleted file mode 100644 index 2a8a53a815..0000000000 --- a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py +++ /dev/null @@ -1,160 +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.context import Context - -ContextT = typing.TypeVar("ContextT") - -Setter = typing.Callable[[ContextT, str, str], None] -Getter = typing.Callable[[ContextT, str], typing.List[str]] - - -class Extractor(abc.ABC): - """API for propagation of span context via headers. - - TODO: update docs to reflect split into extractor/injector - - 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 Extractor - - 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 - """ - - @classmethod - @abc.abstractmethod - def extract( - cls, - carrier: ContextT, - context: typing.Optional[Context] = None, - get_from_carrier: typing.Optional[Getter[ContextT]] = None, - ) -> Context: - """Create a Context from values in the carrier. - - The extract function should retrieve values from the carrier - object using get_from_carrier, use values to populate a - Context value and return it. - - Args: - carrier: and object which contains values that are - 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: The Context to read values from. - 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. - Returns: - A Context with configuration found in the carrier. - """ - - -class Injector(abc.ABC): - @classmethod - @abc.abstractmethod - def inject( - cls, - carrier: ContextT, - context: typing.Optional[Context] = None, - set_in_carrier: typing.Optional[Setter[ContextT]] = None, - ) -> None: - """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 - should use the set_in_carrier method to set values on the - carrier. - - Args: - 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: The Context to read values from. - set_in_carrier: A setter function that can set values - on the carrier. - """ - - -class DefaultExtractor(Extractor): - """The default Extractor that is used when no Extractor implementation is configured. - - All operations are no-ops. - """ - - @classmethod - def extract( - cls, - carrier: ContextT, - context: typing.Optional[Context] = None, - get_from_carrier: typing.Optional[Getter[ContextT]] = None, - ) -> Context: - if context: - return context - return Context.current() - - -class DefaultInjector(Injector): - """The default Injector that is used when no Injector implementation is configured. - - All operations are no-ops. - """ - - @classmethod - def inject( - cls, - carrier: ContextT, - context: typing.Optional[Context] = None, - set_in_carrier: typing.Optional[Setter[ContextT]] = None, - ) -> None: - return None diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py index 7fd2f1c670..f6ad23f637 100644 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py @@ -18,7 +18,7 @@ from typing import Optional from opentelemetry.context import Context -from opentelemetry.context.propagation import Extractor, Injector +from opentelemetry.propagation import Extractor, Injector PRINTABLE = frozenset( itertools.chain( diff --git a/opentelemetry-api/src/opentelemetry/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/propagation/__init__.py index 421620a256..a1a8158761 100644 --- a/opentelemetry-api/src/opentelemetry/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagation/__init__.py @@ -12,24 +12,159 @@ # See the License for the specific language governing permissions and # limitations under the License. +import abc import typing -import opentelemetry.context.propagation.httptextformat as httptextformat import opentelemetry.trace as trace from opentelemetry.context import Context -from opentelemetry.context.propagation import ( - ContextT, - DefaultExtractor, - DefaultInjector, - Getter, - Setter, -) + +ContextT = typing.TypeVar("ContextT") + +Setter = typing.Callable[[ContextT, str, str], None] +Getter = typing.Callable[[ContextT, str], typing.List[str]] + + +class Extractor(abc.ABC): + """API for propagation of span context via headers. + + TODO: update docs to reflect split into extractor/injector + + 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 Extractor + + 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 + """ + + @classmethod + @abc.abstractmethod + def extract( + cls, + carrier: ContextT, + context: typing.Optional[Context] = None, + get_from_carrier: typing.Optional[Getter[ContextT]] = None, + ) -> Context: + """Create a Context from values in the carrier. + + The extract function should retrieve values from the carrier + object using get_from_carrier, use values to populate a + Context value and return it. + + Args: + carrier: and object which contains values that are + 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: The Context to read values from. + 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. + Returns: + A Context with configuration found in the carrier. + """ + + +class Injector(abc.ABC): + @classmethod + @abc.abstractmethod + def inject( + cls, + carrier: ContextT, + context: typing.Optional[Context] = None, + set_in_carrier: typing.Optional[Setter[ContextT]] = None, + ) -> None: + """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 + should use the set_in_carrier method to set values on the + carrier. + + Args: + 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: The Context to read values from. + set_in_carrier: A setter function that can set values + on the carrier. + """ + + +class DefaultExtractor(Extractor): + """The default Extractor that is used when no Extractor implementation is configured. + + All operations are no-ops. + """ + + @classmethod + def extract( + cls, + carrier: ContextT, + context: typing.Optional[Context] = None, + get_from_carrier: typing.Optional[Getter[ContextT]] = None, + ) -> Context: + if context: + return context + return Context.current() + + +class DefaultInjector(Injector): + """The default Injector that is used when no Injector implementation is configured. + + All operations are no-ops. + """ + + @classmethod + def inject( + cls, + carrier: ContextT, + context: typing.Optional[Context] = None, + set_in_carrier: typing.Optional[Setter[ContextT]] = None, + ) -> None: + return None def extract( carrier: ContextT, context: typing.Optional[Context] = None, - extractors: typing.Optional[typing.List[httptextformat.Extractor]] = None, + extractors: typing.Optional[typing.List[Extractor]] = None, get_from_carrier: typing.Optional[Getter[ContextT]] = None, ) -> typing.Optional[Context]: """Load the parent SpanContext from values in the carrier. @@ -67,7 +202,7 @@ def extract( def inject( carrier: ContextT, - injectors: typing.Optional[typing.List[httptextformat.Injector]] = None, + injectors: typing.Optional[typing.List[Injector]] = None, context: typing.Optional[Context] = None, ) -> None: """Inject values from the current context into the carrier. @@ -102,9 +237,7 @@ def inject( ] # typing.List[httptextformat.Extractor] -def set_http_extractors( - extractor_list: typing.List[httptextformat.Extractor], -) -> None: +def set_http_extractors(extractor_list: typing.List[Extractor],) -> None: """ To update the global extractor, the Propagation API provides a function which takes an extractor. @@ -113,9 +246,7 @@ def set_http_extractors( _HTTP_TEXT_EXTRACTORS = extractor_list # type: ignore -def set_http_injectors( - injector_list: typing.List[httptextformat.Injector], -) -> None: +def set_http_injectors(injector_list: typing.List[Injector],) -> None: """ To update the global injector, the Propagation API provides a function which takes an injector. @@ -124,7 +255,7 @@ def set_http_injectors( _HTTP_TEXT_INJECTORS = injector_list # type: ignore -def get_http_extractors() -> typing.List[httptextformat.Extractor]: +def get_http_extractors() -> typing.List[Extractor]: """ To access the global extractor, the Propagation API provides a function which returns an extractor. @@ -132,9 +263,26 @@ def get_http_extractors() -> typing.List[httptextformat.Extractor]: return _HTTP_TEXT_EXTRACTORS # type: ignore -def get_http_injectors() -> typing.List[httptextformat.Injector]: +def get_http_injectors() -> typing.List[Injector]: """ To access the global injector, the Propagation API provides a function which returns an injector. """ return _HTTP_TEXT_INJECTORS # type: ignore + + +def get_as_list( + dict_object: typing.Dict[str, str], key: str +) -> typing.List[str]: + value = dict_object.get(key) + if value is None: + return [] + if isinstance(value, list): + return value + return [value] + + +def set_in_dict( + dict_object: typing.Dict[str, str], key: str, value: str +) -> None: + dict_object[key] = value 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 c9ca79d86e..eff8e80abb 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,7 @@ import typing import opentelemetry.trace as trace -from opentelemetry.context.propagation import ( +from opentelemetry.propagation import ( Extractor, Getter, Injector, diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py index 9167683f3b..11ae57621f 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/tracecontexthttptextformat.py @@ -17,7 +17,7 @@ import opentelemetry.trace as trace from opentelemetry.context import Context -from opentelemetry.context.propagation import ( +from opentelemetry.propagation import ( Extractor, Getter, Injector, From 73dbfab2ba27a526e38c3659b102a0230b923af0 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Mon, 20 Jan 2020 14:18:05 -0800 Subject: [PATCH 67/71] Fix example Signed-off-by: Alex Boten --- .../context_propagation_example.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py index f9129b9ad2..90217b2207 100644 --- a/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py +++ b/examples/opentelemetry-example-app/src/opentelemetry_example_app/context_propagation_example.py @@ -37,6 +37,9 @@ def configure_opentelemetry(flask_app: flask.Flask): trace.set_preferred_tracer_source_implementation(lambda T: TracerSource()) + trace.tracer_source().add_span_processor( + BatchExportSpanProcessor(ConsoleSpanExporter()) + ) # Global initialization (b3_extractor, b3_injector) = b3_format.http_propagator() @@ -79,9 +82,6 @@ def fetch_from_service_c() -> str: @app.route("/") def hello(): tracer = trace.tracer_source().get_tracer(__name__) - trace.tracer_source().add_span_processor( - BatchExportSpanProcessor(ConsoleSpanExporter()) - ) # extract a baggage header propagation.extract(request.headers) @@ -90,8 +90,7 @@ def hello(): version = CorrelationContextManager.correlation("version") if version == "2.0": return fetch_from_service_c() - - return fetch_from_service_b() + return fetch_from_service_b() if __name__ == "__main__": From b9be7bd621cc1175d1912b7b6a60822b7e046350 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 21 Jan 2020 09:23:05 -0800 Subject: [PATCH 68/71] Context refactor Changes the Context to be an ABC that is implemented by BaseContext which is then extended by ThreadLocalRuntimeContext and AsyncRuntimeContext. Signed-off-by: Alex Boten --- .../ext/http_requests/__init__.py | 4 +- .../src/opentelemetry/context/__init__.py | 145 +++---------- .../opentelemetry/context/async_context.py | 4 +- .../src/opentelemetry/context/base_context.py | 198 +++++++++++++++--- .../context/thread_local_context.py | 4 +- .../correlationcontext/__init__.py | 6 +- .../propagation/context/__init__.py | 6 +- .../src/opentelemetry/propagation/__init__.py | 8 +- .../trace/propagation/context/__init__.py | 10 +- .../sdk/correlationcontext/__init__.py | 10 +- .../src/opentelemetry/sdk/trace/__init__.py | 4 +- .../sdk/trace/export/__init__.py | 6 +- .../test_tracecontexthttptextformat.py | 4 +- .../tests/context/test_context.py | 138 ++++++------ opentelemetry-sdk/tests/trace/test_trace.py | 4 +- 15 files changed, 296 insertions(+), 255 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 7fa31b3164..5595b9fe8f 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 @@ -23,7 +23,7 @@ from requests.sessions import Session from opentelemetry import propagation -from opentelemetry.context import Context +from opentelemetry.context import current from opentelemetry.ext.http_requests.version import __version__ from opentelemetry.trace import SpanKind @@ -54,7 +54,7 @@ def enable(tracer_source): @functools.wraps(wrapped) def instrumented_request(self, method, url, *args, **kwargs): - if Context.suppress_instrumentation(): + if current().value("suppress_instrumentation"): return wrapped(self, method, url, *args, **kwargs) # See diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index 57950a14c8..ce74718c62 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -141,126 +141,31 @@ async def main(): import typing from contextlib import contextmanager -from .base_context import BaseRuntimeContext - -__all__ = ["Context"] - -try: - from .async_context import AsyncRuntimeContext - - _CONTEXT = AsyncRuntimeContext() # type: BaseRuntimeContext -except ImportError: - from .thread_local_context import ThreadLocalRuntimeContext - - _CONTEXT = ThreadLocalRuntimeContext() - - -class Context: - def __init__(self) -> None: - self.snapshot = {} - - def get(self, key: str) -> typing.Optional["object"]: - return self.snapshot.get(key) - - @classmethod - def value( - cls, key: str, context: typing.Optional["Context"] = None - ) -> typing.Optional["object"]: - """ - To access the local state of an concern, the Context API - provides a function which takes a context and a key as input, - and returns a value. - """ - if context: - return context.get(key) - - if cls.current(): - return cls.current().get(key) - return None - - @classmethod - def set_value( - cls, - key: str, - value: "object", - context: typing.Optional["Context"] = None, - ) -> "Context": - """ - To record the local state of a cross-cutting concern, the - Context API provides a function which takes a context, a - key, and a value as input, and returns an updated context - which contains the new value. - - Args: - key: - value: - """ - if context: - new_context = Context() - new_context.apply(context) - new_context.snapshot[key] = value - return new_context - setattr(_CONTEXT, key, value) - return cls.current() - - @classmethod - def current(cls) -> "Context": - """ - To access the context associated with program execution, - the Context API provides a function which takes no arguments - and returns a Context. - """ - context = Context() - context.snapshot = _CONTEXT.snapshot() - return context - - @classmethod - def set_current(cls, context: "Context") -> None: - """ - To associate a context with program execution, the Context - API provides a function which takes a Context. - """ - _CONTEXT.apply(context.snapshot) - - @classmethod - @contextmanager - def use(cls, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: - snapshot = Context.current() - for key in kwargs: - _CONTEXT[key] = kwargs[key] - yield - Context.set_current(snapshot) - - @classmethod - def suppress_instrumentation(cls) -> "object": - return _CONTEXT.suppress_instrumentation - - @classmethod - def with_current_context( - cls, func: typing.Callable[..., "object"] - ) -> typing.Callable[..., "object"]: - """Capture the current context and apply it to the provided func. - """ - - caller_context = _CONTEXT.snapshot() - - def call_with_current_context( - *args: "object", **kwargs: "object" - ) -> "object": - try: - backup_context = _CONTEXT.snapshot() - _CONTEXT.apply(caller_context) - return func(*args, **kwargs) - finally: - _CONTEXT.apply(backup_context) - - return call_with_current_context - - def apply(self, ctx: "Context") -> None: - for key in ctx.snapshot: - self.snapshot[key] = ctx.snapshot[key] +from .base_context import Context + + +def current() -> Context: + return _CONTEXT.current() + + +def new_context() -> Context: + try: + from .async_context import ( # pylint: disable=import-outside-toplevel + AsyncRuntimeContext, + ) + + context = AsyncRuntimeContext() # type: Context + except ImportError: + from .thread_local_context import ( # pylint: disable=import-outside-toplevel + ThreadLocalRuntimeContext, + ) + + context = ThreadLocalRuntimeContext() # type: Context + return context def merge_context_correlation(source: Context, dest: Context) -> Context: - dest.apply(source) - return dest + return dest.merge(source) + + +_CONTEXT = new_context() diff --git a/opentelemetry-api/src/opentelemetry/context/async_context.py b/opentelemetry-api/src/opentelemetry/context/async_context.py index 267059fb31..39c72c00b9 100644 --- a/opentelemetry-api/src/opentelemetry/context/async_context.py +++ b/opentelemetry-api/src/opentelemetry/context/async_context.py @@ -20,8 +20,8 @@ import typing # pylint: disable=unused-import from . import base_context - class AsyncRuntimeContext(base_context.BaseRuntimeContext): - class Slot(base_context.BaseRuntimeContext.Slot): + class AsyncRuntimeContext(base_context.BaseContext): + class Slot(base_context.BaseContext.Slot): def __init__(self, name: str, default: object): # pylint: disable=super-init-not-called self.name = name diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py index 2a4c04db4f..95a235daaa 100644 --- a/opentelemetry-api/src/opentelemetry/context/base_context.py +++ b/opentelemetry-api/src/opentelemetry/context/base_context.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +import abc import threading import typing +from contextlib import contextmanager def wrap_callable(target: "object") -> typing.Callable[[], object]: @@ -22,7 +24,86 @@ def wrap_callable(target: "object") -> typing.Callable[[], object]: return lambda: target -class BaseRuntimeContext: +class Context(abc.ABC): + def __init__(self) -> None: + self.snapshot = {} + + def get(self, key: str) -> typing.Optional["object"]: + return self.snapshot.get(key) + + @contextmanager + def use(self, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: + snapshot = self.current() + for key in kwargs: + self.set_value(key, kwargs[key]) + yield + self.set_current(snapshot) + + def with_current_context( + self, func: typing.Callable[..., "object"] + ) -> typing.Callable[..., "object"]: + """Capture the current context and apply it to the provided func. + """ + + caller_context = self.current() + + def call_with_current_context( + *args: "object", **kwargs: "object" + ) -> "object": + try: + backup_context = self.current() + self.set_current(caller_context) + return func(*args, **kwargs) + finally: + self.set_current(backup_context) + + return call_with_current_context + + @abc.abstractmethod + def current(self) -> "Context": + """ + To access the context associated with program execution, + the Context API provides a function which takes no arguments + and returns a Context. + """ + + @abc.abstractmethod + def set_current(self, context: "Context") -> None: + """ + To associate a context with program execution, the Context + API provides a function which takes a Context. + """ + + @abc.abstractmethod + def set_value( + self, + key: str, + value: "object", + context: typing.Optional["Context"] = None, + ) -> "Context": + """ + To record the local state of a cross-cutting concern, the + Context API provides a function which takes a context, a + key, and a value as input, and returns an updated context + which contains the new value. + + Args: + key: + value: + """ + + @abc.abstractmethod + def value( + self, key: str, context: typing.Optional["Context"] = None + ) -> typing.Optional["object"]: + """ + To access the local state of an concern, the Context API + provides a function which takes a context and a key as input, + and returns a value. + """ + + +class BaseContext(Context): class Slot: def __init__(self, name: str, default: "object"): raise NotImplementedError @@ -37,7 +118,7 @@ def set(self, value: "object") -> None: raise NotImplementedError _lock = threading.Lock() - _slots = {} # type: typing.Dict[str, 'BaseRuntimeContext.Slot'] + _slots = {} # type: typing.Dict[str, 'BaseContext.Slot'] @classmethod def clear(cls) -> None: @@ -50,7 +131,7 @@ def clear(cls) -> None: @classmethod def register_slot( cls, name: str, default: "object" = None - ) -> "BaseRuntimeContext.Slot": + ) -> "BaseContext.Slot": """Register a context slot with an optional default value. :type name: str @@ -66,39 +147,98 @@ def register_slot( cls._slots[name] = cls.Slot(name, default) return cls._slots[name] - def apply(self, snapshot: typing.Dict[str, "object"]) -> None: - """Set the current context from a given snapshot dictionary""" - keys = self._slots.keys() - for name in keys: - slot = self._slots[name] - slot.clear() - self._slots.clear() - for name in snapshot: - setattr(self, name, snapshot[name]) + def merge(self, context: Context) -> Context: + new_context = self.current() + for key in context.snapshot: + new_context.snapshot[key] = context.snapshot[key] + return new_context - def snapshot(self) -> typing.Dict[str, "object"]: + def _freeze(self) -> typing.Dict[str, "object"]: """Return a dictionary of current slots by reference.""" keys = self._slots.keys() return dict((n, self._slots[n].get()) for n in keys) def __repr__(self) -> str: - return "{}({})".format(type(self).__name__, self.snapshot()) - - def __getattr__(self, name: str) -> "object": - if name not in self._slots: - self.register_slot(name, None) - slot = self._slots[name] - return slot.get() - - def __setattr__(self, name: str, value: "object") -> None: - if name not in self._slots: - self.register_slot(name, None) - slot = self._slots[name] + return "{}({})".format(type(self).__name__, self._freeze()) + + def _set(self, key: str, value: "object") -> None: + """ + Set creates a Slot for the value if none exists, and sets the + value for that slot. + + Args: + key: Name of the value + value: Contents of the value + """ + if key not in self._slots: + self.register_slot(key, None) + slot = self._slots[key] slot.set(value) - def __getitem__(self, name: str) -> "object": - return self.__getattr__(name) + def value( + self, key: str, context: typing.Optional["Context"] = None + ) -> typing.Optional["object"]: + """ + To access the local state of an concern, the Context API + provides a function which takes a context and a key as input, + and returns a value. + + Args: + key: Name of the value + context: + """ + if context: + return context.get(key) + + return self.current().get(key) - def __setitem__(self, name: str, value: "object") -> None: - self.__setattr__(name, value) + def set_value( + self, + key: str, + value: "object", + context: typing.Optional["Context"] = None, + ) -> "Context": + """ + To record the local state of a cross-cutting concern, the + Context API provides a function which takes a context, a + key, and a value as input, and returns an updated context + which contains the new value. + + Args: + key: Name of the value + value: Contents of the value + context: + """ + if context: + new_context = self.__class__() + for name in context.snapshot: + new_context.snapshot[name] = context.snapshot[name] + new_context.snapshot[key] = value + return new_context + self._set(key, value) + return self.current() + + def current(self) -> "Context": + """ + To access the context associated with program execution, + the Context API provides a function which takes no arguments + and returns a Context. + """ + ctx = self.__class__() + ctx.snapshot = self._freeze() + return ctx + + def set_current(self, context: "Context") -> None: + """ + To associate a context with program execution, the Context + API provides a function which takes a Context. + """ + keys = self._slots.keys() + for name in keys: + slot = self._slots[name] + slot.clear() + self._slots.clear() + if context.snapshot: + for name in context.snapshot: + self._set(name, context.snapshot[name]) diff --git a/opentelemetry-api/src/opentelemetry/context/thread_local_context.py b/opentelemetry-api/src/opentelemetry/context/thread_local_context.py index b60914f846..83fc7fcd55 100644 --- a/opentelemetry-api/src/opentelemetry/context/thread_local_context.py +++ b/opentelemetry-api/src/opentelemetry/context/thread_local_context.py @@ -18,8 +18,8 @@ from . import base_context -class ThreadLocalRuntimeContext(base_context.BaseRuntimeContext): - class Slot(base_context.BaseRuntimeContext.Slot): +class ThreadLocalRuntimeContext(base_context.BaseContext): + class Slot(base_context.BaseContext.Slot): _thread_local = threading.local() def __init__(self, name: str, default: "object"): diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py index f6ad23f637..43eee9cb0b 100644 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py @@ -17,7 +17,7 @@ import typing from typing import Optional -from opentelemetry.context import Context +from opentelemetry.context import Context, current from opentelemetry.propagation import Extractor, Injector PRINTABLE = frozenset( @@ -92,13 +92,13 @@ class CorrelationContextManager: def set_correlation( cls, key: str, value: "object", context: Optional[Context] = None ) -> Context: - return Context.set_value(key, value, context=context) + return current().set_value(key, value, context=context) @classmethod def correlation( cls, key: str, context: Optional[Context] = None ) -> "object": - return Context.value(key, context=context) + return current().value(key, context=context) @classmethod def remove_correlation(cls, context: Optional[Context] = None) -> Context: diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py index fc9a8180d8..2e447b17cb 100644 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py @@ -13,7 +13,7 @@ # limitations under the License. from typing import Optional -from opentelemetry.context import Context +from opentelemetry.context import Context, current from opentelemetry.correlationcontext import CorrelationContext from opentelemetry.correlationcontext.propagation import ContextKeys @@ -21,12 +21,12 @@ def correlation_context_from_context( context: Optional[Context] = None, ) -> CorrelationContext: - return Context.value(ContextKeys.span_context_key(), context=context) # type: ignore + return current().value(ContextKeys.span_context_key(), context=context) # type: ignore def with_correlation_context( correlation_context: CorrelationContext, context: Optional[Context] = None, ) -> Context: - return Context.set_value( + return current().set_value( ContextKeys.span_context_key(), correlation_context, context=context ) diff --git a/opentelemetry-api/src/opentelemetry/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/propagation/__init__.py index a1a8158761..37a1302f1c 100644 --- a/opentelemetry-api/src/opentelemetry/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagation/__init__.py @@ -16,7 +16,7 @@ import typing import opentelemetry.trace as trace -from opentelemetry.context import Context +from opentelemetry.context import Context, current ContextT = typing.TypeVar("ContextT") @@ -142,7 +142,7 @@ def extract( ) -> Context: if context: return context - return Context.current() + return current() class DefaultInjector(Injector): @@ -183,7 +183,7 @@ def extract( which understands how to extract a value from it. """ if context is None: - context = Context.current() + context = current() if extractors is None: extractors = get_http_extractors() @@ -220,7 +220,7 @@ def inject( should know how to set header values on the carrier. """ if context is None: - context = Context.current() + context = current() if injectors is None: injectors = get_http_injectors() diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py index 388a669e35..675d5a015d 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py @@ -13,7 +13,7 @@ # limitations under the License. from typing import Optional -from opentelemetry.context import Context +from opentelemetry.context import Context, current from opentelemetry.trace import INVALID_SPAN_CONTEXT, Span, SpanContext from opentelemetry.trace.propagation import ContextKeys @@ -24,7 +24,7 @@ def span_context_from_context( span = span_from_context(context=context) if span: return span.get_context() - sc = Context.value(ContextKeys.span_context_key(), context=context) # type: ignore + sc = current().value(ContextKeys.span_context_key(), context=context) # type: ignore if sc: return sc @@ -34,14 +34,14 @@ def span_context_from_context( def with_span_context( span_context: SpanContext, context: Optional[Context] = None ) -> Context: - return Context.set_value( + return current().set_value( ContextKeys.span_context_key(), span_context, context=context ) def span_from_context(context: Optional[Context] = None) -> Span: - return Context.value(ContextKeys.span_key(), context=context) # type: ignore + return current().value(ContextKeys.span_key(), context=context) # type: ignore def with_span(span: Span, context: Optional[Context] = None) -> Context: - return Context.set_value(ContextKeys.span_key(), span, context=context) + return current().set_value(ContextKeys.span_key(), span, context=context) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py index 7415bb3245..2339991b19 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py @@ -16,7 +16,7 @@ from contextlib import contextmanager from opentelemetry import correlationcontext as dctx_api -from opentelemetry.context import Context +from opentelemetry.context import Context, current class CorrelationContextManager(dctx_api.CorrelationContextManager): @@ -38,7 +38,7 @@ def current_context(self,) -> typing.Optional[dctx_api.CorrelationContext]: Returns: A CorrelationContext instance representing the current context. """ - return Context.value(self.slot_name) + return current().value(self.slot_name) @contextmanager def use_context( @@ -54,9 +54,9 @@ def use_context( Args: context: A CorrelationContext instance to make current. """ - snapshot = Context.value(self.slot_name) - Context.set_value(self.slot_name, context) + snapshot = current().value(self.slot_name) + current().set_value(self.slot_name, context) try: yield context finally: - Context.set_value(self.slot_name, snapshot) + current().set_value(self.slot_name, snapshot) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 4ef1ac9e9f..0fc0f7d934 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -22,7 +22,7 @@ from typing import Iterator, Optional, Sequence, Tuple, Type from opentelemetry import trace as trace_api -from opentelemetry.context import Context +from opentelemetry.context import Context, current from opentelemetry.sdk import util from opentelemetry.sdk.util import BoundedDict, BoundedList from opentelemetry.trace import SpanContext, sampling @@ -542,7 +542,7 @@ def get_tracer( def get_current_span(self, context: Optional[Context] = None) -> Span: """See `opentelemetry.trace.Tracer.get_current_span`.""" - return Context.value(self._current_span_name, context=context) + return current().value(self._current_span_name, context=context) def add_span_processor(self, span_processor: SpanProcessor) -> None: """Registers a new :class:`SpanProcessor` for this `TracerSource`. diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py index 36459c5b73..3ca1bb9ebc 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py @@ -18,7 +18,7 @@ import typing from enum import Enum -from opentelemetry.context import Context +from opentelemetry.context import current from opentelemetry.util import time_ns from .. import Span, SpanProcessor @@ -73,7 +73,7 @@ def on_start(self, span: Span) -> None: pass def on_end(self, span: Span) -> None: - with Context.use(suppress_instrumentation=True): + with current().use(suppress_instrumentation=True): try: self.span_exporter.export((span,)) # pylint: disable=broad-except @@ -182,7 +182,7 @@ def export(self) -> None: while idx < self.max_export_batch_size and self.queue: self.spans_list[idx] = self.queue.pop() idx += 1 - with Context.use(suppress_instrumentation=True): + with current().use(suppress_instrumentation=True): try: # Ignore type b/c the Optional[None]+slicing is too "clever" # for mypy diff --git a/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py index 37321214d1..47853563fc 100644 --- a/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-sdk/tests/context/propagation/test_tracecontexthttptextformat.py @@ -16,7 +16,7 @@ import unittest from opentelemetry import trace -from opentelemetry.context import Context +from opentelemetry.context import current from opentelemetry.sdk.context.propagation.tracecontexthttptextformat import ( TraceContextHTTPExtractor, TraceContextHTTPInjector, @@ -35,7 +35,7 @@ class TestTraceContextFormat(unittest.TestCase): SPAN_ID = int("1234567890123456", 16) # type:int def setUp(self): - self.ctx = Context.current() + self.ctx = current() def test_no_traceparent_header(self): """When tracecontext headers are not present, a new SpanContext diff --git a/opentelemetry-sdk/tests/context/test_context.py b/opentelemetry-sdk/tests/context/test_context.py index b8a613b335..ae0f3b2dd6 100644 --- a/opentelemetry-sdk/tests/context/test_context.py +++ b/opentelemetry-sdk/tests/context/test_context.py @@ -17,7 +17,11 @@ import unittest from multiprocessing.dummy import Pool as ThreadPool -from opentelemetry.context import Context, merge_context_correlation +from opentelemetry.context import ( + current, + merge_context_correlation, + new_context, +) from opentelemetry.sdk import trace from opentelemetry.sdk.trace import export from opentelemetry.sdk.trace.export.in_memory_span_exporter import ( @@ -26,7 +30,7 @@ def do_work(): - Context.set_value("say-something", "bar") + current().set_value("say-something", "bar") class TestContext(unittest.TestCase): @@ -50,38 +54,38 @@ def setUp(self): self.tracer_source.add_span_processor(span_processor) def test_context(self): - self.assertIsNone(Context.value("say-something")) - empty_context = Context.current() - Context.set_value("say-something", "foo") - self.assertEqual(Context.value("say-something"), "foo") - second_context = Context.current() + self.assertIsNone(current().value("say-something")) + empty_context = current() + current().set_value("say-something", "foo") + self.assertEqual(current().value("say-something"), "foo") + second_context = current() do_work() - self.assertEqual(Context.value("say-something"), "bar") - third_context = Context.current() + self.assertEqual(current().value("say-something"), "bar") + third_context = current() self.assertIsNone(empty_context.get("say-something")) self.assertEqual(second_context.get("say-something"), "foo") self.assertEqual(third_context.get("say-something"), "bar") def test_merge(self): - Context.set_value("name", "first") - Context.set_value("somebool", True) - Context.set_value("key", "value") - Context.set_value("otherkey", "othervalue") - src_ctx = Context.current() - - Context.set_value("name", "second") - Context.set_value("somebool", False) - Context.set_value("anotherkey", "anothervalue") - dst_ctx = Context.current() - - Context.set_current(merge_context_correlation(src_ctx, dst_ctx)) - self.assertEqual(Context.current().get("name"), "first") - self.assertTrue(Context.current().get("somebool")) - self.assertEqual(Context.current().get("key"), "value") - self.assertEqual(Context.current().get("otherkey"), "othervalue") - self.assertEqual(Context.current().get("anotherkey"), "anothervalue") + current().set_value("name", "first") + current().set_value("somebool", True) + current().set_value("key", "value") + current().set_value("otherkey", "othervalue") + src_ctx = current() + + current().set_value("name", "second") + current().set_value("somebool", False) + current().set_value("anotherkey", "anothervalue") + dst_ctx = current() + + current().set_current(merge_context_correlation(src_ctx, dst_ctx)) + self.assertEqual(current().get("name"), "first") + self.assertTrue(current().get("somebool")) + self.assertEqual(current().get("key"), "value") + self.assertEqual(current().get("otherkey"), "othervalue") + self.assertEqual(current().get("anotherkey"), "anothervalue") def test_propagation(self): pass @@ -100,61 +104,53 @@ def test_with_futures(self): ) span_list = self.memory_exporter.get_finished_spans() - spans_names_list = [span.name for span in span_list] - self.assertListEqual( - [ - "test_span1", - "test_span2", - "test_span3", - "test_span4", - "test_span5", - "futures_test", - ], - spans_names_list, - ) + expected = [ + "test_span1", + "test_span2", + "test_span3", + "test_span4", + "test_span5", + "futures_test", + ] + self.assertEqual(len(span_list), len(expected)) def test_with_threads(self): with self.tracer.start_as_current_span("threads_test"): pool = ThreadPool(5) # create a thread pool pool.map( - Context.with_current_context(self.do_some_work), self.spans, + current().with_current_context(self.do_some_work), self.spans, ) pool.close() pool.join() span_list = self.memory_exporter.get_finished_spans() - spans_names_list = [span.name for span in span_list] - self.assertListEqual( - [ - "test_span1", - "test_span2", - "test_span3", - "test_span4", - "test_span5", - "threads_test", - ], - spans_names_list, - ) + expected = [ + "test_span1", + "test_span2", + "test_span3", + "test_span4", + "test_span5", + "threads_test", + ] + self.assertEqual(len(span_list), len(expected)) def test_restore_context_on_exit(self): - Context.set_current(Context()) - Context.set_value("a", "xxx") - Context.set_value("b", "yyy") - - self.assertEqual({"a": "xxx", "b": "yyy"}, Context.current().snapshot) - with Context.use(a="foo"): - self.assertEqual( - {"a": "foo", "b": "yyy"}, Context.current().snapshot - ) - Context.set_value("a", "i_want_to_mess_it_but_wont_work") - Context.set_value("b", "i_want_to_mess_it") - self.assertEqual({"a": "xxx", "b": "yyy"}, Context.current().snapshot) + current().set_current(new_context()) + current().set_value("a", "xxx") + current().set_value("b", "yyy") + + self.assertEqual({"a": "xxx", "b": "yyy"}, current().snapshot) + with current().use(a="foo"): + self.assertEqual({"a": "foo", "b": "yyy"}, current().snapshot) + current().set_value("a", "i_want_to_mess_it_but_wont_work") + current().set_value("b", "i_want_to_mess_it") + self.assertEqual({"a": "xxx", "b": "yyy"}, current().snapshot) def test_set_value(self): - context = Context.set_value("a", "yyy") - context2 = Context.set_value("a", "zzz") - context3 = Context.set_value("a", "---", context) - current = Context.current() - self.assertEqual("yyy", Context.value("a", context=context)) - self.assertEqual("zzz", Context.value("a", context=context2)) - self.assertEqual("---", Context.value("a", context=context3)) - self.assertEqual("zzz", Context.value("a", context=current)) + context = current().set_value("a", "yyy") + context2 = current().set_value("a", "zzz") + context3 = current().set_value("a", "---", context) + current_context = current() + self.assertEqual("yyy", current().value("a", context=context)) + self.assertEqual("zzz", current().value("a", context=context2)) + self.assertEqual("---", current().value("a", context=context3)) + self.assertEqual("zzz", current().value("a", context=current_context)) diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index e8128d94fa..6402c3724b 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -18,7 +18,7 @@ from unittest import mock from opentelemetry import trace as trace_api -from opentelemetry.context import Context +from opentelemetry.context import current, new_context from opentelemetry.sdk import trace from opentelemetry.trace import sampling from opentelemetry.trace.status import StatusCanonicalCode @@ -132,7 +132,7 @@ def test_sampler_no_sampling(self): class TestSpanCreation(unittest.TestCase): def setUp(self): - Context.set_current(Context()) + current().set_current(new_context()) def test_start_span_invalid_spancontext(self): """If an invalid span context is passed as the parent, the created From d70a47cabb8903fdcdb76da00ed56558dd2447e8 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 21 Jan 2020 10:58:11 -0800 Subject: [PATCH 69/71] Adding set_in_carrier parameter, updating docs Signed-off-by: Alex Boten --- .../src/opentelemetry/propagation/__init__.py | 148 +++++++++--------- 1 file changed, 72 insertions(+), 76 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/propagation/__init__.py index 37a1302f1c..fb8b5e39c3 100644 --- a/opentelemetry-api/src/opentelemetry/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagation/__init__.py @@ -12,6 +12,48 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +The OpenTelemetry propagation module provides an interface that enables +propagation of Context details across any concerns which implement the +Extractor and Injector interfaces. + +Example:: + + import flask + import requests + from opentelemetry.propagation import DefaultExtractor, DefaultInjector + + extractor = DefaultExtractor() + injector = DefaultInjector() + + + 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 = extractor.extract( + get_header_from_flask_request, + flask.request + ) + request_to_downstream = requests.Request( + "GET", "http://httpbin.org/get" + ) + injector.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 +""" import abc import typing @@ -24,62 +66,31 @@ Getter = typing.Callable[[ContextT, str], typing.List[str]] -class Extractor(abc.ABC): - """API for propagation of span context via headers. - - TODO: update docs to reflect split into extractor/injector - - 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 Extractor - - 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 get_as_list( + dict_object: typing.Dict[str, str], key: str +) -> typing.List[str]: + value = dict_object.get(key) + if value is None: + return [] + if isinstance(value, list): + return value + return [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()) +def set_in_dict( + dict_object: typing.Dict[str, str], key: str, value: str +) -> None: + dict_object[key] = value - .. _Propagation API Specification: - https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/api-propagators.md - """ +class Extractor(abc.ABC): @classmethod @abc.abstractmethod def extract( cls, carrier: ContextT, context: typing.Optional[Context] = None, - get_from_carrier: typing.Optional[Getter[ContextT]] = None, + get_from_carrier: typing.Optional[Getter[ContextT]] = get_as_list, ) -> Context: """Create a Context from values in the carrier. @@ -88,12 +99,12 @@ def extract( Context value and return it. Args: - carrier: and object which contains values that are + carrier: And object which contains values that are 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: The Context to read values from. - get_from_carrier: a function that can retrieve zero + context: The Context to set values into. + 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. Returns: @@ -108,7 +119,7 @@ def inject( cls, carrier: ContextT, context: typing.Optional[Context] = None, - set_in_carrier: typing.Optional[Setter[ContextT]] = None, + set_in_carrier: typing.Optional[Setter[ContextT]] = set_in_dict, ) -> None: """Inject values from a Context into a carrier. @@ -138,7 +149,7 @@ def extract( cls, carrier: ContextT, context: typing.Optional[Context] = None, - get_from_carrier: typing.Optional[Getter[ContextT]] = None, + get_from_carrier: typing.Optional[Getter[ContextT]] = get_as_list, ) -> Context: if context: return context @@ -156,7 +167,7 @@ def inject( cls, carrier: ContextT, context: typing.Optional[Context] = None, - set_in_carrier: typing.Optional[Setter[ContextT]] = None, + set_in_carrier: typing.Optional[Setter[ContextT]] = set_in_dict, ) -> None: return None @@ -165,19 +176,18 @@ def extract( carrier: ContextT, context: typing.Optional[Context] = None, extractors: typing.Optional[typing.List[Extractor]] = None, - get_from_carrier: typing.Optional[Getter[ContextT]] = None, + get_from_carrier: typing.Optional[Getter[ContextT]] = get_as_list, ) -> typing.Optional[Context]: - """Load the parent SpanContext from values in the carrier. + """Load the Context from values in the carrier. Using the specified Extractor, 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. + extract a Context from the carrier. Args: - get_from_carrier: a function that can retrieve zero + 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 + carrier: An object which contains values that are used to construct a SpanContext. This object must be paired with an appropriate get_from_carrier which understands how to extract a value from it. @@ -204,6 +214,7 @@ def inject( carrier: ContextT, injectors: typing.Optional[typing.List[Injector]] = None, context: typing.Optional[Context] = None, + set_in_carrier: typing.Optional[Setter[ContextT]] = set_in_dict, ) -> None: """Inject values from the current context into the carrier. @@ -225,7 +236,9 @@ def inject( injectors = get_http_injectors() for injector in injectors: - injector.inject(context=context, carrier=carrier) + injector.inject( + context=context, carrier=carrier, set_in_carrier=set_in_carrier + ) _HTTP_TEXT_INJECTORS = [ @@ -269,20 +282,3 @@ def get_http_injectors() -> typing.List[Injector]: function which returns an injector. """ return _HTTP_TEXT_INJECTORS # type: ignore - - -def get_as_list( - dict_object: typing.Dict[str, str], key: str -) -> typing.List[str]: - value = dict_object.get(key) - if value is None: - return [] - if isinstance(value, list): - return value - return [value] - - -def set_in_dict( - dict_object: typing.Dict[str, str], key: str, value: str -) -> None: - dict_object[key] = value From 1e3ee56394c10198e2ae08e31b3d470974cf2671 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 21 Jan 2020 14:57:19 -0800 Subject: [PATCH 70/71] rename dctx_api Signed-off-by: Alex Boten --- .../opentelemetry/sdk/correlationcontext/__init__.py | 10 +++++----- .../correlationcontext/test_distributed_context.py | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py index 2339991b19..373aa8e5c8 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py @@ -15,11 +15,11 @@ import typing from contextlib import contextmanager -from opentelemetry import correlationcontext as dctx_api +from opentelemetry import correlationcontext as cctx_api from opentelemetry.context import Context, current -class CorrelationContextManager(dctx_api.CorrelationContextManager): +class CorrelationContextManager(cctx_api.CorrelationContextManager): """See `opentelemetry.correlationcontext.CorrelationContextManager` Args: @@ -32,7 +32,7 @@ def __init__(self, name: str = "") -> None: else: self.slot_name = "CorrelationContext" - def current_context(self,) -> typing.Optional[dctx_api.CorrelationContext]: + def current_context(self,) -> typing.Optional[cctx_api.CorrelationContext]: """Gets the current CorrelationContext. Returns: @@ -42,8 +42,8 @@ def current_context(self,) -> typing.Optional[dctx_api.CorrelationContext]: @contextmanager def use_context( - self, context: dctx_api.CorrelationContext - ) -> typing.Iterator[dctx_api.CorrelationContext]: + self, context: cctx_api.CorrelationContext + ) -> typing.Iterator[cctx_api.CorrelationContext]: """Context manager for controlling a CorrelationContext lifetime. Set the context as the active CorrelationContext. diff --git a/opentelemetry-sdk/tests/correlationcontext/test_distributed_context.py b/opentelemetry-sdk/tests/correlationcontext/test_distributed_context.py index adfca5b25b..2f151fea8e 100644 --- a/opentelemetry-sdk/tests/correlationcontext/test_distributed_context.py +++ b/opentelemetry-sdk/tests/correlationcontext/test_distributed_context.py @@ -14,7 +14,7 @@ import unittest -from opentelemetry import correlationcontext as dctx_api +from opentelemetry import correlationcontext as cctx_api from opentelemetry.sdk import correlationcontext @@ -27,13 +27,13 @@ def test_use_context(self): self.assertIsNone(self.manager.current_context()) # Start initial context - dctx = dctx_api.CorrelationContext() + dctx = cctx_api.CorrelationContext() with self.manager.use_context(dctx) as current: self.assertIs(current, dctx) self.assertIs(self.manager.current_context(), dctx) # Context is overridden - nested_dctx = dctx_api.CorrelationContext() + nested_dctx = cctx_api.CorrelationContext() with self.manager.use_context(nested_dctx) as current: self.assertIs(current, nested_dctx) self.assertIs(self.manager.current_context(), nested_dctx) From 47f521fa9cbea08fbce8d60718df5cdf7645645e Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 22 Jan 2020 22:37:14 -0800 Subject: [PATCH 71/71] Gutting Context class Simplifying the Context API implementation significantly: - removing multiple layers of Context classes, replacing it with `context` module level functions - moved Slot classi out of BaseContext - broke off threads/futures test into separate tests files, which allowed me to move test_context back into the api package where it belongs Signed-off-by: Alex Boten --- .../src/opentelemetry/context/__init__.py | 139 +++++++++-- .../opentelemetry/context/async_context.py | 73 ++++-- .../src/opentelemetry/context/base_context.py | 220 +----------------- .../context/thread_local_context.py | 65 ++++-- .../correlationcontext/__init__.py | 23 +- .../propagation/context/__init__.py | 6 +- .../trace/propagation/context/__init__.py | 22 +- .../tests/context/test_context.py | 87 +++++++ .../sdk/context/propagation/b3_format.py | 2 +- .../sdk/correlationcontext/__init__.py | 8 +- .../src/opentelemetry/sdk/trace/__init__.py | 14 +- .../sdk/trace/export/__init__.py | 6 +- .../tests/context/test_context.py | 156 ------------- .../tests/context/test_futures.py | 84 +++++++ .../tests/context/test_threads.py | 75 ++++++ opentelemetry-sdk/tests/trace/test_trace.py | 4 +- 16 files changed, 521 insertions(+), 463 deletions(-) create mode 100644 opentelemetry-api/tests/context/test_context.py delete mode 100644 opentelemetry-sdk/tests/context/test_context.py create mode 100644 opentelemetry-sdk/tests/context/test_futures.py create mode 100644 opentelemetry-sdk/tests/context/test_threads.py diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py index ce74718c62..f3a843f47b 100644 --- a/opentelemetry-api/src/opentelemetry/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/context/__init__.py @@ -138,34 +138,141 @@ async def main(): asyncio.run(main()) """ +import threading import typing from contextlib import contextmanager -from .base_context import Context +from .base_context import Context, Slot + +try: + from .async_context import ( + AsyncRuntimeContext, + ContextVarSlot, + ) + + _context_class = AsyncRuntimeContext # pylint: disable=invalid-name + _slot_class = ContextVarSlot # pylint: disable=invalid-name +except ImportError: + from .thread_local_context import ( + ThreadLocalRuntimeContext, + ThreadLocalSlot, + ) + + _context_class = ThreadLocalRuntimeContext # pylint: disable=invalid-name + _slot_class = ThreadLocalSlot # pylint: disable=invalid-name + +_slots = {} # type: typing.Dict[str, 'Slot'] +_lock = threading.Lock() + + +def _register_slot(name: str, default: "object" = None) -> Slot: + """Register a context slot with an optional default value. + + :type name: str + :param name: The name of the context slot. + + :type default: object + :param name: The default value of the slot, can be a value or lambda. + + :returns: The registered slot. + """ + with _lock: + if name not in _slots: + _slots[name] = _slot_class(name, default) # type: Slot + return _slots[name] + + +def set_value( + name: str, val: "object", context: typing.Optional[Context] = None, +) -> Context: + """ + To record the local state of a cross-cutting concern, the + Context API provides a function which takes a context, a + key, and a value as input, and returns an updated context + which contains the new value. + + Args: + name: name of the entry to set + value: value of the entry to set + context: a context to copy, if None, the current context is used + """ + # Function inside the module that performs the action on the current context + # or in the passsed one based on the context object + if context: + ret = Context() + ret.snapshot = dict((n, v) for n, v in context.snapshot.items()) + ret.snapshot[name] = val + return ret + + # update value on current context: + slot = _register_slot(name) + slot.set(val) + return current() + + +def value(name: str, context: Context = None) -> typing.Optional["object"]: + """ + To access the local state of an concern, the Context API + provides a function which takes a context and a key as input, + and returns a value. + + Args: + name: name of the entry to retrieve + context: a context from which to retrieve the value, if None, the current context is used + """ + if context: + return context.value(name) + + # get context from current context + if name in _slots: + return _slots[name].get() + return None def current() -> Context: - return _CONTEXT.current() + """ + To access the context associated with program execution, + the Context API provides a function which takes no arguments + and returns a Context. + """ + ret = Context() + for key, slot in _slots.items(): + ret.snapshot[key] = slot.get() + return ret -def new_context() -> Context: - try: - from .async_context import ( # pylint: disable=import-outside-toplevel - AsyncRuntimeContext, - ) - context = AsyncRuntimeContext() # type: Context - except ImportError: - from .thread_local_context import ( # pylint: disable=import-outside-toplevel - ThreadLocalRuntimeContext, - ) +def set_current(context: Context) -> None: + """ + To associate a context with program execution, the Context + API provides a function which takes a Context. + """ + _slots.clear() # remove current data + + for key, val in context.snapshot.items(): + slot = _register_slot(key) + slot.set(val) - context = ThreadLocalRuntimeContext() # type: Context - return context + +@contextmanager +def use(**kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: + snapshot = current() + for key in kwargs: + set_value(key, kwargs[key]) + yield + set_current(snapshot) + + +def new_context() -> Context: + return _context_class() def merge_context_correlation(source: Context, dest: Context) -> Context: - return dest.merge(source) + ret = Context() + for key in dest.snapshot: + ret.snapshot[key] = dest.snapshot[key] -_CONTEXT = new_context() + for key in source.snapshot: + ret.snapshot[key] = source.snapshot[key] + return ret diff --git a/opentelemetry-api/src/opentelemetry/context/async_context.py b/opentelemetry-api/src/opentelemetry/context/async_context.py index 39c72c00b9..fec66dce62 100644 --- a/opentelemetry-api/src/opentelemetry/context/async_context.py +++ b/opentelemetry-api/src/opentelemetry/context/async_context.py @@ -17,29 +17,54 @@ except ImportError: pass else: - import typing # pylint: disable=unused-import + # import contextvars + import typing from . import base_context - class AsyncRuntimeContext(base_context.BaseContext): - class Slot(base_context.BaseContext.Slot): - def __init__(self, name: str, default: object): - # pylint: disable=super-init-not-called - self.name = name - self.contextvar = ContextVar(name) # type: ContextVar[object] - self.default = base_context.wrap_callable( - default - ) # type: typing.Callable[..., object] - - def clear(self) -> None: - self.contextvar.set(self.default()) - - def get(self) -> object: - try: - return self.contextvar.get() - except LookupError: - value = self.default() - self.set(value) - return value - - def set(self, value: object) -> None: - self.contextvar.set(value) + class ContextVarSlot(base_context.Slot): + def __init__(self, name: str, default: object): + # pylint: disable=super-init-not-called + self.name = name + self.contextvar = ContextVar(name) # type: ContextVar[object] + self.default = base_context.wrap_callable( + default + ) # type: typing.Callable[..., object] + + def clear(self) -> None: + self.contextvar.set(self.default()) + + def get(self) -> object: + try: + return self.contextvar.get() + except LookupError: + value = self.default() + self.set(value) + return value + + def set(self, value: object) -> None: + self.contextvar.set(value) + + class AsyncRuntimeContext(base_context.Context): + def with_current_context( + self, func: typing.Callable[..., "object"] + ) -> typing.Callable[..., "object"]: + """Capture the current context and apply it to the provided func. + """ + + # TODO: implement this + # ctx = contextvars.copy_context() + # ctx.run() + # caller_context = self.current() + + # def call_with_current_context( + # *args: "object", **kwargs: "object" + # ) -> "object": + # try: + # backup_context = self.current() + # self.set_current(caller_context) + # # return ctx.run(func(*args, **kwargs)) + # return func(*args, **kwargs) + # finally: + # self.set_current(backup_context) + + # return call_with_current_context diff --git a/opentelemetry-api/src/opentelemetry/context/base_context.py b/opentelemetry-api/src/opentelemetry/context/base_context.py index 95a235daaa..e0fa514bf9 100644 --- a/opentelemetry-api/src/opentelemetry/context/base_context.py +++ b/opentelemetry-api/src/opentelemetry/context/base_context.py @@ -13,9 +13,7 @@ # limitations under the License. import abc -import threading import typing -from contextlib import contextmanager def wrap_callable(target: "object") -> typing.Callable[[], object]: @@ -24,221 +22,27 @@ def wrap_callable(target: "object") -> typing.Callable[[], object]: return lambda: target -class Context(abc.ABC): +class Context: def __init__(self) -> None: self.snapshot = {} - def get(self, key: str) -> typing.Optional["object"]: - return self.snapshot.get(key) + def value(self, name): + return self.snapshot.get(name) - @contextmanager - def use(self, **kwargs: typing.Dict[str, object]) -> typing.Iterator[None]: - snapshot = self.current() - for key in kwargs: - self.set_value(key, kwargs[key]) - yield - self.set_current(snapshot) - - def with_current_context( - self, func: typing.Callable[..., "object"] - ) -> typing.Callable[..., "object"]: - """Capture the current context and apply it to the provided func. - """ - - caller_context = self.current() - - def call_with_current_context( - *args: "object", **kwargs: "object" - ) -> "object": - try: - backup_context = self.current() - self.set_current(caller_context) - return func(*args, **kwargs) - finally: - self.set_current(backup_context) - - return call_with_current_context +class Slot(abc.ABC): @abc.abstractmethod - def current(self) -> "Context": - """ - To access the context associated with program execution, - the Context API provides a function which takes no arguments - and returns a Context. - """ + def __init__(self, name: str, default: "object"): + raise NotImplementedError @abc.abstractmethod - def set_current(self, context: "Context") -> None: - """ - To associate a context with program execution, the Context - API provides a function which takes a Context. - """ + def clear(self) -> None: + raise NotImplementedError @abc.abstractmethod - def set_value( - self, - key: str, - value: "object", - context: typing.Optional["Context"] = None, - ) -> "Context": - """ - To record the local state of a cross-cutting concern, the - Context API provides a function which takes a context, a - key, and a value as input, and returns an updated context - which contains the new value. - - Args: - key: - value: - """ + def get(self) -> "object": + raise NotImplementedError @abc.abstractmethod - def value( - self, key: str, context: typing.Optional["Context"] = None - ) -> typing.Optional["object"]: - """ - To access the local state of an concern, the Context API - provides a function which takes a context and a key as input, - and returns a value. - """ - - -class BaseContext(Context): - class Slot: - def __init__(self, name: str, default: "object"): - raise NotImplementedError - - def clear(self) -> None: - raise NotImplementedError - - def get(self) -> "object": - raise NotImplementedError - - def set(self, value: "object") -> None: - raise NotImplementedError - - _lock = threading.Lock() - _slots = {} # type: typing.Dict[str, 'BaseContext.Slot'] - - @classmethod - def clear(cls) -> None: - """Clear all slots to their default value.""" - keys = cls._slots.keys() - for name in keys: - slot = cls._slots[name] - slot.clear() - - @classmethod - def register_slot( - cls, name: str, default: "object" = None - ) -> "BaseContext.Slot": - """Register a context slot with an optional default value. - - :type name: str - :param name: The name of the context slot. - - :type default: object - :param name: The default value of the slot, can be a value or lambda. - - :returns: The registered slot. - """ - with cls._lock: - if name not in cls._slots: - cls._slots[name] = cls.Slot(name, default) - return cls._slots[name] - - def merge(self, context: Context) -> Context: - new_context = self.current() - for key in context.snapshot: - new_context.snapshot[key] = context.snapshot[key] - return new_context - - def _freeze(self) -> typing.Dict[str, "object"]: - """Return a dictionary of current slots by reference.""" - - keys = self._slots.keys() - return dict((n, self._slots[n].get()) for n in keys) - - def __repr__(self) -> str: - return "{}({})".format(type(self).__name__, self._freeze()) - - def _set(self, key: str, value: "object") -> None: - """ - Set creates a Slot for the value if none exists, and sets the - value for that slot. - - Args: - key: Name of the value - value: Contents of the value - """ - if key not in self._slots: - self.register_slot(key, None) - slot = self._slots[key] - slot.set(value) - - def value( - self, key: str, context: typing.Optional["Context"] = None - ) -> typing.Optional["object"]: - """ - To access the local state of an concern, the Context API - provides a function which takes a context and a key as input, - and returns a value. - - Args: - key: Name of the value - context: - """ - if context: - return context.get(key) - - return self.current().get(key) - - def set_value( - self, - key: str, - value: "object", - context: typing.Optional["Context"] = None, - ) -> "Context": - """ - To record the local state of a cross-cutting concern, the - Context API provides a function which takes a context, a - key, and a value as input, and returns an updated context - which contains the new value. - - Args: - key: Name of the value - value: Contents of the value - context: - """ - if context: - new_context = self.__class__() - for name in context.snapshot: - new_context.snapshot[name] = context.snapshot[name] - new_context.snapshot[key] = value - return new_context - self._set(key, value) - return self.current() - - def current(self) -> "Context": - """ - To access the context associated with program execution, - the Context API provides a function which takes no arguments - and returns a Context. - """ - ctx = self.__class__() - ctx.snapshot = self._freeze() - return ctx - - def set_current(self, context: "Context") -> None: - """ - To associate a context with program execution, the Context - API provides a function which takes a Context. - """ - keys = self._slots.keys() - for name in keys: - slot = self._slots[name] - slot.clear() - self._slots.clear() - if context.snapshot: - for name in context.snapshot: - self._set(name, context.snapshot[name]) + def set(self, value: "object") -> None: + raise NotImplementedError diff --git a/opentelemetry-api/src/opentelemetry/context/thread_local_context.py b/opentelemetry-api/src/opentelemetry/context/thread_local_context.py index 83fc7fcd55..83f7aa4806 100644 --- a/opentelemetry-api/src/opentelemetry/context/thread_local_context.py +++ b/opentelemetry-api/src/opentelemetry/context/thread_local_context.py @@ -13,33 +13,54 @@ # limitations under the License. import threading -import typing # pylint: disable=unused-import +import typing from . import base_context -class ThreadLocalRuntimeContext(base_context.BaseContext): - class Slot(base_context.BaseContext.Slot): - _thread_local = threading.local() +class ThreadLocalSlot(base_context.Slot): + _thread_local = threading.local() - def __init__(self, name: str, default: "object"): - # pylint: disable=super-init-not-called - self.name = name - self.default = base_context.wrap_callable( - default - ) # type: typing.Callable[..., object] + def __init__(self, name: str, default: "object"): + # pylint: disable=super-init-not-called + self.name = name + self.default = base_context.wrap_callable( + default + ) # type: typing.Callable[..., object] - def clear(self) -> None: - setattr(self._thread_local, self.name, self.default()) + def clear(self) -> None: + setattr(self._thread_local, self.name, self.default()) - def get(self) -> "object": - try: - got = getattr(self._thread_local, self.name) # type: object - return got - except AttributeError: - value = self.default() - self.set(value) - return value + def get(self) -> "object": + try: + got = getattr(self._thread_local, self.name) # type: object + return got + except AttributeError: + value = self.default() + self.set(value) + return value - def set(self, value: "object") -> None: - setattr(self._thread_local, self.name, value) + def set(self, value: "object") -> None: + setattr(self._thread_local, self.name, value) + + +class ThreadLocalRuntimeContext(base_context.Context): + def with_current_context( + self, func: typing.Callable[..., "object"] + ) -> typing.Callable[..., "object"]: + """Capture the current context and apply it to the provided func. + """ + # TODO: implement this + # caller_context = self.current() + + # def call_with_current_context( + # *args: "object", **kwargs: "object" + # ) -> "object": + # try: + # backup_context = self.current() + # self.set_current(caller_context) + # return func(*args, **kwargs) + # finally: + # self.set_current(backup_context) + + # return call_with_current_context diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py index 43eee9cb0b..6451dedbc2 100644 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/__init__.py @@ -17,7 +17,7 @@ import typing from typing import Optional -from opentelemetry.context import Context, current +from opentelemetry import context as ctx_api from opentelemetry.propagation import Extractor, Injector PRINTABLE = frozenset( @@ -90,20 +90,27 @@ class CorrelationContext: class CorrelationContextManager: @classmethod def set_correlation( - cls, key: str, value: "object", context: Optional[Context] = None - ) -> Context: - return current().set_value(key, value, context=context) + cls, + key: str, + value: "object", + context: Optional[ctx_api.Context] = None, + ) -> ctx_api.Context: + return ctx_api.set_value(key, value, context=context) @classmethod def correlation( - cls, key: str, context: Optional[Context] = None + cls, key: str, context: Optional[ctx_api.Context] = None ) -> "object": - return current().value(key, context=context) + return ctx_api.value(key, context=context) @classmethod - def remove_correlation(cls, context: Optional[Context] = None) -> Context: + def remove_correlation( + cls, context: Optional[ctx_api.Context] = None + ) -> ctx_api.Context: pass @classmethod - def clear_correlation(cls, context: Optional[Context] = None) -> Context: + def clear_correlation( + cls, context: Optional[ctx_api.Context] = None + ) -> ctx_api.Context: pass diff --git a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py index 2e447b17cb..0e4a881591 100644 --- a/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/correlationcontext/propagation/context/__init__.py @@ -13,7 +13,7 @@ # limitations under the License. from typing import Optional -from opentelemetry.context import Context, current +from opentelemetry.context import Context, set_value, value from opentelemetry.correlationcontext import CorrelationContext from opentelemetry.correlationcontext.propagation import ContextKeys @@ -21,12 +21,12 @@ def correlation_context_from_context( context: Optional[Context] = None, ) -> CorrelationContext: - return current().value(ContextKeys.span_context_key(), context=context) # type: ignore + return value(ContextKeys.span_context_key(), context=context) # type: ignore def with_correlation_context( correlation_context: CorrelationContext, context: Optional[Context] = None, ) -> Context: - return current().set_value( + return set_value( ContextKeys.span_context_key(), correlation_context, context=context ) diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py index 675d5a015d..961f3fd9b5 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/context/__init__.py @@ -13,18 +13,18 @@ # limitations under the License. from typing import Optional -from opentelemetry.context import Context, current +from opentelemetry import context as ctx_api from opentelemetry.trace import INVALID_SPAN_CONTEXT, Span, SpanContext from opentelemetry.trace.propagation import ContextKeys def span_context_from_context( - context: Optional[Context] = None, + context: Optional[ctx_api.Context] = None, ) -> SpanContext: span = span_from_context(context=context) if span: return span.get_context() - sc = current().value(ContextKeys.span_context_key(), context=context) # type: ignore + sc = ctx_api.value(ContextKeys.span_context_key(), context=context) # type: ignore if sc: return sc @@ -32,16 +32,18 @@ def span_context_from_context( def with_span_context( - span_context: SpanContext, context: Optional[Context] = None -) -> Context: - return current().set_value( + span_context: SpanContext, context: Optional[ctx_api.Context] = None +) -> ctx_api.Context: + return ctx_api.set_value( ContextKeys.span_context_key(), span_context, context=context ) -def span_from_context(context: Optional[Context] = None) -> Span: - return current().value(ContextKeys.span_key(), context=context) # type: ignore +def span_from_context(context: Optional[ctx_api.Context] = None) -> Span: + return ctx_api.value(ContextKeys.span_key(), context=context) # type: ignore -def with_span(span: Span, context: Optional[Context] = None) -> Context: - return current().set_value(ContextKeys.span_key(), span, context=context) +def with_span( + span: Span, context: Optional[ctx_api.Context] = None +) -> ctx_api.Context: + return ctx_api.set_value(ContextKeys.span_key(), span, context=context) diff --git a/opentelemetry-api/tests/context/test_context.py b/opentelemetry-api/tests/context/test_context.py new file mode 100644 index 0000000000..77ca794345 --- /dev/null +++ b/opentelemetry-api/tests/context/test_context.py @@ -0,0 +1,87 @@ +# 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 unittest + +from opentelemetry import context + + +def do_work(): + context.set_value("say-something", "bar") + + +class TestContext(unittest.TestCase): + def test_context(self): + self.assertIsNone(context.current().value("say-something")) + empty_context = context.current() + context.set_value("say-something", "foo") + self.assertEqual(context.current().value("say-something"), "foo") + second_context = context.current() + + do_work() + self.assertEqual(context.current().value("say-something"), "bar") + third_context = context.current() + + self.assertIsNone(empty_context.value("say-something")) + self.assertEqual(second_context.value("say-something"), "foo") + self.assertEqual(third_context.value("say-something"), "bar") + + def test_merge(self): + context.set_value("name", "first") + context.set_value("somebool", True) + context.set_value("key", "value") + context.set_value("otherkey", "othervalue") + src_ctx = context.current() + + context.set_value("name", "second") + context.set_value("somebool", False) + context.set_value("anotherkey", "anothervalue") + dst_ctx = context.current() + + context.set_current( + context.merge_context_correlation(src_ctx, dst_ctx) + ) + current = context.current() + self.assertEqual(current.value("name"), "first") + self.assertTrue(current.value("somebool")) + self.assertEqual(current.value("key"), "value") + self.assertEqual(current.value("otherkey"), "othervalue") + self.assertEqual(current.value("anotherkey"), "anothervalue") + + def test_propagation(self): + pass + + def test_restore_context_on_exit(self): + context.set_current(context.new_context()) + context.set_value("a", "xxx") + context.set_value("b", "yyy") + + self.assertEqual({"a": "xxx", "b": "yyy"}, context.current().snapshot) + with context.use(a="foo"): + self.assertEqual( + {"a": "foo", "b": "yyy"}, context.current().snapshot + ) + context.set_value("a", "i_want_to_mess_it_but_wont_work") + context.set_value("b", "i_want_to_mess_it") + self.assertEqual({"a": "xxx", "b": "yyy"}, context.current().snapshot) + + def test_set_value(self): + first = context.set_value("a", "yyy") + second = context.set_value("a", "zzz") + third = context.set_value("a", "---", first) + current_context = context.current() + self.assertEqual("yyy", context.value("a", context=first)) + self.assertEqual("zzz", context.value("a", context=second)) + self.assertEqual("---", context.value("a", context=third)) + self.assertEqual("zzz", context.value("a", context=current_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 eff8e80abb..3ad69a1d29 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py @@ -15,6 +15,7 @@ import typing import opentelemetry.trace as trace +from opentelemetry.context import Context from opentelemetry.propagation import ( Extractor, Getter, @@ -24,7 +25,6 @@ set_in_dict, ) from opentelemetry.trace.propagation.context import ( - Context, span_context_from_context, with_span_context, ) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py index 373aa8e5c8..06473fa4b5 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/correlationcontext/__init__.py @@ -16,7 +16,7 @@ from contextlib import contextmanager from opentelemetry import correlationcontext as cctx_api -from opentelemetry.context import Context, current +from opentelemetry.context import current, set_value, value class CorrelationContextManager(cctx_api.CorrelationContextManager): @@ -38,7 +38,7 @@ def current_context(self,) -> typing.Optional[cctx_api.CorrelationContext]: Returns: A CorrelationContext instance representing the current context. """ - return current().value(self.slot_name) + return value(self.slot_name) @contextmanager def use_context( @@ -55,8 +55,8 @@ def use_context( context: A CorrelationContext instance to make current. """ snapshot = current().value(self.slot_name) - current().set_value(self.slot_name, context) + set_value(self.slot_name, context) try: yield context finally: - current().set_value(self.slot_name, snapshot) + set_value(self.slot_name, snapshot) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 0fc0f7d934..2e33f22277 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -21,8 +21,8 @@ from types import TracebackType from typing import Iterator, Optional, Sequence, Tuple, Type +from opentelemetry import context as ctx_api from opentelemetry import trace as trace_api -from opentelemetry.context import Context, current from opentelemetry.sdk import util from opentelemetry.sdk.util import BoundedDict, BoundedList from opentelemetry.trace import SpanContext, sampling @@ -392,7 +392,7 @@ def __init__( self.source = source self.instrumentation_info = instrumentation_info - def get_current_span(self, context: Optional[Context] = None): + def get_current_span(self, context: Optional[ctx_api.Context] = None): """See `opentelemetry.trace.Tracer.get_current_span`.""" return span_from_context(context=context) @@ -403,7 +403,7 @@ def start_as_current_span( kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL, attributes: Optional[types.Attributes] = None, links: Sequence[trace_api.Link] = (), - context: Optional[Context] = None, + context: Optional[ctx_api.Context] = None, ) -> Iterator[trace_api.Span]: """See `opentelemetry.trace.Tracer.start_as_current_span`.""" @@ -421,7 +421,7 @@ def start_span( # pylint: disable=too-many-locals links: Sequence[trace_api.Link] = (), start_time: Optional[int] = None, set_status_on_exception: bool = True, - context: Optional[Context] = None, + context: Optional[ctx_api.Context] = None, ) -> trace_api.Span: """See `opentelemetry.trace.Tracer.start_span`.""" @@ -540,9 +540,11 @@ def get_tracer( ), ) - def get_current_span(self, context: Optional[Context] = None) -> Span: + def get_current_span( + self, context: Optional[ctx_api.Context] = None + ) -> Span: """See `opentelemetry.trace.Tracer.get_current_span`.""" - return current().value(self._current_span_name, context=context) + return ctx_api.value(self._current_span_name, context=context) def add_span_processor(self, span_processor: SpanProcessor) -> None: """Registers a new :class:`SpanProcessor` for this `TracerSource`. diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py index 3ca1bb9ebc..e68807219f 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py @@ -18,7 +18,7 @@ import typing from enum import Enum -from opentelemetry.context import current +from opentelemetry import context from opentelemetry.util import time_ns from .. import Span, SpanProcessor @@ -73,7 +73,7 @@ def on_start(self, span: Span) -> None: pass def on_end(self, span: Span) -> None: - with current().use(suppress_instrumentation=True): + with context.use(suppress_instrumentation=True): try: self.span_exporter.export((span,)) # pylint: disable=broad-except @@ -182,7 +182,7 @@ def export(self) -> None: while idx < self.max_export_batch_size and self.queue: self.spans_list[idx] = self.queue.pop() idx += 1 - with current().use(suppress_instrumentation=True): + with context.use(suppress_instrumentation=True): try: # Ignore type b/c the Optional[None]+slicing is too "clever" # for mypy diff --git a/opentelemetry-sdk/tests/context/test_context.py b/opentelemetry-sdk/tests/context/test_context.py deleted file mode 100644 index ae0f3b2dd6..0000000000 --- a/opentelemetry-sdk/tests/context/test_context.py +++ /dev/null @@ -1,156 +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 concurrent.futures -import contextvars -import unittest -from multiprocessing.dummy import Pool as ThreadPool - -from opentelemetry.context import ( - current, - merge_context_correlation, - new_context, -) -from opentelemetry.sdk import trace -from opentelemetry.sdk.trace import export -from opentelemetry.sdk.trace.export.in_memory_span_exporter import ( - InMemorySpanExporter, -) - - -def do_work(): - current().set_value("say-something", "bar") - - -class TestContext(unittest.TestCase): - spans = [ - "test_span1", - "test_span2", - "test_span3", - "test_span4", - "test_span5", - ] - - def do_some_work(self, name): - with self.tracer.start_as_current_span(name): - pass - - def setUp(self): - self.tracer_source = trace.TracerSource() - self.tracer = self.tracer_source.get_tracer(__name__) - self.memory_exporter = InMemorySpanExporter() - span_processor = export.SimpleExportSpanProcessor(self.memory_exporter) - self.tracer_source.add_span_processor(span_processor) - - def test_context(self): - self.assertIsNone(current().value("say-something")) - empty_context = current() - current().set_value("say-something", "foo") - self.assertEqual(current().value("say-something"), "foo") - second_context = current() - - do_work() - self.assertEqual(current().value("say-something"), "bar") - third_context = current() - - self.assertIsNone(empty_context.get("say-something")) - self.assertEqual(second_context.get("say-something"), "foo") - self.assertEqual(third_context.get("say-something"), "bar") - - def test_merge(self): - current().set_value("name", "first") - current().set_value("somebool", True) - current().set_value("key", "value") - current().set_value("otherkey", "othervalue") - src_ctx = current() - - current().set_value("name", "second") - current().set_value("somebool", False) - current().set_value("anotherkey", "anothervalue") - dst_ctx = current() - - current().set_current(merge_context_correlation(src_ctx, dst_ctx)) - self.assertEqual(current().get("name"), "first") - self.assertTrue(current().get("somebool")) - self.assertEqual(current().get("key"), "value") - self.assertEqual(current().get("otherkey"), "othervalue") - self.assertEqual(current().get("anotherkey"), "anothervalue") - - def test_propagation(self): - pass - - def test_with_futures(self): - with self.tracer.start_as_current_span("futures_test"): - with concurrent.futures.ThreadPoolExecutor( - max_workers=5 - ) as executor: - # Start the load operations - for span in self.spans: - executor.submit( - contextvars.copy_context().run, - self.do_some_work, - span, - ) - - span_list = self.memory_exporter.get_finished_spans() - expected = [ - "test_span1", - "test_span2", - "test_span3", - "test_span4", - "test_span5", - "futures_test", - ] - self.assertEqual(len(span_list), len(expected)) - - def test_with_threads(self): - with self.tracer.start_as_current_span("threads_test"): - pool = ThreadPool(5) # create a thread pool - pool.map( - current().with_current_context(self.do_some_work), self.spans, - ) - pool.close() - pool.join() - span_list = self.memory_exporter.get_finished_spans() - expected = [ - "test_span1", - "test_span2", - "test_span3", - "test_span4", - "test_span5", - "threads_test", - ] - self.assertEqual(len(span_list), len(expected)) - - def test_restore_context_on_exit(self): - current().set_current(new_context()) - current().set_value("a", "xxx") - current().set_value("b", "yyy") - - self.assertEqual({"a": "xxx", "b": "yyy"}, current().snapshot) - with current().use(a="foo"): - self.assertEqual({"a": "foo", "b": "yyy"}, current().snapshot) - current().set_value("a", "i_want_to_mess_it_but_wont_work") - current().set_value("b", "i_want_to_mess_it") - self.assertEqual({"a": "xxx", "b": "yyy"}, current().snapshot) - - def test_set_value(self): - context = current().set_value("a", "yyy") - context2 = current().set_value("a", "zzz") - context3 = current().set_value("a", "---", context) - current_context = current() - self.assertEqual("yyy", current().value("a", context=context)) - self.assertEqual("zzz", current().value("a", context=context2)) - self.assertEqual("---", current().value("a", context=context3)) - self.assertEqual("zzz", current().value("a", context=current_context)) diff --git a/opentelemetry-sdk/tests/context/test_futures.py b/opentelemetry-sdk/tests/context/test_futures.py new file mode 100644 index 0000000000..5761bca3ed --- /dev/null +++ b/opentelemetry-sdk/tests/context/test_futures.py @@ -0,0 +1,84 @@ +# 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 concurrent.futures +import unittest + +from opentelemetry import context +from opentelemetry.sdk import trace +from opentelemetry.sdk.trace import export +from opentelemetry.sdk.trace.export.in_memory_span_exporter import ( + InMemorySpanExporter, +) + + +class TestContextWithFutures(unittest.TestCase): + span_names = [ + "test_span1", + "test_span2", + "test_span3", + "test_span4", + "test_span5", + ] + + def do_work(self, name="default"): + with self.tracer.start_as_current_span(name): + context.set_value("say-something", "bar") + + def setUp(self): + self.tracer_source = trace.TracerSource() + self.tracer = self.tracer_source.get_tracer(__name__) + self.memory_exporter = InMemorySpanExporter() + span_processor = export.SimpleExportSpanProcessor(self.memory_exporter) + self.tracer_source.add_span_processor(span_processor) + + def test_with_futures(self): + try: + import contextvars # pylint: disable=import-outside-toplevel + except ImportError: + self.skipTest("contextvars not available") + + with self.tracer.start_as_current_span("futures_test"): + with concurrent.futures.ThreadPoolExecutor( + max_workers=5 + ) as executor: + # Start the load operations + for span in self.span_names: + executor.submit( + contextvars.copy_context().run, self.do_work, span, + ) + span_list = self.memory_exporter.get_finished_spans() + span_names_list = [span.name for span in span_list] + + expected = [ + "test_span1", + "test_span2", + "test_span3", + "test_span4", + "test_span5", + "futures_test", + ] + + self.assertCountEqual(span_names_list, expected) + span_names_list.sort() + expected.sort() + self.assertListEqual(span_names_list, expected) + # expected_parent = next( + # span for span in span_list if span.name == "futures_test" + # ) + # TODO: ensure the following passes + # for span in span_list: + # if span is expected_parent: + # continue + # self.assertEqual(span.parent, expected_parent) + # diff --git a/opentelemetry-sdk/tests/context/test_threads.py b/opentelemetry-sdk/tests/context/test_threads.py new file mode 100644 index 0000000000..39c6428f76 --- /dev/null +++ b/opentelemetry-sdk/tests/context/test_threads.py @@ -0,0 +1,75 @@ +# 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 unittest +from multiprocessing.dummy import Pool as ThreadPool + +from opentelemetry import context +from opentelemetry.sdk import trace +from opentelemetry.sdk.trace import export +from opentelemetry.sdk.trace.export.in_memory_span_exporter import ( + InMemorySpanExporter, +) +from opentelemetry.trace.propagation.context import span_from_context + + +class TestContext(unittest.TestCase): + span_names = [ + "test_span1", + "test_span2", + "test_span3", + "test_span4", + "test_span5", + ] + + def do_work(self, name="default"): + with self.tracer.start_as_current_span(name): + context.set_value("say-something", "bar") + + def setUp(self): + self.tracer_source = trace.TracerSource() + self.tracer = self.tracer_source.get_tracer(__name__) + self.memory_exporter = InMemorySpanExporter() + span_processor = export.SimpleExportSpanProcessor(self.memory_exporter) + self.tracer_source.add_span_processor(span_processor) + + def test_with_threads(self): + with self.tracer.start_as_current_span("threads_test"): + print(span_from_context()) + pool = ThreadPool(5) # create a thread pool + pool.map(self.do_work, self.span_names) + pool.close() + pool.join() + span_list = self.memory_exporter.get_finished_spans() + span_names_list = [span.name for span in span_list] + expected = [ + "test_span1", + "test_span2", + "test_span3", + "test_span4", + "test_span5", + "threads_test", + ] + self.assertCountEqual(span_names_list, expected) + span_names_list.sort() + expected.sort() + self.assertListEqual(span_names_list, expected) + # expected_parent = next( + # span for span in span_list if span.name == "threads_test" + # ) + # TODO: ensure the following passes + # for span in span_list: + # if span is expected_parent: + # continue + # self.assertEqual(span.parent, expected_parent) diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index 6402c3724b..4486004845 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -18,7 +18,7 @@ from unittest import mock from opentelemetry import trace as trace_api -from opentelemetry.context import current, new_context +from opentelemetry.context import new_context, set_current from opentelemetry.sdk import trace from opentelemetry.trace import sampling from opentelemetry.trace.status import StatusCanonicalCode @@ -132,7 +132,7 @@ def test_sampler_no_sampling(self): class TestSpanCreation(unittest.TestCase): def setUp(self): - current().set_current(new_context()) + set_current(new_context()) def test_start_span_invalid_spancontext(self): """If an invalid span context is passed as the parent, the created