diff --git a/docs/api/trace.rst b/docs/api/trace.rst index 00823aa0362..411e31023ec 100644 --- a/docs/api/trace.rst +++ b/docs/api/trace.rst @@ -8,8 +8,9 @@ Submodules trace.sampling trace.status + trace.span Module contents --------------- -.. automodule:: opentelemetry.trace +.. automodule:: opentelemetry.trace \ No newline at end of file diff --git a/docs/api/trace.span.rst b/docs/api/trace.span.rst new file mode 100644 index 00000000000..94b36930dfb --- /dev/null +++ b/docs/api/trace.span.rst @@ -0,0 +1,7 @@ +opentelemetry.trace.span +======================== + +.. automodule:: opentelemetry.trace.span + :members: + :undoc-members: + :show-inheritance: diff --git a/ext/opentelemetry-ext-aiohttp-client/src/opentelemetry/ext/aiohttp_client/__init__.py b/ext/opentelemetry-ext-aiohttp-client/src/opentelemetry/ext/aiohttp_client/__init__.py index bc6c361f9bd..a05e7c06015 100644 --- a/ext/opentelemetry-ext-aiohttp-client/src/opentelemetry/ext/aiohttp_client/__init__.py +++ b/ext/opentelemetry-ext-aiohttp-client/src/opentelemetry/ext/aiohttp_client/__init__.py @@ -145,7 +145,7 @@ async def on_request_start( ) trace_config_ctx.token = context_api.attach( - trace.propagation.set_span_in_context(trace_config_ctx.span) + trace.set_span_in_context(trace_config_ctx.span) ) propagators.inject(type(params.headers).__setitem__, params.headers) diff --git a/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/propagator.py b/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/propagator.py index d6595e8d93a..6ff192f4257 100644 --- a/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/propagator.py +++ b/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/propagator.py @@ -16,10 +16,7 @@ from opentelemetry import trace from opentelemetry.context import Context -from opentelemetry.trace.propagation import ( - get_span_from_context, - set_span_in_context, -) +from opentelemetry.trace import get_current_span, set_span_in_context from opentelemetry.trace.propagation.httptextformat import ( Getter, HTTPTextFormat, @@ -88,7 +85,7 @@ def inject( carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: - span = get_span_from_context(context=context) + span = get_current_span(context) sampled = (trace.TraceFlags.SAMPLED & span.context.trace_flags) != 0 set_in_carrier( carrier, self.TRACE_ID_KEY, format_trace_id(span.context.trace_id), diff --git a/ext/opentelemetry-ext-datadog/tests/test_datadog_format.py b/ext/opentelemetry-ext-datadog/tests/test_datadog_format.py index cf2fbf42208..31633f83701 100644 --- a/ext/opentelemetry-ext-datadog/tests/test_datadog_format.py +++ b/ext/opentelemetry-ext-datadog/tests/test_datadog_format.py @@ -17,10 +17,7 @@ from opentelemetry import trace as trace_api from opentelemetry.ext.datadog import constants, propagator from opentelemetry.sdk import trace -from opentelemetry.trace.propagation import ( - get_span_from_context, - set_span_in_context, -) +from opentelemetry.trace import get_current_span, set_span_in_context FORMAT = propagator.DatadogFormat() @@ -45,7 +42,7 @@ def test_malformed_headers(self): """Test with no Datadog headers""" malformed_trace_id_key = FORMAT.TRACE_ID_KEY + "-x" malformed_parent_id_key = FORMAT.PARENT_ID_KEY + "-x" - context = get_span_from_context( + context = get_current_span( FORMAT.extract( get_as_list, { @@ -66,7 +63,7 @@ def test_missing_trace_id(self): } ctx = FORMAT.extract(get_as_list, carrier) - span_context = get_span_from_context(ctx).get_context() + span_context = get_current_span(ctx).get_context() self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID) def test_missing_parent_id(self): @@ -76,12 +73,12 @@ def test_missing_parent_id(self): } ctx = FORMAT.extract(get_as_list, carrier) - span_context = get_span_from_context(ctx).get_context() + span_context = get_current_span(ctx).get_context() self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID) def test_context_propagation(self): """Test the propagation of Datadog headers.""" - parent_context = get_span_from_context( + parent_context = get_current_span( FORMAT.extract( get_as_list, { @@ -138,7 +135,7 @@ def test_context_propagation(self): def test_sampling_priority_auto_reject(self): """Test sampling priority rejected.""" - parent_context = get_span_from_context( + parent_context = get_current_span( FORMAT.extract( get_as_list, { diff --git a/ext/opentelemetry-ext-grpc/tests/test_server_interceptor.py b/ext/opentelemetry-ext-grpc/tests/test_server_interceptor.py index 6c055c863c2..67ff0053bf3 100644 --- a/ext/opentelemetry-ext-grpc/tests/test_server_interceptor.py +++ b/ext/opentelemetry-ext-grpc/tests/test_server_interceptor.py @@ -91,15 +91,13 @@ def test_span_lifetime(self): """Check that the span is active for the duration of the call.""" interceptor = server_interceptor() - tracer = self.tracer_provider.get_tracer(__name__) # To capture the current span at the time the handler is called active_span_in_handler = None def handler(request, context): nonlocal active_span_in_handler - # The current span is shared among all the tracers. - active_span_in_handler = tracer.get_current_span() + active_span_in_handler = trace.get_current_span() return b"" server = grpc.server( @@ -112,13 +110,13 @@ def handler(request, context): port = server.add_insecure_port("[::]:0") channel = grpc.insecure_channel("localhost:{:d}".format(port)) - active_span_before_call = tracer.get_current_span() + active_span_before_call = trace.get_current_span() try: server.start() channel.unary_unary("")(b"") finally: server.stop(None) - active_span_after_call = tracer.get_current_span() + active_span_after_call = trace.get_current_span() self.assertIsNone(active_span_before_call) self.assertIsNone(active_span_after_call) @@ -128,15 +126,13 @@ def handler(request, context): def test_sequential_server_spans(self): """Check that sequential RPCs get separate server spans.""" - tracer = self.tracer_provider.get_tracer(__name__) - interceptor = server_interceptor() # Capture the currently active span in each thread active_spans_in_handler = [] def handler(request, context): - active_spans_in_handler.append(tracer.get_current_span()) + active_spans_in_handler.append(trace.get_current_span()) return b"" server = grpc.server( @@ -175,8 +171,6 @@ def test_concurrent_server_spans(self): context. """ - tracer = self.tracer_provider.get_tracer(__name__) - interceptor = server_interceptor() # Capture the currently active span in each thread @@ -185,7 +179,7 @@ def test_concurrent_server_spans(self): def handler(request, context): latch() - active_spans_in_handler.append(tracer.get_current_span()) + active_spans_in_handler.append(trace.get_current_span()) return b"" server = grpc.server( 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 c62f063de24..81f25013ff0 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 @@ -94,11 +94,7 @@ from opentelemetry import propagators from opentelemetry.ext.opentracing_shim import util from opentelemetry.ext.opentracing_shim.version import __version__ -from opentelemetry.trace import DefaultSpan -from opentelemetry.trace.propagation import ( - get_span_from_context, - set_span_in_context, -) +from opentelemetry.trace import DefaultSpan, set_span_in_context logger = logging.getLogger(__name__) @@ -473,7 +469,7 @@ def active(self): shim and is likely to be handled in future versions. """ - span = self._tracer.unwrap().get_current_span() + span = trace_api.get_current_span() if span is None: return None @@ -703,6 +699,10 @@ def get_as_list(dict_object, key): propagator = propagators.get_global_httptextformat() ctx = propagator.extract(get_as_list, carrier) - otel_context = get_span_from_context(ctx).get_context() + span = trace_api.get_current_span(ctx) + if span is not None: + otel_context = span.get_context() + else: + otel_context = trace_api.INVALID_SPAN_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 abca2e052bc..941d4280690 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -24,7 +24,10 @@ from opentelemetry import propagators, trace from opentelemetry.ext.opentracing_shim import util from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.test.mock_httptextformat import MockHTTPTextFormat +from opentelemetry.test.mock_httptextformat import ( + MockHTTPTextFormat, + NOOPHTTPTextFormat, +) class TestShim(TestCase): @@ -515,6 +518,20 @@ def test_extract_http_headers(self): self.assertEqual(ctx.unwrap().trace_id, 1220) self.assertEqual(ctx.unwrap().span_id, 7478) + def test_extract_empty_context_returns_invalid_context(self): + """In the case where the propagator cannot extract a + SpanContext, extract should return and invalid span context. + """ + _old_propagator = propagators.get_global_httptextformat() + propagators.set_global_httptextformat(NOOPHTTPTextFormat()) + try: + carrier = {} + + ctx = self.shim.extract(opentracing.Format.HTTP_HEADERS, carrier) + self.assertEqual(ctx.unwrap(), trace.INVALID_SPAN_CONTEXT) + finally: + propagators.set_global_httptextformat(_old_propagator) + def test_extract_text_map(self): """Test `extract()` method for Format.TEXT_MAP.""" 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 0895bc75b07..c5b0216869c 100644 --- a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py +++ b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py @@ -62,7 +62,6 @@ def hello(): from opentelemetry import context, propagators, trace from opentelemetry.ext.wsgi.version import __version__ from opentelemetry.instrumentation.utils import http_status_to_canonical_code -from opentelemetry.trace.propagation import get_span_from_context from opentelemetry.trace.status import Status, StatusCanonicalCode _HTTP_VERSION_PREFIX = "HTTP/" diff --git a/opentelemetry-api/CHANGELOG.md b/opentelemetry-api/CHANGELOG.md index 4503489fa85..b1610ab2c3d 100644 --- a/opentelemetry-api/CHANGELOG.md +++ b/opentelemetry-api/CHANGELOG.md @@ -6,6 +6,8 @@ ([#751](https://github.com/open-telemetry/opentelemetry-python/pull/751)) - Rename Measure to ValueRecorder in metrics ([#761](https://github.com/open-telemetry/opentelemetry-python/pull/761)) +- Adding trace.get_current_span, Removing Tracer.get_current_span + ([#552](https://github.com/open-telemetry/opentelemetry-python/pull/552)) - Rename Observer to ValueObserver ([#764](https://github.com/open-telemetry/opentelemetry-python/pull/764)) diff --git a/opentelemetry-api/src/opentelemetry/trace/__init__.py b/opentelemetry-api/src/opentelemetry/trace/__init__.py index 709b84284cb..fa0bc376e7f 100644 --- a/opentelemetry-api/src/opentelemetry/trace/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/__init__.py @@ -70,6 +70,36 @@ `set_tracer_provider`. """ +__all__ = [ + "DEFAULT_TRACE_OPTIONS", + "DEFAULT_TRACE_STATE", + "INVALID_SPAN", + "INVALID_SPAN_CONTEXT", + "INVALID_SPAN_ID", + "INVALID_TRACE_ID", + "DefaultSpan", + "DefaultTracer", + "DefaultTracerProvider", + "LazyLink", + "Link", + "LinkBase", + "ParentSpan", + "Span", + "SpanContext", + "SpanKind", + "TraceFlags", + "TraceState", + "TracerProvider", + "Tracer", + "format_span_id", + "format_trace_id", + "get_current_span", + "get_tracer", + "get_tracer_provider", + "set_tracer_provider", + "set_span_in_context", +] + import abc import enum import types as python_types @@ -77,6 +107,26 @@ from contextlib import contextmanager from logging import getLogger +from opentelemetry.configuration import Configuration +from opentelemetry.trace.propagation import ( + get_current_span, + set_span_in_context, +) +from opentelemetry.trace.span import ( + DEFAULT_TRACE_OPTIONS, + DEFAULT_TRACE_STATE, + INVALID_SPAN, + INVALID_SPAN_CONTEXT, + INVALID_SPAN_ID, + INVALID_TRACE_ID, + DefaultSpan, + Span, + SpanContext, + TraceFlags, + TraceState, + format_span_id, + format_trace_id, +) from opentelemetry.trace.status import Status from opentelemetry.util import _load_trace_provider, types @@ -170,282 +220,6 @@ class SpanKind(enum.Enum): CONSUMER = 4 -class Span(abc.ABC): - """A span represents a single operation within a trace.""" - - @abc.abstractmethod - def end(self, end_time: typing.Optional[int] = None) -> None: - """Sets the current time as the span's end time. - - The span's end time is the wall time at which the operation finished. - - Only the first call to `end` should modify the span, and - implementations are free to ignore or raise on further calls. - """ - - @abc.abstractmethod - def get_context(self) -> "SpanContext": - """Gets the span's SpanContext. - - Get an immutable, serializable identifier for this span that can be - used to create new child spans. - - Returns: - A :class:`.SpanContext` with a copy of this span's immutable state. - """ - - @abc.abstractmethod - def set_attribute(self, key: str, value: types.AttributeValue) -> None: - """Sets an Attribute. - - Sets a single Attribute with the key and value passed as arguments. - """ - - @abc.abstractmethod - def add_event( - self, - name: str, - attributes: types.Attributes = None, - timestamp: typing.Optional[int] = None, - ) -> None: - """Adds an `Event`. - - Adds a single `Event` with the name and, optionally, a timestamp and - attributes passed as arguments. Implementations should generate a - timestamp if the `timestamp` argument is omitted. - """ - - @abc.abstractmethod - def add_lazy_event( - self, - name: str, - event_formatter: types.AttributesFormatter, - timestamp: typing.Optional[int] = None, - ) -> None: - """Adds an `Event`. - - Adds a single `Event` with the name, an event formatter that calculates - the attributes lazily and, optionally, a timestamp. Implementations - should generate a timestamp if the `timestamp` argument is omitted. - """ - - @abc.abstractmethod - def update_name(self, name: str) -> None: - """Updates the `Span` name. - - This will override the name provided via :func:`Tracer.start_span`. - - Upon this update, any sampling behavior based on Span name will depend - on the implementation. - """ - - @abc.abstractmethod - def is_recording_events(self) -> bool: - """Returns whether this span will be recorded. - - Returns true if this Span is active and recording information like - events with the add_event operation and attributes using set_attribute. - """ - - @abc.abstractmethod - def set_status(self, status: Status) -> None: - """Sets the Status of the Span. If used, this will override the default - Span status, which is OK. - """ - - @abc.abstractmethod - def record_error(self, err: Exception) -> None: - """Records an error as a span event.""" - - def __enter__(self) -> "Span": - """Invoked when `Span` is used as a context manager. - - Returns the `Span` itself. - """ - 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: - """Ends context manager and calls `end` on the `Span`.""" - - self.end() - - -class TraceFlags(int): - """A bitmask that represents options specific to the trace. - - The only supported option is the "sampled" flag (``0x01``). If set, this - flag indicates that the trace may have been sampled upstream. - - See the `W3C Trace Context - Traceparent`_ spec for details. - - .. _W3C Trace Context - Traceparent: - https://www.w3.org/TR/trace-context/#trace-flags - """ - - DEFAULT = 0x00 - SAMPLED = 0x01 - - @classmethod - def get_default(cls) -> "TraceFlags": - return cls(cls.DEFAULT) - - @property - def sampled(self) -> bool: - return bool(self & TraceFlags.SAMPLED) - - -DEFAULT_TRACE_OPTIONS = TraceFlags.get_default() - - -class TraceState(typing.Dict[str, str]): - """A list of key-value pairs representing vendor-specific trace info. - - Keys and values are strings of up to 256 printable US-ASCII characters. - Implementations should conform to the `W3C Trace Context - Tracestate`_ - spec, which describes additional restrictions on valid field values. - - .. _W3C Trace Context - Tracestate: - https://www.w3.org/TR/trace-context/#tracestate-field - """ - - @classmethod - def get_default(cls) -> "TraceState": - return cls() - - -DEFAULT_TRACE_STATE = TraceState.get_default() - - -def format_trace_id(trace_id: int) -> str: - return "0x{:032x}".format(trace_id) - - -def format_span_id(span_id: int) -> str: - return "0x{:016x}".format(span_id) - - -class SpanContext: - """The state of a Span to propagate between processes. - - This class includes the immutable attributes of a :class:`.Span` that must - be propagated to a span's children and across process boundaries. - - Args: - trace_id: The ID of the trace that this span belongs to. - span_id: This span's ID. - trace_flags: Trace options to propagate. - trace_state: Tracing-system-specific info to propagate. - is_remote: True if propagated from a remote parent. - """ - - def __init__( - self, - trace_id: int, - span_id: int, - is_remote: bool, - trace_flags: "TraceFlags" = DEFAULT_TRACE_OPTIONS, - trace_state: "TraceState" = DEFAULT_TRACE_STATE, - ) -> None: - if trace_flags is None: - trace_flags = DEFAULT_TRACE_OPTIONS - if trace_state is None: - trace_state = DEFAULT_TRACE_STATE - self.trace_id = trace_id - self.span_id = span_id - self.trace_flags = trace_flags - self.trace_state = trace_state - self.is_remote = is_remote - - def __repr__(self) -> str: - return ( - "{}(trace_id={}, span_id={}, trace_state={!r}, is_remote={})" - ).format( - type(self).__name__, - format_trace_id(self.trace_id), - format_span_id(self.span_id), - self.trace_state, - self.is_remote, - ) - - def is_valid(self) -> bool: - """Get whether this `SpanContext` is valid. - - A `SpanContext` is said to be invalid if its trace ID or span ID is - invalid (i.e. ``0``). - - Returns: - True if the `SpanContext` is valid, false otherwise. - """ - return ( - self.trace_id != INVALID_TRACE_ID - and self.span_id != INVALID_SPAN_ID - ) - - -class DefaultSpan(Span): - """The default Span that is used when no Span implementation is available. - - All operations are no-op except context propagation. - """ - - def __init__(self, context: "SpanContext") -> None: - self._context = context - - def get_context(self) -> "SpanContext": - return self._context - - def is_recording_events(self) -> bool: - return False - - def end(self, end_time: typing.Optional[int] = None) -> None: - pass - - def set_attribute(self, key: str, value: types.AttributeValue) -> None: - pass - - def add_event( - self, - name: str, - attributes: types.Attributes = None, - timestamp: typing.Optional[int] = None, - ) -> None: - pass - - def add_lazy_event( - self, - name: str, - event_formatter: types.AttributesFormatter, - timestamp: typing.Optional[int] = None, - ) -> None: - pass - - def update_name(self, name: str) -> None: - pass - - def set_status(self, status: Status) -> None: - pass - - def record_error(self, err: Exception) -> None: - pass - - -INVALID_SPAN_ID = 0x0000000000000000 -INVALID_TRACE_ID = 0x00000000000000000000000000000000 -INVALID_SPAN_CONTEXT = SpanContext( - trace_id=INVALID_TRACE_ID, - span_id=INVALID_SPAN_ID, - is_remote=False, - trace_flags=DEFAULT_TRACE_OPTIONS, - trace_state=DEFAULT_TRACE_STATE, -) -INVALID_SPAN = DefaultSpan(INVALID_SPAN_CONTEXT) - - class TracerProvider(abc.ABC): @abc.abstractmethod def get_tracer( @@ -502,18 +276,6 @@ class Tracer(abc.ABC): # This is the default behavior when creating spans. CURRENT_SPAN = DefaultSpan(INVALID_SPAN_CONTEXT) - @abc.abstractmethod - def get_current_span(self) -> "Span": - """Gets the currently active span from the context. - - If there is no current span, return a placeholder span with an invalid - context. - - Returns: - The currently active :class:`.Span`, or a placeholder span with an - invalid :class:`.SpanContext`. - """ - @abc.abstractmethod def start_span( self, @@ -531,7 +293,7 @@ def start_span( span in this tracer's context. By default the current span will be used as parent, but an explicit - parent can also be specified, either a `Span` or a `SpanContext`. If + parent can also be specified, either a `Span` or a `opentelemetry.trace.SpanContext`. If the specified value is `None`, the created span will be a root span. The span can be used as context manager. On exiting, the span will be @@ -539,7 +301,7 @@ def start_span( Example:: - # tracer.get_current_span() will be used as the implicit parent. + # trace.get_current_span() will be used as the implicit parent. # If none is found, the created span will be a root instance. with tracer.start_span("one") as child: child.add_event("child's event") @@ -585,11 +347,11 @@ def start_as_current_span( with tracer.start_as_current_span("one") as parent: parent.add_event("parent's event") - with tracer.start_as_current_span("two") as child: + with trace.start_as_current_span("two") as child: child.add_event("child's event") - tracer.get_current_span() # returns child - tracer.get_current_span() # returns parent - tracer.get_current_span() # returns previously active span + trace.get_current_span() # returns child + trace.get_current_span() # returns parent + trace.get_current_span() # returns previously active span This is a convenience method for creating spans attached to the tracer's context. Applications that need more control over the span @@ -643,10 +405,6 @@ class DefaultTracer(Tracer): All operations are no-op. """ - def get_current_span(self) -> "Span": - # pylint: disable=no-self-use - return INVALID_SPAN - def start_span( self, name: str, diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py index f17350c7455..45a07c920db 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py @@ -13,22 +13,40 @@ # limitations under the License. from typing import Optional -from opentelemetry import trace as trace_api from opentelemetry.context import get_value, set_value from opentelemetry.context.context import Context +from opentelemetry.trace.span import INVALID_SPAN, Span SPAN_KEY = "current-span" def set_span_in_context( - span: trace_api.Span, context: Optional[Context] = None + span: Span, context: Optional[Context] = None ) -> Context: + """Set the span in the given context. + + Args: + span: The Span to set. + context: a Context object. if one is not passed, the + default current context is used instead. + """ ctx = set_value(SPAN_KEY, span, context=context) return ctx -def get_span_from_context(context: Optional[Context] = None) -> trace_api.Span: +def get_current_span(context: Optional[Context] = None) -> Optional[Span]: + """Retrieve the current span. + + Args: + context: A Context object. If one is not passed, the + default current context is used instead. + + Returns: + The Span set in the context if it exists. None otherwise. + """ span = get_value(SPAN_KEY, context=context) - if not isinstance(span, trace_api.Span): - return trace_api.INVALID_SPAN + if span is None: + return None + if not isinstance(span, Span): + return INVALID_SPAN return span diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py b/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py index 732ce96c665..fa2fae87035 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py @@ -17,11 +17,7 @@ import opentelemetry.trace as trace from opentelemetry.context.context import Context -from opentelemetry.trace.propagation import ( - get_span_from_context, - httptextformat, - set_span_in_context, -) +from opentelemetry.trace.propagation import httptextformat # Keys and values are strings of up to 256 printable US-ASCII characters. # Implementations should conform to the `W3C Trace Context - Tracestate`_ @@ -77,11 +73,11 @@ def extract( header = get_from_carrier(carrier, self._TRACEPARENT_HEADER_NAME) if not header: - return set_span_in_context(trace.INVALID_SPAN, context) + return trace.set_span_in_context(trace.INVALID_SPAN, context) match = re.search(self._TRACEPARENT_HEADER_FORMAT_RE, header[0]) if not match: - return set_span_in_context(trace.INVALID_SPAN, context) + return trace.set_span_in_context(trace.INVALID_SPAN, context) version = match.group(1) trace_id = match.group(2) @@ -89,13 +85,13 @@ def extract( trace_flags = match.group(4) if trace_id == "0" * 32 or span_id == "0" * 16: - return set_span_in_context(trace.INVALID_SPAN, context) + return trace.set_span_in_context(trace.INVALID_SPAN, context) if version == "00": if match.group(5): - return set_span_in_context(trace.INVALID_SPAN, context) + return trace.set_span_in_context(trace.INVALID_SPAN, context) if version == "ff": - return set_span_in_context(trace.INVALID_SPAN, context) + return trace.set_span_in_context(trace.INVALID_SPAN, context) tracestate_headers = get_from_carrier( carrier, self._TRACESTATE_HEADER_NAME @@ -109,7 +105,9 @@ def extract( trace_flags=trace.TraceFlags(trace_flags), trace_state=tracestate, ) - return set_span_in_context(trace.DefaultSpan(span_context), context) + return trace.set_span_in_context( + trace.DefaultSpan(span_context), context + ) def inject( self, @@ -121,8 +119,10 @@ def inject( See `opentelemetry.trace.propagation.httptextformat.HTTPTextFormat.inject` """ - span_context = get_span_from_context(context).get_context() - + span = trace.get_current_span(context) + if span is None: + return + span_context = span.get_context() if span_context == trace.INVALID_SPAN_CONTEXT: return traceparent_string = "00-{:032x}-{:016x}-{:02x}".format( diff --git a/opentelemetry-api/src/opentelemetry/trace/span.py b/opentelemetry-api/src/opentelemetry/trace/span.py new file mode 100644 index 00000000000..7f0f39d92a1 --- /dev/null +++ b/opentelemetry-api/src/opentelemetry/trace/span.py @@ -0,0 +1,274 @@ +import abc +import types as python_types +import typing + +from opentelemetry.trace.status import Status +from opentelemetry.util import types + + +class Span(abc.ABC): + """A span represents a single operation within a trace.""" + + @abc.abstractmethod + def end(self, end_time: typing.Optional[int] = None) -> None: + """Sets the current time as the span's end time. + + The span's end time is the wall time at which the operation finished. + + Only the first call to `end` should modify the span, and + implementations are free to ignore or raise on further calls. + """ + + @abc.abstractmethod + def get_context(self) -> "SpanContext": + """Gets the span's SpanContext. + + Get an immutable, serializable identifier for this span that can be + used to create new child spans. + + Returns: + A :class:`opentelemetry.trace.SpanContext` with a copy of this span's immutable state. + """ + + @abc.abstractmethod + def set_attribute(self, key: str, value: types.AttributeValue) -> None: + """Sets an Attribute. + + Sets a single Attribute with the key and value passed as arguments. + """ + + @abc.abstractmethod + def add_event( + self, + name: str, + attributes: types.Attributes = None, + timestamp: typing.Optional[int] = None, + ) -> None: + """Adds an `Event`. + + Adds a single `Event` with the name and, optionally, a timestamp and + attributes passed as arguments. Implementations should generate a + timestamp if the `timestamp` argument is omitted. + """ + + @abc.abstractmethod + def add_lazy_event( + self, + name: str, + event_formatter: types.AttributesFormatter, + timestamp: typing.Optional[int] = None, + ) -> None: + """Adds an `Event`. + Adds a single `Event` with the name, an event formatter that calculates + the attributes lazily and, optionally, a timestamp. Implementations + should generate a timestamp if the `timestamp` argument is omitted. + """ + + @abc.abstractmethod + def update_name(self, name: str) -> None: + """Updates the `Span` name. + + This will override the name provided via :func:`opentelemetry.trace.Tracer.start_span`. + + Upon this update, any sampling behavior based on Span name will depend + on the implementation. + """ + + @abc.abstractmethod + def is_recording_events(self) -> bool: + """Returns whether this span will be recorded. + + Returns true if this Span is active and recording information like + events with the add_event operation and attributes using set_attribute. + """ + + @abc.abstractmethod + def set_status(self, status: Status) -> None: + """Sets the Status of the Span. If used, this will override the default + Span status, which is OK. + """ + + def __enter__(self) -> "Span": + """Invoked when `Span` is used as a context manager. + + Returns the `Span` itself. + """ + 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: + """Ends context manager and calls `end` on the `Span`.""" + + self.end() + + +class TraceFlags(int): + """A bitmask that represents options specific to the trace. + + The only supported option is the "sampled" flag (``0x01``). If set, this + flag indicates that the trace may have been sampled upstream. + + See the `W3C Trace Context - Traceparent`_ spec for details. + + .. _W3C Trace Context - Traceparent: + https://www.w3.org/TR/trace-context/#trace-flags + """ + + DEFAULT = 0x00 + SAMPLED = 0x01 + + @classmethod + def get_default(cls) -> "TraceFlags": + return cls(cls.DEFAULT) + + @property + def sampled(self) -> bool: + return bool(self & TraceFlags.SAMPLED) + + +DEFAULT_TRACE_OPTIONS = TraceFlags.get_default() + + +class TraceState(typing.Dict[str, str]): + """A list of key-value pairs representing vendor-specific trace info. + + Keys and values are strings of up to 256 printable US-ASCII characters. + Implementations should conform to the `W3C Trace Context - Tracestate`_ + spec, which describes additional restrictions on valid field values. + + .. _W3C Trace Context - Tracestate: + https://www.w3.org/TR/trace-context/#tracestate-field + """ + + @classmethod + def get_default(cls) -> "TraceState": + return cls() + + +DEFAULT_TRACE_STATE = TraceState.get_default() + + +class SpanContext: + """The state of a Span to propagate between processes. + + This class includes the immutable attributes of a :class:`.Span` that must + be propagated to a span's children and across process boundaries. + + Args: + trace_id: The ID of the trace that this span belongs to. + span_id: This span's ID. + trace_flags: Trace options to propagate. + trace_state: Tracing-system-specific info to propagate. + is_remote: True if propagated from a remote parent. + """ + + def __init__( + self, + trace_id: int, + span_id: int, + is_remote: bool, + trace_flags: "TraceFlags" = DEFAULT_TRACE_OPTIONS, + trace_state: "TraceState" = DEFAULT_TRACE_STATE, + ) -> None: + if trace_flags is None: + trace_flags = DEFAULT_TRACE_OPTIONS + if trace_state is None: + trace_state = DEFAULT_TRACE_STATE + self.trace_id = trace_id + self.span_id = span_id + self.trace_flags = trace_flags + self.trace_state = trace_state + self.is_remote = is_remote + + def __repr__(self) -> str: + return ( + "{}(trace_id={}, span_id={}, trace_state={!r}, is_remote={})" + ).format( + type(self).__name__, + format_trace_id(self.trace_id), + format_span_id(self.span_id), + self.trace_state, + self.is_remote, + ) + + def is_valid(self) -> bool: + """Get whether this `SpanContext` is valid. + + A `SpanContext` is said to be invalid if its trace ID or span ID is + invalid (i.e. ``0``). + + Returns: + True if the `SpanContext` is valid, false otherwise. + """ + return ( + self.trace_id != INVALID_TRACE_ID + and self.span_id != INVALID_SPAN_ID + ) + + +class DefaultSpan(Span): + """The default Span that is used when no Span implementation is available. + + All operations are no-op except context propagation. + """ + + def __init__(self, context: "SpanContext") -> None: + self._context = context + + def get_context(self) -> "SpanContext": + return self._context + + def is_recording_events(self) -> bool: + return False + + def end(self, end_time: typing.Optional[int] = None) -> None: + pass + + def set_attribute(self, key: str, value: types.AttributeValue) -> None: + pass + + def add_event( + self, + name: str, + attributes: types.Attributes = None, + timestamp: typing.Optional[int] = None, + ) -> None: + pass + + def add_lazy_event( + self, + name: str, + event_formatter: types.AttributesFormatter, + timestamp: typing.Optional[int] = None, + ) -> None: + pass + + def update_name(self, name: str) -> None: + pass + + def set_status(self, status: Status) -> None: + pass + + +INVALID_SPAN_ID = 0x0000000000000000 +INVALID_TRACE_ID = 0x00000000000000000000000000000000 +INVALID_SPAN_CONTEXT = SpanContext( + trace_id=INVALID_TRACE_ID, + span_id=INVALID_SPAN_ID, + is_remote=False, + trace_flags=DEFAULT_TRACE_OPTIONS, + trace_state=DEFAULT_TRACE_STATE, +) +INVALID_SPAN = DefaultSpan(INVALID_SPAN_CONTEXT) + + +def format_trace_id(trace_id: int) -> str: + return "0x{:032x}".format(trace_id) + + +def format_span_id(span_id: int) -> str: + return "0x{:016x}".format(span_id) diff --git a/opentelemetry-api/tests/propagators/test_global_httptextformat.py b/opentelemetry-api/tests/propagators/test_global_httptextformat.py index cac24f30a08..a7e94302233 100644 --- a/opentelemetry-api/tests/propagators/test_global_httptextformat.py +++ b/opentelemetry-api/tests/propagators/test_global_httptextformat.py @@ -17,10 +17,7 @@ from opentelemetry import correlationcontext, trace from opentelemetry.propagators import extract, inject -from opentelemetry.trace.propagation import ( - get_span_from_context, - set_span_in_context, -) +from opentelemetry.trace import get_current_span, set_span_in_context def get_as_list( @@ -52,7 +49,7 @@ def test_propagation(self): correlations = correlationcontext.get_correlations(context=ctx) expected = {"key1": "val1", "key2": "val2"} self.assertEqual(correlations, expected) - span_context = get_span_from_context(context=ctx).get_context() + span_context = get_current_span(context=ctx).get_context() self.assertEqual(span_context.trace_id, self.TRACE_ID) self.assertEqual(span_context.span_id, self.SPAN_ID) diff --git a/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py index 11a8ecd56e9..5adc180d9fc 100644 --- a/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py @@ -16,11 +16,7 @@ import unittest from opentelemetry import trace -from opentelemetry.trace.propagation import ( - get_span_from_context, - set_span_in_context, - tracecontexthttptextformat, -) +from opentelemetry.trace.propagation import tracecontexthttptextformat FORMAT = tracecontexthttptextformat.TraceContextHTTPTextFormat() @@ -46,7 +42,7 @@ def test_no_traceparent_header(self): trace-id and parent-id that represents the current request. """ output = {} # type:typing.Dict[str, typing.List[str]] - span = get_span_from_context(FORMAT.extract(get_as_list, output)) + span = trace.get_current_span(FORMAT.extract(get_as_list, output)) self.assertIsInstance(span.get_context(), trace.SpanContext) def test_headers_with_tracestate(self): @@ -58,7 +54,7 @@ def test_headers_with_tracestate(self): span_id=format(self.SPAN_ID, "016x"), ) tracestate_value = "foo=1,bar=2,baz=3" - span_context = get_span_from_context( + span_context = trace.get_current_span( FORMAT.extract( get_as_list, { @@ -76,7 +72,7 @@ def test_headers_with_tracestate(self): output = {} # type:typing.Dict[str, str] span = trace.DefaultSpan(span_context) - ctx = set_span_in_context(span) + ctx = trace.set_span_in_context(span) FORMAT.inject(dict.__setitem__, output, ctx) self.assertEqual(output["traceparent"], traceparent_value) for pair in ["foo=1", "bar=2", "baz=3"]: @@ -102,7 +98,7 @@ 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 = get_span_from_context( + span = trace.get_current_span( FORMAT.extract( get_as_list, { @@ -133,7 +129,7 @@ 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 = get_span_from_context( + span = trace.get_current_span( FORMAT.extract( get_as_list, { @@ -158,7 +154,7 @@ def test_no_send_empty_tracestate(self): span = trace.DefaultSpan( trace.SpanContext(self.TRACE_ID, self.SPAN_ID, is_remote=False) ) - ctx = set_span_in_context(span) + ctx = trace.set_span_in_context(span) FORMAT.inject(dict.__setitem__, output, ctx) self.assertTrue("traceparent" in output) self.assertFalse("tracestate" in output) @@ -171,7 +167,7 @@ def test_format_not_supported(self): If the version cannot be parsed, return an invalid trace header. """ - span = get_span_from_context( + span = trace.get_current_span( FORMAT.extract( get_as_list, { @@ -188,14 +184,14 @@ def test_format_not_supported(self): def test_propagate_invalid_context(self): """Do not propagate invalid trace context.""" output = {} # type:typing.Dict[str, str] - ctx = set_span_in_context(trace.INVALID_SPAN) + ctx = trace.set_span_in_context(trace.INVALID_SPAN) FORMAT.inject(dict.__setitem__, output, context=ctx) self.assertFalse("traceparent" in output) def test_tracestate_empty_header(self): """Test tracestate with an additional empty header (should be ignored) """ - span = get_span_from_context( + span = trace.get_current_span( FORMAT.extract( get_as_list, { @@ -211,7 +207,7 @@ def test_tracestate_empty_header(self): def test_tracestate_header_with_trailing_comma(self): """Do not propagate invalid trace context. """ - span = get_span_from_context( + span = trace.get_current_span( FORMAT.extract( get_as_list, { @@ -235,7 +231,7 @@ def test_tracestate_keys(self): "foo-_*/bar=bar4", ] ) - span = get_span_from_context( + span = trace.get_current_span( FORMAT.extract( get_as_list, { diff --git a/opentelemetry-api/tests/trace/test_globals.py b/opentelemetry-api/tests/trace/test_globals.py index 4f38f99ee86..2f0f88fb280 100644 --- a/opentelemetry-api/tests/trace/test_globals.py +++ b/opentelemetry-api/tests/trace/test_globals.py @@ -1,7 +1,7 @@ import unittest from unittest.mock import patch -from opentelemetry import trace +from opentelemetry import context, trace class TestGlobals(unittest.TestCase): @@ -16,7 +16,25 @@ def test_get_tracer(self): """trace.get_tracer should proxy to the global tracer provider.""" trace.get_tracer("foo", "var") self._mock_tracer_provider.get_tracer.assert_called_with("foo", "var") - mock_provider = unittest.mock.Mock() trace.get_tracer("foo", "var", mock_provider) mock_provider.get_tracer.assert_called_with("foo", "var") + + +class TestTracer(unittest.TestCase): + def setUp(self): + self.tracer = trace.DefaultTracer() + + def test_get_current_span(self): + """DefaultTracer's start_span will also + be retrievable via get_current_span + """ + self.assertIs(trace.get_current_span(), None) + span = trace.DefaultSpan(trace.INVALID_SPAN_CONTEXT) + ctx = trace.set_span_in_context(span) + token = context.attach(ctx) + try: + self.assertIs(trace.get_current_span(), span) + finally: + context.detach(token) + self.assertIs(trace.get_current_span(), None) diff --git a/opentelemetry-api/tests/trace/test_tracer.py b/opentelemetry-api/tests/trace/test_tracer.py index 4fe3d20f78c..1eb15062305 100644 --- a/opentelemetry-api/tests/trace/test_tracer.py +++ b/opentelemetry-api/tests/trace/test_tracer.py @@ -21,10 +21,6 @@ class TestTracer(unittest.TestCase): def setUp(self): self.tracer = trace.DefaultTracer() - def test_get_current_span(self): - span = self.tracer.get_current_span() - self.assertIsInstance(span, trace.Span) - def test_start_span(self): with self.tracer.start_span("") as span: self.assertIsInstance(span, trace.Span) diff --git a/opentelemetry-sdk/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index d8eb23ab525..713580dbd6b 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -6,6 +6,8 @@ ([#751](https://github.com/open-telemetry/opentelemetry-python/pull/751)) - Rename Measure to ValueRecorder in metrics ([#761](https://github.com/open-telemetry/opentelemetry-python/pull/761)) +- Adding trace.get_current_span, Removing Tracer.get_current_span + ([#552](https://github.com/open-telemetry/opentelemetry-python/pull/552)) - bugfix: byte type attributes are decoded before adding to attributes dict ([#775](https://github.com/open-telemetry/opentelemetry-python/pull/775)) - Rename Observer to ValueObserver diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index da4972dbe0b..c0e204f360d 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -241,7 +241,7 @@ class Span(trace_api.Span): Args: name: The name of the operation this span represents context: The immutable span context - parent: This span's parent's `SpanContext`, or + parent: This span's parent's `opentelemetry.trace.SpanContext`, or null if this is a root span sampler: The sampler used to create this span trace_config: TODO @@ -611,9 +611,6 @@ def __init__( self.source = source self.instrumentation_info = instrumentation_info - def get_current_span(self): - return self.source.get_current_span() - def start_as_current_span( self, name: str, @@ -636,7 +633,7 @@ def start_span( # pylint: disable=too-many-locals set_status_on_exception: bool = True, ) -> trace_api.Span: if parent is Tracer.CURRENT_SPAN: - parent = self.get_current_span() + parent = trace_api.get_current_span() parent_context = parent if isinstance(parent_context, trace_api.Span): @@ -768,10 +765,6 @@ def get_tracer( ), ) - @staticmethod - def get_current_span() -> Span: - return context_api.get_value(SPAN_KEY) # type: ignore - def add_span_processor(self, span_processor: SpanProcessor) -> None: """Registers a new :class:`SpanProcessor` for this `TracerProvider`. diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/propagation/b3_format.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/propagation/b3_format.py index c0a080e631b..3a9722bc36c 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/propagation/b3_format.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/propagation/b3_format.py @@ -16,10 +16,6 @@ import opentelemetry.trace as trace from opentelemetry.context import Context -from opentelemetry.trace.propagation import ( - get_span_from_context, - set_span_in_context, -) from opentelemetry.trace.propagation.httptextformat import ( Getter, HTTPTextFormat, @@ -72,7 +68,7 @@ def extract( elif len(fields) == 4: trace_id, span_id, sampled, _ = fields else: - return set_span_in_context(trace.INVALID_SPAN) + return trace.set_span_in_context(trace.INVALID_SPAN) else: trace_id = ( _extract_first_element( @@ -106,7 +102,7 @@ def extract( # header is set to allow. if sampled in self._SAMPLE_PROPAGATE_VALUES or flags == "1": options |= trace.TraceFlags.SAMPLED - return set_span_in_context( + return trace.set_span_in_context( trace.DefaultSpan( trace.SpanContext( # trace an span ids are encoded in hex, so must be converted @@ -125,7 +121,11 @@ def inject( carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: - span = get_span_from_context(context=context) + span = trace.get_current_span(context=context) + + if span is None: + return + sampled = (trace.TraceFlags.SAMPLED & span.context.trace_flags) != 0 set_in_carrier( carrier, self.TRACE_ID_KEY, format_trace_id(span.context.trace_id), diff --git a/opentelemetry-sdk/tests/trace/propagation/test_b3_format.py b/opentelemetry-sdk/tests/trace/propagation/test_b3_format.py index e6c644dcdb6..a5bd1baaa48 100644 --- a/opentelemetry-sdk/tests/trace/propagation/test_b3_format.py +++ b/opentelemetry-sdk/tests/trace/propagation/test_b3_format.py @@ -17,10 +17,7 @@ import opentelemetry.sdk.trace as trace import opentelemetry.sdk.trace.propagation.b3_format as b3_format import opentelemetry.trace as trace_api -from opentelemetry.trace.propagation import ( - get_span_from_context, - set_span_in_context, -) +from opentelemetry.context import get_current FORMAT = b3_format.B3Format() @@ -33,7 +30,7 @@ def get_as_list(dict_object, key): def get_child_parent_new_carrier(old_carrier): ctx = FORMAT.extract(get_as_list, old_carrier) - parent_context = get_span_from_context(ctx).get_context() + parent_context = trace_api.get_current_span(ctx).get_context() parent = trace.Span("parent", parent_context) child = trace.Span( @@ -49,7 +46,7 @@ def get_child_parent_new_carrier(old_carrier): ) new_carrier = {} - ctx = set_span_in_context(child) + ctx = trace_api.set_span_in_context(child) FORMAT.inject(dict.__setitem__, new_carrier, context=ctx) return child, parent, new_carrier @@ -233,7 +230,7 @@ def test_invalid_single_header(self): """ carrier = {FORMAT.SINGLE_HEADER_KEY: "0-1-2-3-4-5-6-7"} ctx = FORMAT.extract(get_as_list, carrier) - span_context = get_span_from_context(ctx).get_context() + span_context = trace_api.get_current_span(ctx).get_context() self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID) self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID) @@ -245,7 +242,7 @@ def test_missing_trace_id(self): } ctx = FORMAT.extract(get_as_list, carrier) - span_context = get_span_from_context(ctx).get_context() + span_context = trace_api.get_current_span(ctx).get_context() self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID) def test_missing_span_id(self): @@ -256,5 +253,12 @@ def test_missing_span_id(self): } ctx = FORMAT.extract(get_as_list, carrier) - span_context = get_span_from_context(ctx).get_context() + span_context = trace_api.get_current_span(ctx).get_context() self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID) + + @staticmethod + def test_inject_empty_context(): + """If the current context has no span, don't add headers""" + new_carrier = {} + FORMAT.inject(dict.__setitem__, new_carrier, get_current()) + assert len(new_carrier) == 0 diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index 586264891e9..cac22f8fbae 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -211,26 +211,10 @@ def test_span_processor_for_source(self): span2.span_processor, tracer_provider._active_span_processor ) - def test_get_current_span_multiple_tracers(self): - """In the case where there are multiple tracers, - get_current_span will return the same active span - for both tracers. - """ - tracer_1 = new_tracer() - tracer_2 = new_tracer() - root = tracer_1.start_span("root") - with tracer_1.use_span(root, True): - self.assertIs(tracer_1.get_current_span(), root) - self.assertIs(tracer_2.get_current_span(), root) - - # outside of the loop, both should not reference a span. - self.assertIs(tracer_1.get_current_span(), None) - self.assertIs(tracer_2.get_current_span(), None) - def test_start_span_implicit(self): tracer = new_tracer() - self.assertIsNone(tracer.get_current_span()) + self.assertIsNone(trace_api.get_current_span()) root = tracer.start_span("root") self.assertIsNotNone(root.start_time) @@ -238,7 +222,7 @@ def test_start_span_implicit(self): self.assertEqual(root.kind, trace_api.SpanKind.INTERNAL) with tracer.use_span(root, True): - self.assertIs(tracer.get_current_span(), root) + self.assertIs(trace_api.get_current_span(), root) with tracer.start_span( "child", kind=trace_api.SpanKind.CLIENT @@ -265,11 +249,11 @@ def test_start_span_implicit(self): ) # Verify start_span() did not set the current span. - self.assertIs(tracer.get_current_span(), root) + self.assertIs(trace_api.get_current_span(), root) self.assertIsNotNone(child.end_time) - self.assertIsNone(tracer.get_current_span()) + self.assertIsNone(trace_api.get_current_span()) self.assertIsNotNone(root.end_time) def test_start_span_explicit(self): @@ -282,7 +266,7 @@ def test_start_span_explicit(self): trace_flags=trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED), ) - self.assertIsNone(tracer.get_current_span()) + self.assertIsNone(trace_api.get_current_span()) root = tracer.start_span("root") self.assertIsNotNone(root.start_time) @@ -290,7 +274,7 @@ def test_start_span_explicit(self): # Test with the implicit root span with tracer.use_span(root, True): - self.assertIs(tracer.get_current_span(), root) + self.assertIs(trace_api.get_current_span(), root) with tracer.start_span("stepchild", other_parent) as child: # The child's parent should be the one passed in, @@ -316,30 +300,30 @@ def test_start_span_explicit(self): ) # Verify start_span() did not set the current span. - self.assertIs(tracer.get_current_span(), root) + self.assertIs(trace_api.get_current_span(), root) # Verify ending the child did not set the current span. - self.assertIs(tracer.get_current_span(), root) + self.assertIs(trace_api.get_current_span(), root) self.assertIsNotNone(child.end_time) def test_start_as_current_span_implicit(self): tracer = new_tracer() - self.assertIsNone(tracer.get_current_span()) + self.assertIsNone(trace_api.get_current_span()) with tracer.start_as_current_span("root") as root: - self.assertIs(tracer.get_current_span(), root) + self.assertIs(trace_api.get_current_span(), root) with tracer.start_as_current_span("child") as child: - self.assertIs(tracer.get_current_span(), child) + self.assertIs(trace_api.get_current_span(), child) self.assertIs(child.parent, root.get_context()) # After exiting the child's scope the parent should become the # current span again. - self.assertIs(tracer.get_current_span(), root) + self.assertIs(trace_api.get_current_span(), root) self.assertIsNotNone(child.end_time) - self.assertIsNone(tracer.get_current_span()) + self.assertIsNone(trace_api.get_current_span()) self.assertIsNotNone(root.end_time) def test_start_as_current_span_explicit(self): @@ -351,11 +335,11 @@ def test_start_as_current_span_explicit(self): is_remote=False, ) - self.assertIsNone(tracer.get_current_span()) + self.assertIsNone(trace_api.get_current_span()) # Test with the implicit root span with tracer.start_as_current_span("root") as root: - self.assertIs(tracer.get_current_span(), root) + self.assertIs(trace_api.get_current_span(), root) self.assertIsNotNone(root.start_time) self.assertIsNone(root.end_time) @@ -366,14 +350,14 @@ def test_start_as_current_span_explicit(self): # The child should become the current span as usual, but its # parent should be the one passed in, not the # previously-current span. - self.assertIs(tracer.get_current_span(), child) + self.assertIs(trace_api.get_current_span(), child) self.assertNotEqual(child.parent, root) self.assertIs(child.parent, other_parent) # After exiting the child's scope the last span on the stack should # become current, not the child's parent. - self.assertNotEqual(tracer.get_current_span(), other_parent) - self.assertIs(tracer.get_current_span(), root) + self.assertNotEqual(trace_api.get_current_span(), other_parent) + self.assertIs(trace_api.get_current_span(), root) self.assertIsNotNone(child.end_time) def test_explicit_span_resource(self): @@ -557,7 +541,7 @@ def test_sampling_attributes(self): self.assertEqual(root.attributes["attr-in-both"], "decision-attr") def test_events(self): - self.assertIsNone(self.tracer.get_current_span()) + self.assertIsNone(trace_api.get_current_span()) with self.tracer.start_as_current_span("root") as root: # only event name @@ -613,7 +597,7 @@ def event_formatter(): self.assertEqual(root.events[4].timestamp, now) def test_invalid_event_attributes(self): - self.assertIsNone(self.tracer.get_current_span()) + self.assertIsNone(trace_api.get_current_span()) with self.tracer.start_as_current_span("root") as root: root.add_event("event0", {"attr1": True, "attr2": ["hi", False]}) diff --git a/tests/util/src/opentelemetry/test/mock_httptextformat.py b/tests/util/src/opentelemetry/test/mock_httptextformat.py index 1d4b1d5d511..76165c3e4b2 100644 --- a/tests/util/src/opentelemetry/test/mock_httptextformat.py +++ b/tests/util/src/opentelemetry/test/mock_httptextformat.py @@ -15,11 +15,7 @@ import typing from opentelemetry import trace -from opentelemetry.context import Context -from opentelemetry.trace.propagation import ( - get_span_from_context, - set_span_in_context, -) +from opentelemetry.context import Context, get_current from opentelemetry.trace.propagation.httptextformat import ( Getter, HTTPTextFormat, @@ -28,6 +24,30 @@ ) +class NOOPHTTPTextFormat(HTTPTextFormat): + """A propagator that does not extract nor inject. + + This class is useful for catching edge cases assuming + a SpanContext will always be present. + """ + + def extract( + self, + get_from_carrier: Getter[HTTPTextFormatT], + carrier: HTTPTextFormatT, + context: typing.Optional[Context] = None, + ) -> Context: + return get_current() + + def inject( + self, + set_in_carrier: Setter[HTTPTextFormatT], + carrier: HTTPTextFormatT, + context: typing.Optional[Context] = None, + ) -> None: + return None + + class MockHTTPTextFormat(HTTPTextFormat): """Mock propagator for testing purposes.""" @@ -44,9 +64,9 @@ def extract( span_id_list = get_from_carrier(carrier, self.SPAN_ID_KEY) if not trace_id_list or not span_id_list: - return set_span_in_context(trace.INVALID_SPAN) + return trace.set_span_in_context(trace.INVALID_SPAN) - return set_span_in_context( + return trace.set_span_in_context( trace.DefaultSpan( trace.SpanContext( trace_id=int(trace_id_list[0]), @@ -62,7 +82,7 @@ def inject( carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: - span = get_span_from_context(context) + span = trace.get_current_span(context) set_in_carrier( carrier, self.TRACE_ID_KEY, str(span.get_context().trace_id) )