1# Copyright The OpenTelemetry Authors
2# SPDX-License-Identifier: Apache-2.0
3
4"""
5The OpenTelemetry tracing API describes the classes used to generate
6distributed traces.
7
8The :class:`.Tracer` class controls access to the execution context, and
9manages span creation. Each operation in a trace is represented by a
10:class:`.Span`, which records the start, end time, and metadata associated with
11the operation.
12
13This module provides abstract (i.e. unimplemented) classes required for
14tracing, and a concrete no-op :class:`.NonRecordingSpan` that allows applications
15to use the API package alone without a supporting implementation.
16
17To get a tracer, you need to provide the package name from which you are
18calling the tracer APIs to OpenTelemetry by calling `TracerProvider.get_tracer`
19with the calling module name and the version of your package.
20
21The tracer supports creating spans that are "attached" or "detached" from the
22context. New spans are "attached" to the context in that they are
23created as children of the currently active span, and the newly-created span
24can optionally become the new active span::
25
26 from opentelemetry import trace
27
28 tracer = trace.get_tracer(__name__)
29
30 # Create a new root span, set it as the current span in context
31 with tracer.start_as_current_span("parent"):
32 # Attach a new child and update the current span
33 with tracer.start_as_current_span("child"):
34 do_work():
35 # Close child span, set parent as current
36 # Close parent span, set default span as current
37
38When creating a span that's "detached" from the context the active span doesn't
39change, and the caller is responsible for managing the span's lifetime::
40
41 # Explicit parent span assignment is done via the Context
42 from opentelemetry.trace import set_span_in_context
43
44 context = set_span_in_context(parent)
45 child = tracer.start_span("child", context=context)
46
47 try:
48 do_work(span=child)
49 finally:
50 child.end()
51
52Applications should generally use a single global TracerProvider, and use
53either implicit or explicit context propagation consistently throughout.
54
55.. versionadded:: 0.1.0
56.. versionchanged:: 0.3.0
57 `TracerProvider` was introduced and the global ``tracer`` getter was
58 replaced by ``tracer_provider``.
59.. versionchanged:: 0.5.0
60 ``tracer_provider`` was replaced by `get_tracer_provider`,
61 ``set_preferred_tracer_provider_implementation`` was replaced by
62 `set_tracer_provider`.
63"""
64
65import os
66from abc import ABC, abstractmethod
67from collections.abc import Iterator, Sequence
68from enum import Enum
69from logging import getLogger
70from typing import cast
71
72from typing_extensions import deprecated
73
74from opentelemetry import context as context_api
75from opentelemetry.attributes import BoundedAttributes
76from opentelemetry.context.context import Context
77from opentelemetry.environment_variables import OTEL_PYTHON_TRACER_PROVIDER
78from opentelemetry.trace.propagation import (
79 _SPAN_KEY,
80 get_current_span,
81 set_span_in_context,
82)
83from opentelemetry.trace.span import (
84 DEFAULT_TRACE_OPTIONS,
85 DEFAULT_TRACE_STATE,
86 INVALID_SPAN,
87 INVALID_SPAN_CONTEXT,
88 INVALID_SPAN_ID,
89 INVALID_TRACE_ID,
90 NonRecordingSpan,
91 Span,
92 SpanContext,
93 TraceFlags,
94 TraceState,
95 format_span_id,
96 format_trace_id,
97)
98from opentelemetry.trace.status import Status, StatusCode
99from opentelemetry.util import types
100from opentelemetry.util._decorator import _agnosticcontextmanager
101from opentelemetry.util._once import Once
102from opentelemetry.util._providers import _load_provider
103
104logger = getLogger(__name__)
105
106
107class _LinkBase(ABC):
108 def __init__(self, context: "SpanContext") -> None:
109 self._context = context
110
111 @property
112 def context(self) -> "SpanContext":
113 return self._context
114
115 @property
116 @abstractmethod
117 def attributes(self) -> types.Attributes:
118 pass
119
120
121class Link(_LinkBase):
122 """A link to a `Span`. The attributes of a Link are immutable.
123
124 Args:
125 context: `SpanContext` of the `Span` to link to.
126 attributes: Link's attributes.
127 """
128
129 def __init__(
130 self,
131 context: "SpanContext",
132 attributes: types.Attributes = None,
133 ) -> None:
134 super().__init__(context)
135 self._attributes = attributes
136
137 @property
138 def attributes(self) -> types.Attributes:
139 return self._attributes
140
141 @property
142 def dropped_attributes(self) -> int:
143 if isinstance(self._attributes, BoundedAttributes):
144 return self._attributes.dropped
145 return 0
146
147
148_Links = Sequence[Link] | None
149
150
151class SpanKind(Enum):
152 """Specifies additional details on how this span relates to its parent span.
153
154 Note that this enumeration is experimental and likely to change. See
155 https://github.com/open-telemetry/opentelemetry-specification/pull/226.
156 """
157
158 #: Default value. Indicates that the span is used internally in the
159 # application.
160 INTERNAL = 0
161
162 #: Indicates that the span describes an operation that handles a remote
163 # request.
164 SERVER = 1
165
166 #: Indicates that the span describes a request to some remote service.
167 CLIENT = 2
168
169 #: Indicates that the span describes a producer sending a message to a
170 #: broker. Unlike client and server, there is usually no direct critical
171 #: path latency relationship between producer and consumer spans.
172 PRODUCER = 3
173
174 #: Indicates that the span describes a consumer receiving a message from a
175 #: broker. Unlike client and server, there is usually no direct critical
176 #: path latency relationship between producer and consumer spans.
177 CONSUMER = 4
178
179
180class TracerProvider(ABC):
181 @abstractmethod
182 def get_tracer(
183 self,
184 instrumenting_module_name: str,
185 instrumenting_library_version: str | None = None,
186 schema_url: str | None = None,
187 attributes: types.Attributes = None,
188 ) -> "Tracer":
189 """Returns a `Tracer` for use by the given instrumentation library.
190
191 For any two calls it is undefined whether the same or different
192 `Tracer` instances are returned, even for different library names.
193
194 This function may return different `Tracer` types (e.g. a no-op tracer
195 vs. a functional tracer).
196
197 Args:
198 instrumenting_module_name: The uniquely identifiable name for instrumentation
199 scope, such as instrumentation library, package, module or class name.
200 ``__name__`` should be avoided as this can result in
201 different tracer names if the tracers are in different files.
202 It is better to use a fixed string that can be imported where
203 needed and used consistently as the name of the tracer.
204
205 This should *not* be the name of the module that is
206 instrumented but the name of the module doing the instrumentation.
207 E.g., instead of ``"requests"``, use
208 ``"opentelemetry.instrumentation.requests"``.
209
210 instrumenting_library_version: Optional. The version string of the
211 instrumenting library. Usually this should be the same as
212 ``importlib.metadata.version(instrumenting_library_name)``.
213
214 schema_url: Optional. Specifies the Schema URL of the emitted telemetry.
215 attributes: Optional. Specifies the attributes of the emitted telemetry.
216 """
217
218
219class NoOpTracerProvider(TracerProvider):
220 """The default TracerProvider, used when no implementation is available.
221
222 All operations are no-op.
223 """
224
225 def get_tracer(
226 self,
227 instrumenting_module_name: str,
228 instrumenting_library_version: str | None = None,
229 schema_url: str | None = None,
230 attributes: types.Attributes = None,
231 ) -> "Tracer":
232 # pylint:disable=no-self-use,unused-argument
233 return NoOpTracer()
234
235
236@deprecated("You should use NoOpTracerProvider. Deprecated since version 1.9.0.")
237class _DefaultTracerProvider(NoOpTracerProvider):
238 """The default TracerProvider, used when no implementation is available.
239
240 All operations are no-op.
241 """
242
243
244class ProxyTracerProvider(TracerProvider):
245 def get_tracer(
246 self,
247 instrumenting_module_name: str,
248 instrumenting_library_version: str | None = None,
249 schema_url: str | None = None,
250 attributes: types.Attributes = None,
251 ) -> "Tracer":
252 if _TRACER_PROVIDER:
253 return _TRACER_PROVIDER.get_tracer(
254 instrumenting_module_name,
255 instrumenting_library_version,
256 schema_url,
257 attributes,
258 )
259 return ProxyTracer(
260 instrumenting_module_name,
261 instrumenting_library_version,
262 schema_url,
263 attributes,
264 )
265
266
267class Tracer(ABC):
268 """Handles span creation and in-process context propagation.
269
270 This class provides methods for manipulating the context, creating spans,
271 and controlling spans' lifecycles.
272 """
273
274 @abstractmethod
275 def start_span(
276 self,
277 name: str,
278 context: Context | None = None,
279 kind: SpanKind = SpanKind.INTERNAL,
280 attributes: types.Attributes = None,
281 links: _Links = None,
282 start_time: int | None = None,
283 record_exception: bool = True,
284 set_status_on_exception: bool = True,
285 ) -> "Span":
286 """Starts a span.
287
288 Create a new span. Start the span without setting it as the current
289 span in the context. To start the span and use the context in a single
290 method, see :meth:`start_as_current_span`.
291
292 By default the current span in the context will be used as parent, but an
293 explicit context can also be specified, by passing in a `Context` containing
294 a current `Span`. If there is no current span in the global `Context` or in
295 the specified context, the created span will be a root span.
296
297 The span can be used as a context manager. On exiting the context manager,
298 the span's end() method will be called.
299
300 Example::
301
302 # trace.get_current_span() will be used as the implicit parent.
303 # If none is found, the created span will be a root instance.
304 with tracer.start_span("one") as child:
305 child.add_event("child's event")
306
307 Args:
308 name: The name of the span to be created.
309 context: An optional Context containing the span's parent. Defaults to the
310 global context.
311 kind: The span's kind (relationship to parent). Note that is
312 meaningful even if there is no parent.
313 attributes: The span's attributes.
314 links: Links span to other spans
315 start_time: Sets the start time of a span
316 record_exception: Whether to record any exceptions raised within the
317 context as error event on the span.
318 set_status_on_exception: Only relevant if the returned span is used
319 in a with/context manager. Defines whether the span status will
320 be automatically set to ERROR when an uncaught exception is
321 raised in the span with block. The span status won't be set by
322 this mechanism if it was previously set manually.
323
324 Returns:
325 The newly-created span.
326 """
327
328 @_agnosticcontextmanager
329 @abstractmethod
330 def start_as_current_span(
331 self,
332 name: str,
333 context: Context | None = None,
334 kind: SpanKind = SpanKind.INTERNAL,
335 attributes: types.Attributes = None,
336 links: _Links = None,
337 start_time: int | None = None,
338 record_exception: bool = True,
339 set_status_on_exception: bool = True,
340 end_on_exit: bool = True,
341 ) -> Iterator["Span"]:
342 """Context manager for creating a new span and set it
343 as the current span in this tracer's context.
344
345 Exiting the context manager will call the span's end method,
346 as well as return the current span to its previous value by
347 returning to the previous context.
348
349 Example::
350
351 with tracer.start_as_current_span("one") as parent:
352 parent.add_event("parent's event")
353 with tracer.start_as_current_span("two") as child:
354 child.add_event("child's event")
355 trace.get_current_span() # returns child
356 trace.get_current_span() # returns parent
357 trace.get_current_span() # returns previously active span
358
359 This is a convenience method for creating spans attached to the
360 tracer's context. Applications that need more control over the span
361 lifetime should use :meth:`start_span` instead. For example::
362
363 with tracer.start_as_current_span(name) as span:
364 do_work()
365
366 is equivalent to::
367
368 span = tracer.start_span(name)
369 with opentelemetry.trace.use_span(span, end_on_exit=True):
370 do_work()
371
372 This can also be used as a decorator::
373
374 @tracer.start_as_current_span("name")
375 def function(): ...
376
377
378 function()
379
380 Args:
381 name: The name of the span to be created.
382 context: An optional Context containing the span's parent. Defaults to the
383 global context.
384 kind: The span's kind (relationship to parent). Note that is
385 meaningful even if there is no parent.
386 attributes: The span's attributes.
387 links: Links span to other spans
388 start_time: Sets the start time of a span
389 record_exception: Whether to record any exceptions raised within the
390 context as error event on the span.
391 set_status_on_exception: Only relevant if the returned span is used
392 in a with/context manager. Defines whether the span status will
393 be automatically set to ERROR when an uncaught exception is
394 raised in the span with block. The span status won't be set by
395 this mechanism if it was previously set manually.
396 end_on_exit: Whether to end the span automatically when leaving the
397 context manager.
398
399 Yields:
400 The newly-created span.
401 """
402
403
404class ProxyTracer(Tracer):
405 # pylint: disable=W0222,signature-differs
406 def __init__(
407 self,
408 instrumenting_module_name: str,
409 instrumenting_library_version: str | None = None,
410 schema_url: str | None = None,
411 attributes: types.Attributes = None,
412 ):
413 self._instrumenting_module_name = instrumenting_module_name
414 self._instrumenting_library_version = instrumenting_library_version
415 self._schema_url = schema_url
416 self._attributes = attributes
417 self._real_tracer: Tracer | None = None
418 self._noop_tracer = NoOpTracer()
419
420 @property
421 def _tracer(self) -> Tracer:
422 if self._real_tracer:
423 return self._real_tracer
424
425 if _TRACER_PROVIDER:
426 self._real_tracer = _TRACER_PROVIDER.get_tracer(
427 self._instrumenting_module_name,
428 self._instrumenting_library_version,
429 self._schema_url,
430 self._attributes,
431 )
432 return self._real_tracer
433 return self._noop_tracer
434
435 def start_span(self, *args, **kwargs) -> Span: # type: ignore
436 return self._tracer.start_span(*args, **kwargs) # type: ignore
437
438 @_agnosticcontextmanager # type: ignore
439 def start_as_current_span(self, *args, **kwargs) -> Iterator[Span]:
440 with self._tracer.start_as_current_span(*args, **kwargs) as span: # type: ignore
441 yield span
442
443
444class NoOpTracer(Tracer):
445 """The default Tracer, used when no Tracer implementation is available.
446
447 All operations are no-op.
448 """
449
450 def start_span(
451 self,
452 name: str,
453 context: Context | None = None,
454 kind: SpanKind = SpanKind.INTERNAL,
455 attributes: types.Attributes = None,
456 links: _Links = None,
457 start_time: int | None = None,
458 record_exception: bool = True,
459 set_status_on_exception: bool = True,
460 ) -> "Span":
461 current_span = get_current_span(context)
462 if isinstance(current_span, NonRecordingSpan):
463 return current_span
464 parent_span_context = current_span.get_span_context()
465 if parent_span_context is not None and not isinstance(parent_span_context, SpanContext):
466 logger.warning(
467 "Invalid span context for %s: %s",
468 current_span,
469 parent_span_context,
470 )
471 return INVALID_SPAN
472
473 return NonRecordingSpan(context=parent_span_context)
474
475 @_agnosticcontextmanager
476 def start_as_current_span(
477 self,
478 name: str,
479 context: Context | None = None,
480 kind: SpanKind = SpanKind.INTERNAL,
481 attributes: types.Attributes = None,
482 links: _Links = None,
483 start_time: int | None = None,
484 record_exception: bool = True,
485 set_status_on_exception: bool = True,
486 end_on_exit: bool = True,
487 ) -> Iterator["Span"]:
488 span = self.start_span(
489 name=name,
490 context=context,
491 kind=kind,
492 attributes=attributes,
493 links=links,
494 start_time=start_time,
495 record_exception=record_exception,
496 set_status_on_exception=set_status_on_exception,
497 )
498 with use_span(
499 span,
500 end_on_exit=end_on_exit,
501 record_exception=record_exception,
502 set_status_on_exception=set_status_on_exception,
503 ) as span:
504 yield span
505
506
507@deprecated("You should use NoOpTracer. Deprecated since version 1.9.0.")
508class _DefaultTracer(NoOpTracer):
509 """The default Tracer, used when no Tracer implementation is available.
510
511 All operations are no-op.
512 """
513
514
515_TRACER_PROVIDER_SET_ONCE = Once()
516_TRACER_PROVIDER: TracerProvider | None = None
517_PROXY_TRACER_PROVIDER = ProxyTracerProvider()
518
519
520def get_tracer(
521 instrumenting_module_name: str,
522 instrumenting_library_version: str | None = None,
523 tracer_provider: TracerProvider | None = None,
524 schema_url: str | None = None,
525 attributes: types.Attributes = None,
526) -> "Tracer":
527 """Returns a `Tracer` for use by the given instrumentation library.
528
529 This function is a convenience wrapper for
530 opentelemetry.trace.TracerProvider.get_tracer.
531
532 If tracer_provider is omitted the current configured one is used.
533 """
534 if tracer_provider is None:
535 tracer_provider = get_tracer_provider()
536 return tracer_provider.get_tracer(
537 instrumenting_module_name,
538 instrumenting_library_version,
539 schema_url,
540 attributes,
541 )
542
543
544def _set_tracer_provider(tracer_provider: TracerProvider, log: bool) -> None:
545 def set_tp() -> None:
546 global _TRACER_PROVIDER # pylint: disable=global-statement
547 _TRACER_PROVIDER = tracer_provider
548
549 did_set = _TRACER_PROVIDER_SET_ONCE.do_once(set_tp)
550
551 if log and not did_set:
552 logger.warning("Overriding of current TracerProvider is not allowed")
553
554
555def set_tracer_provider(tracer_provider: TracerProvider) -> None:
556 """Sets the current global :class:`~.TracerProvider` object.
557
558 This can only be done once, a warning will be logged if any further attempt
559 is made.
560 """
561 _set_tracer_provider(tracer_provider, log=True)
562
563
564def get_tracer_provider() -> TracerProvider:
565 """Gets the current global :class:`~.TracerProvider` object."""
566 if _TRACER_PROVIDER is None:
567 # if a global tracer provider has not been set either via code or env
568 # vars, return a proxy tracer provider
569 if OTEL_PYTHON_TRACER_PROVIDER not in os.environ:
570 return _PROXY_TRACER_PROVIDER
571
572 tracer_provider: TracerProvider = _load_provider(OTEL_PYTHON_TRACER_PROVIDER, "tracer_provider")
573 _set_tracer_provider(tracer_provider, log=False)
574 # _TRACER_PROVIDER will have been set by one thread
575 return cast("TracerProvider", _TRACER_PROVIDER)
576
577
578@_agnosticcontextmanager
579def use_span(
580 span: Span,
581 end_on_exit: bool = False,
582 record_exception: bool = True,
583 set_status_on_exception: bool = True,
584) -> Iterator[Span]:
585 """Takes a non-active span and activates it in the current context.
586
587 Args:
588 span: The span that should be activated in the current context.
589 end_on_exit: Whether to end the span automatically when leaving the
590 context manager scope.
591 record_exception: Whether to record any exceptions raised within the
592 context as error event on the span.
593 set_status_on_exception: Only relevant if the returned span is used
594 in a with/context manager. Defines whether the span status will
595 be automatically set to ERROR when an uncaught exception is
596 raised in the span with block. The span status won't be set by
597 this mechanism if it was previously set manually.
598 """
599 try:
600 token = context_api.attach(context_api.set_value(_SPAN_KEY, span))
601 try:
602 yield span
603 finally:
604 context_api.detach(token)
605
606 # Record only exceptions that inherit Exception class but not BaseException, because
607 # classes that directly inherit BaseException are not technically errors, e.g. GeneratorExit.
608 # See https://github.com/open-telemetry/opentelemetry-python/issues/4484
609 except Exception as exc: # pylint: disable=broad-exception-caught
610 if isinstance(span, Span) and span.is_recording():
611 # Record the exception as an event
612 if record_exception:
613 span.record_exception(exc)
614
615 # Set status in case exception was raised
616 if set_status_on_exception:
617 span.set_status(
618 Status(
619 status_code=StatusCode.ERROR,
620 description=f"{type(exc).__name__}: {exc}",
621 )
622 )
623
624 # This causes parent spans to set their status to ERROR and to record
625 # an exception as an event if a child span raises an exception even if
626 # such child span was started with both record_exception and
627 # set_status_on_exception attributes set to False.
628 raise
629
630 finally:
631 if end_on_exit:
632 span.end()
633
634
635__all__ = [
636 "DEFAULT_TRACE_OPTIONS",
637 "DEFAULT_TRACE_STATE",
638 "INVALID_SPAN",
639 "INVALID_SPAN_CONTEXT",
640 "INVALID_SPAN_ID",
641 "INVALID_TRACE_ID",
642 "Link",
643 "NonRecordingSpan",
644 "Span",
645 "SpanContext",
646 "SpanKind",
647 "Status",
648 "StatusCode",
649 "TraceFlags",
650 "TraceState",
651 "Tracer",
652 "TracerProvider",
653 "format_span_id",
654 "format_trace_id",
655 "get_current_span",
656 "get_tracer",
657 "get_tracer_provider",
658 "set_span_in_context",
659 "set_tracer_provider",
660 "use_span",
661]