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