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 = 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 = None,
231 ) -> "Tracer":
232 # pylint:disable=no-self-use,unused-argument
233 return NoOpTracer()
234
235
236@deprecated(
237 "You should use NoOpTracerProvider. Deprecated since version 1.9.0."
238)
239class _DefaultTracerProvider(NoOpTracerProvider):
240 """The default TracerProvider, used when no implementation is available.
241
242 All operations are no-op.
243 """
244
245
246class ProxyTracerProvider(TracerProvider):
247 def get_tracer(
248 self,
249 instrumenting_module_name: str,
250 instrumenting_library_version: str | None = None,
251 schema_url: str | None = None,
252 attributes: types.Attributes | None = None,
253 ) -> "Tracer":
254 if _TRACER_PROVIDER:
255 return _TRACER_PROVIDER.get_tracer(
256 instrumenting_module_name,
257 instrumenting_library_version,
258 schema_url,
259 attributes,
260 )
261 return ProxyTracer(
262 instrumenting_module_name,
263 instrumenting_library_version,
264 schema_url,
265 attributes,
266 )
267
268
269class Tracer(ABC):
270 """Handles span creation and in-process context propagation.
271
272 This class provides methods for manipulating the context, creating spans,
273 and controlling spans' lifecycles.
274 """
275
276 @abstractmethod
277 def start_span(
278 self,
279 name: str,
280 context: Context | None = None,
281 kind: SpanKind = SpanKind.INTERNAL,
282 attributes: types.Attributes = None,
283 links: _Links = None,
284 start_time: int | None = None,
285 record_exception: bool = True,
286 set_status_on_exception: bool = True,
287 ) -> "Span":
288 """Starts a span.
289
290 Create a new span. Start the span without setting it as the current
291 span in the context. To start the span and use the context in a single
292 method, see :meth:`start_as_current_span`.
293
294 By default the current span in the context will be used as parent, but an
295 explicit context can also be specified, by passing in a `Context` containing
296 a current `Span`. If there is no current span in the global `Context` or in
297 the specified context, the created span will be a root span.
298
299 The span can be used as a context manager. On exiting the context manager,
300 the span's end() method will be called.
301
302 Example::
303
304 # trace.get_current_span() will be used as the implicit parent.
305 # If none is found, the created span will be a root instance.
306 with tracer.start_span("one") as child:
307 child.add_event("child's event")
308
309 Args:
310 name: The name of the span to be created.
311 context: An optional Context containing the span's parent. Defaults to the
312 global context.
313 kind: The span's kind (relationship to parent). Note that is
314 meaningful even if there is no parent.
315 attributes: The span's attributes.
316 links: Links span to other spans
317 start_time: Sets the start time of a span
318 record_exception: Whether to record any exceptions raised within the
319 context as error event on the span.
320 set_status_on_exception: Only relevant if the returned span is used
321 in a with/context manager. Defines whether the span status will
322 be automatically set to ERROR when an uncaught exception is
323 raised in the span with block. The span status won't be set by
324 this mechanism if it was previously set manually.
325
326 Returns:
327 The newly-created span.
328 """
329
330 @_agnosticcontextmanager
331 @abstractmethod
332 def start_as_current_span(
333 self,
334 name: str,
335 context: Context | None = None,
336 kind: SpanKind = SpanKind.INTERNAL,
337 attributes: types.Attributes = None,
338 links: _Links = None,
339 start_time: int | None = None,
340 record_exception: bool = True,
341 set_status_on_exception: bool = True,
342 end_on_exit: bool = True,
343 ) -> Iterator["Span"]:
344 """Context manager for creating a new span and set it
345 as the current span in this tracer's context.
346
347 Exiting the context manager will call the span's end method,
348 as well as return the current span to its previous value by
349 returning to the previous context.
350
351 Example::
352
353 with tracer.start_as_current_span("one") as parent:
354 parent.add_event("parent's event")
355 with tracer.start_as_current_span("two") as child:
356 child.add_event("child's event")
357 trace.get_current_span() # returns child
358 trace.get_current_span() # returns parent
359 trace.get_current_span() # returns previously active span
360
361 This is a convenience method for creating spans attached to the
362 tracer's context. Applications that need more control over the span
363 lifetime should use :meth:`start_span` instead. For example::
364
365 with tracer.start_as_current_span(name) as span:
366 do_work()
367
368 is equivalent to::
369
370 span = tracer.start_span(name)
371 with opentelemetry.trace.use_span(span, end_on_exit=True):
372 do_work()
373
374 This can also be used as a decorator::
375
376 @tracer.start_as_current_span("name")
377 def function():
378 ...
379
380 function()
381
382 Args:
383 name: The name of the span to be created.
384 context: An optional Context containing the span's parent. Defaults to the
385 global context.
386 kind: The span's kind (relationship to parent). Note that is
387 meaningful even if there is no parent.
388 attributes: The span's attributes.
389 links: Links span to other spans
390 start_time: Sets the start time of a span
391 record_exception: Whether to record any exceptions raised within the
392 context as error event on the span.
393 set_status_on_exception: Only relevant if the returned span is used
394 in a with/context manager. Defines whether the span status will
395 be automatically set to ERROR when an uncaught exception is
396 raised in the span with block. The span status won't be set by
397 this mechanism if it was previously set manually.
398 end_on_exit: Whether to end the span automatically when leaving the
399 context manager.
400
401 Yields:
402 The newly-created span.
403 """
404
405
406class ProxyTracer(Tracer):
407 # pylint: disable=W0222,signature-differs
408 def __init__(
409 self,
410 instrumenting_module_name: str,
411 instrumenting_library_version: str | None = None,
412 schema_url: str | None = None,
413 attributes: types.Attributes | None = None,
414 ):
415 self._instrumenting_module_name = instrumenting_module_name
416 self._instrumenting_library_version = instrumenting_library_version
417 self._schema_url = schema_url
418 self._attributes = attributes
419 self._real_tracer: Tracer | None = None
420 self._noop_tracer = NoOpTracer()
421
422 @property
423 def _tracer(self) -> Tracer:
424 if self._real_tracer:
425 return self._real_tracer
426
427 if _TRACER_PROVIDER:
428 self._real_tracer = _TRACER_PROVIDER.get_tracer(
429 self._instrumenting_module_name,
430 self._instrumenting_library_version,
431 self._schema_url,
432 self._attributes,
433 )
434 return self._real_tracer
435 return self._noop_tracer
436
437 def start_span(self, *args, **kwargs) -> Span: # type: ignore
438 return self._tracer.start_span(*args, **kwargs) # type: ignore
439
440 @_agnosticcontextmanager # type: ignore
441 def start_as_current_span(self, *args, **kwargs) -> Iterator[Span]:
442 with self._tracer.start_as_current_span(*args, **kwargs) as span: # type: ignore
443 yield span
444
445
446class NoOpTracer(Tracer):
447 """The default Tracer, used when no Tracer implementation is available.
448
449 All operations are no-op.
450 """
451
452 def start_span(
453 self,
454 name: str,
455 context: Context | None = None,
456 kind: SpanKind = SpanKind.INTERNAL,
457 attributes: types.Attributes = None,
458 links: _Links = None,
459 start_time: int | None = None,
460 record_exception: bool = True,
461 set_status_on_exception: bool = True,
462 ) -> "Span":
463 current_span = get_current_span(context)
464 if isinstance(current_span, NonRecordingSpan):
465 return current_span
466 parent_span_context = current_span.get_span_context()
467 if parent_span_context is not None and not isinstance(
468 parent_span_context, SpanContext
469 ):
470 logger.warning(
471 "Invalid span context for %s: %s",
472 current_span,
473 parent_span_context,
474 )
475 return INVALID_SPAN
476
477 return NonRecordingSpan(context=parent_span_context)
478
479 @_agnosticcontextmanager
480 def start_as_current_span(
481 self,
482 name: str,
483 context: Context | None = None,
484 kind: SpanKind = SpanKind.INTERNAL,
485 attributes: types.Attributes = None,
486 links: _Links = None,
487 start_time: int | None = None,
488 record_exception: bool = True,
489 set_status_on_exception: bool = True,
490 end_on_exit: bool = True,
491 ) -> Iterator["Span"]:
492 span = self.start_span(
493 name=name,
494 context=context,
495 kind=kind,
496 attributes=attributes,
497 links=links,
498 start_time=start_time,
499 record_exception=record_exception,
500 set_status_on_exception=set_status_on_exception,
501 )
502 with use_span(
503 span,
504 end_on_exit=end_on_exit,
505 record_exception=record_exception,
506 set_status_on_exception=set_status_on_exception,
507 ) as span:
508 yield span
509
510
511@deprecated("You should use NoOpTracer. Deprecated since version 1.9.0.")
512class _DefaultTracer(NoOpTracer):
513 """The default Tracer, used when no Tracer implementation is available.
514
515 All operations are no-op.
516 """
517
518
519_TRACER_PROVIDER_SET_ONCE = Once()
520_TRACER_PROVIDER: TracerProvider | None = None
521_PROXY_TRACER_PROVIDER = ProxyTracerProvider()
522
523
524def get_tracer(
525 instrumenting_module_name: str,
526 instrumenting_library_version: str | None = None,
527 tracer_provider: TracerProvider | None = None,
528 schema_url: str | None = None,
529 attributes: types.Attributes | None = None,
530) -> "Tracer":
531 """Returns a `Tracer` for use by the given instrumentation library.
532
533 This function is a convenience wrapper for
534 opentelemetry.trace.TracerProvider.get_tracer.
535
536 If tracer_provider is omitted the current configured one is used.
537 """
538 if tracer_provider is None:
539 tracer_provider = get_tracer_provider()
540 return tracer_provider.get_tracer(
541 instrumenting_module_name,
542 instrumenting_library_version,
543 schema_url,
544 attributes,
545 )
546
547
548def _set_tracer_provider(tracer_provider: TracerProvider, log: bool) -> None:
549 def set_tp() -> None:
550 global _TRACER_PROVIDER # pylint: disable=global-statement
551 _TRACER_PROVIDER = tracer_provider
552
553 did_set = _TRACER_PROVIDER_SET_ONCE.do_once(set_tp)
554
555 if log and not did_set:
556 logger.warning("Overriding of current TracerProvider is not allowed")
557
558
559def set_tracer_provider(tracer_provider: TracerProvider) -> None:
560 """Sets the current global :class:`~.TracerProvider` object.
561
562 This can only be done once, a warning will be logged if any further attempt
563 is made.
564 """
565 _set_tracer_provider(tracer_provider, log=True)
566
567
568def get_tracer_provider() -> TracerProvider:
569 """Gets the current global :class:`~.TracerProvider` object."""
570 if _TRACER_PROVIDER is None:
571 # if a global tracer provider has not been set either via code or env
572 # vars, return a proxy tracer provider
573 if OTEL_PYTHON_TRACER_PROVIDER not in os.environ:
574 return _PROXY_TRACER_PROVIDER
575
576 tracer_provider: TracerProvider = _load_provider(
577 OTEL_PYTHON_TRACER_PROVIDER, "tracer_provider"
578 )
579 _set_tracer_provider(tracer_provider, log=False)
580 # _TRACER_PROVIDER will have been set by one thread
581 return cast("TracerProvider", _TRACER_PROVIDER)
582
583
584@_agnosticcontextmanager
585def use_span(
586 span: Span,
587 end_on_exit: bool = False,
588 record_exception: bool = True,
589 set_status_on_exception: bool = True,
590) -> Iterator[Span]:
591 """Takes a non-active span and activates it in the current context.
592
593 Args:
594 span: The span that should be activated in the current context.
595 end_on_exit: Whether to end the span automatically when leaving the
596 context manager scope.
597 record_exception: Whether to record any exceptions raised within the
598 context as error event on the span.
599 set_status_on_exception: Only relevant if the returned span is used
600 in a with/context manager. Defines whether the span status will
601 be automatically set to ERROR when an uncaught exception is
602 raised in the span with block. The span status won't be set by
603 this mechanism if it was previously set manually.
604 """
605 try:
606 token = context_api.attach(context_api.set_value(_SPAN_KEY, span))
607 try:
608 yield span
609 finally:
610 context_api.detach(token)
611
612 # Record only exceptions that inherit Exception class but not BaseException, because
613 # classes that directly inherit BaseException are not technically errors, e.g. GeneratorExit.
614 # See https://github.com/open-telemetry/opentelemetry-python/issues/4484
615 except Exception as exc: # pylint: disable=broad-exception-caught
616 if isinstance(span, Span) and span.is_recording():
617 # Record the exception as an event
618 if record_exception:
619 span.record_exception(exc)
620
621 # Set status in case exception was raised
622 if set_status_on_exception:
623 span.set_status(
624 Status(
625 status_code=StatusCode.ERROR,
626 description=f"{type(exc).__name__}: {exc}",
627 )
628 )
629
630 # This causes parent spans to set their status to ERROR and to record
631 # an exception as an event if a child span raises an exception even if
632 # such child span was started with both record_exception and
633 # set_status_on_exception attributes set to False.
634 raise
635
636 finally:
637 if end_on_exit:
638 span.end()
639
640
641__all__ = [
642 "DEFAULT_TRACE_OPTIONS",
643 "DEFAULT_TRACE_STATE",
644 "INVALID_SPAN",
645 "INVALID_SPAN_CONTEXT",
646 "INVALID_SPAN_ID",
647 "INVALID_TRACE_ID",
648 "NonRecordingSpan",
649 "Link",
650 "Span",
651 "SpanContext",
652 "SpanKind",
653 "TraceFlags",
654 "TraceState",
655 "TracerProvider",
656 "Tracer",
657 "format_span_id",
658 "format_trace_id",
659 "get_current_span",
660 "get_tracer",
661 "get_tracer_provider",
662 "set_tracer_provider",
663 "set_span_in_context",
664 "use_span",
665 "Status",
666 "StatusCode",
667]