diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py
index cf748bb8ea..45afb56cc9 100644
--- a/sentry_sdk/hub.py
+++ b/sentry_sdk/hub.py
@@ -7,17 +7,10 @@
from sentry_sdk.consts import INSTRUMENTER
from sentry_sdk.scope import Scope
from sentry_sdk.client import Client
-from sentry_sdk.profiler import Profile
from sentry_sdk.tracing import (
NoOpSpan,
Span,
Transaction,
- BAGGAGE_HEADER_NAME,
- SENTRY_TRACE_HEADER_NAME,
-)
-from sentry_sdk.tracing_utils import (
- has_tracing_enabled,
- normalize_incoming_data,
)
from sentry_sdk.utils import (
@@ -28,18 +21,18 @@
from sentry_sdk._types import TYPE_CHECKING
if TYPE_CHECKING:
- from typing import Union
from typing import Any
- from typing import Optional
- from typing import Tuple
- from typing import Dict
- from typing import List
from typing import Callable
+ from typing import ContextManager
+ from typing import Dict
from typing import Generator
+ from typing import List
+ from typing import Optional
+ from typing import overload
+ from typing import Tuple
from typing import Type
from typing import TypeVar
- from typing import overload
- from typing import ContextManager
+ from typing import Union
from sentry_sdk.integrations import Integration
from sentry_sdk._types import (
@@ -447,54 +440,12 @@ def start_span(self, span=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs):
For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Span`.
"""
- configuration_instrumenter = self.client and self.client.options["instrumenter"]
-
- if instrumenter != configuration_instrumenter:
- return NoOpSpan()
-
- # THIS BLOCK IS DEPRECATED
- # TODO: consider removing this in a future release.
- # This is for backwards compatibility with releases before
- # start_transaction existed, to allow for a smoother transition.
- if isinstance(span, Transaction) or "transaction" in kwargs:
- deprecation_msg = (
- "Deprecated: use start_transaction to start transactions and "
- "Transaction.start_child to start spans."
- )
-
- if isinstance(span, Transaction):
- logger.warning(deprecation_msg)
- return self.start_transaction(span)
-
- if "transaction" in kwargs:
- logger.warning(deprecation_msg)
- name = kwargs.pop("transaction")
- return self.start_transaction(name=name, **kwargs)
-
- # THIS BLOCK IS DEPRECATED
- # We do not pass a span into start_span in our code base, so I deprecate this.
- if span is not None:
- deprecation_msg = "Deprecated: passing a span into `start_span` is deprecated and will be removed in the future."
- logger.warning(deprecation_msg)
- return span
-
- kwargs.setdefault("hub", self)
-
- active_span = self.scope.span
- if active_span is not None:
- new_child_span = active_span.start_child(**kwargs)
- return new_child_span
+ client, scope = self._stack[-1]
- # If there is already a trace_id in the propagation context, use it.
- # This does not need to be done for `start_child` above because it takes
- # the trace_id from the parent span.
- if "trace_id" not in kwargs:
- traceparent = self.get_traceparent()
- trace_id = traceparent.split("-")[0] if traceparent else None
- if trace_id is not None:
- kwargs["trace_id"] = trace_id
+ kwargs["hub"] = self
+ kwargs["client"] = client
- return Span(**kwargs)
+ return scope.start_span(span=span, instrumenter=instrumenter, **kwargs)
def start_transaction(
self, transaction=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs
@@ -524,55 +475,25 @@ def start_transaction(
For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Transaction`.
"""
- configuration_instrumenter = self.client and self.client.options["instrumenter"]
-
- if instrumenter != configuration_instrumenter:
- return NoOpSpan()
-
- custom_sampling_context = kwargs.pop("custom_sampling_context", {})
-
- # if we haven't been given a transaction, make one
- if transaction is None:
- kwargs.setdefault("hub", self)
- transaction = Transaction(**kwargs)
-
- # use traces_sample_rate, traces_sampler, and/or inheritance to make a
- # sampling decision
- sampling_context = {
- "transaction_context": transaction.to_json(),
- "parent_sampled": transaction.parent_sampled,
- }
- sampling_context.update(custom_sampling_context)
- transaction._set_initial_sampling_decision(sampling_context=sampling_context)
-
- profile = Profile(transaction, hub=self)
- profile._set_initial_sampling_decision(sampling_context=sampling_context)
+ client, scope = self._stack[-1]
- # we don't bother to keep spans if we already know we're not going to
- # send the transaction
- if transaction.sampled:
- max_spans = (
- self.client and self.client.options["_experiments"].get("max_spans")
- ) or 1000
- transaction.init_span_recorder(maxlen=max_spans)
+ kwargs["hub"] = self
+ kwargs["client"] = client
- return transaction
+ return scope.start_transaction(
+ transaction=transaction, instrumenter=instrumenter, **kwargs
+ )
def continue_trace(self, environ_or_headers, op=None, name=None, source=None):
# type: (Dict[str, Any], Optional[str], Optional[str], Optional[str]) -> Transaction
"""
Sets the propagation context from environment or headers and returns a transaction.
"""
- with self.configure_scope() as scope:
- scope.generate_propagation_context(environ_or_headers)
+ scope = self._stack[-1][1]
- transaction = Transaction.continue_from_headers(
- normalize_incoming_data(environ_or_headers),
- op=op,
- name=name,
- source=source,
+ return scope.continue_trace(
+ environ_or_headers=environ_or_headers, op=op, name=name, source=source
)
- return transaction
@overload
def push_scope(
@@ -735,25 +656,16 @@ def get_traceparent(self):
"""
Returns the traceparent either from the active span or from the scope.
"""
- if self.client is not None:
- if has_tracing_enabled(self.client.options) and self.scope.span is not None:
- return self.scope.span.to_traceparent()
-
- return self.scope.get_traceparent()
+ client, scope = self._stack[-1]
+ return scope.get_traceparent(client=client)
def get_baggage(self):
# type: () -> Optional[str]
"""
Returns Baggage either from the active span or from the scope.
"""
- if (
- self.client is not None
- and has_tracing_enabled(self.client.options)
- and self.scope.span is not None
- ):
- baggage = self.scope.span.to_baggage()
- else:
- baggage = self.scope.get_baggage()
+ client, scope = self._stack[-1]
+ baggage = scope.get_baggage(client=client)
if baggage is not None:
return baggage.serialize()
@@ -767,19 +679,9 @@ def iter_trace_propagation_headers(self, span=None):
from the span representing the request, if available, or the current
span on the scope if not.
"""
- client = self._stack[-1][0]
- propagate_traces = client and client.options["propagate_traces"]
- if not propagate_traces:
- return
-
- span = span or self.scope.span
+ client, scope = self._stack[-1]
- if client and has_tracing_enabled(client.options) and span is not None:
- for header in span.iter_headers():
- yield header
- else:
- for header in self.scope.iter_headers():
- yield header
+ return scope.iter_trace_propagation_headers(span=span, client=client)
def trace_propagation_meta(self, span=None):
# type: (Optional[Span]) -> str
@@ -792,23 +694,8 @@ def trace_propagation_meta(self, span=None):
"The parameter `span` in trace_propagation_meta() is deprecated and will be removed in the future."
)
- meta = ""
-
- sentry_trace = self.get_traceparent()
- if sentry_trace is not None:
- meta += '' % (
- SENTRY_TRACE_HEADER_NAME,
- sentry_trace,
- )
-
- baggage = self.get_baggage()
- if baggage is not None:
- meta += '' % (
- BAGGAGE_HEADER_NAME,
- baggage,
- )
-
- return meta
+ client, scope = self._stack[-1]
+ return scope.trace_propagation_meta(span=span, client=client)
GLOBAL_HUB = Hub()
diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py
index c715847d38..9507306812 100644
--- a/sentry_sdk/scope.py
+++ b/sentry_sdk/scope.py
@@ -7,8 +7,9 @@
from sentry_sdk.attachments import Attachment
from sentry_sdk._compat import datetime_utcnow
-from sentry_sdk.consts import FALSE_VALUES
+from sentry_sdk.consts import FALSE_VALUES, INSTRUMENTER
from sentry_sdk._functools import wraps
+from sentry_sdk.profiler import Profile
from sentry_sdk.session import Session
from sentry_sdk.tracing_utils import (
Baggage,
@@ -19,6 +20,8 @@
from sentry_sdk.tracing import (
BAGGAGE_HEADER_NAME,
SENTRY_TRACE_HEADER_NAME,
+ NoOpSpan,
+ Span,
Transaction,
)
from sentry_sdk._types import TYPE_CHECKING
@@ -31,14 +34,16 @@
if TYPE_CHECKING:
from typing import Any
+ from typing import Callable
+ from typing import Deque
from typing import Dict
+ from typing import Generator
from typing import Iterator
- from typing import Optional
- from typing import Deque
from typing import List
- from typing import Callable
+ from typing import Optional
from typing import Tuple
from typing import TypeVar
+ from typing import Union
from sentry_sdk._types import (
Breadcrumb,
@@ -53,8 +58,6 @@
)
import sentry_sdk
- from sentry_sdk.profiler import Profile
- from sentry_sdk.tracing import Span
F = TypeVar("F", bound=Callable[..., Any])
T = TypeVar("T")
@@ -274,11 +277,22 @@ def get_dynamic_sampling_context(self):
return self._propagation_context["dynamic_sampling_context"]
- def get_traceparent(self):
- # type: () -> Optional[str]
+ def get_traceparent(self, *args, **kwargs):
+ # type: (Any, Any) -> Optional[str]
"""
- Returns the Sentry "sentry-trace" header (aka the traceparent) from the Propagation Context.
+ Returns the Sentry "sentry-trace" header (aka the traceparent) from the
+ currently active span or the scopes Propagation Context.
"""
+ client = kwargs.pop("client", None)
+
+ # If we have an active span, return traceparent from there
+ if (
+ client is not None
+ and has_tracing_enabled(client.options)
+ and self.span is not None
+ ):
+ return self.span.to_traceparent()
+
if self._propagation_context is None:
return None
@@ -288,8 +302,18 @@ def get_traceparent(self):
)
return traceparent
- def get_baggage(self):
- # type: () -> Optional[Baggage]
+ def get_baggage(self, *args, **kwargs):
+ # type: (Any, Any) -> Optional[Baggage]
+ client = kwargs.pop("client", None)
+
+ # If we have an active span, return baggage from there
+ if (
+ client is not None
+ and has_tracing_enabled(client.options)
+ and self.span is not None
+ ):
+ return self.span.to_baggage()
+
if self._propagation_context is None:
return None
@@ -318,6 +342,38 @@ def get_trace_context(self):
return trace_context
+ def trace_propagation_meta(self, *args, **kwargs):
+ # type: (*Any, **Any) -> str
+ """
+ Return meta tags which should be injected into HTML templates
+ to allow propagation of trace information.
+ """
+ span = kwargs.pop("span", None)
+ if span is not None:
+ logger.warning(
+ "The parameter `span` in trace_propagation_meta() is deprecated and will be removed in the future."
+ )
+
+ client = kwargs.pop("client", None)
+
+ meta = ""
+
+ sentry_trace = self.get_traceparent(client=client)
+ if sentry_trace is not None:
+ meta += '' % (
+ SENTRY_TRACE_HEADER_NAME,
+ sentry_trace,
+ )
+
+ baggage = self.get_baggage(client=client)
+ if baggage is not None:
+ meta += '' % (
+ BAGGAGE_HEADER_NAME,
+ baggage.serialize(),
+ )
+
+ return meta
+
def iter_headers(self):
# type: () -> Iterator[Tuple[str, str]]
"""
@@ -333,6 +389,29 @@ def iter_headers(self):
baggage = Baggage(dsc).serialize()
yield BAGGAGE_HEADER_NAME, baggage
+ def iter_trace_propagation_headers(self, *args, **kwargs):
+ # type: (Any, Any) -> Generator[Tuple[str, str], None, None]
+ """
+ Return HTTP headers which allow propagation of trace data. Data taken
+ from the span representing the request, if available, or the current
+ span on the scope if not.
+ """
+ span = kwargs.pop("span", None)
+ client = kwargs.pop("client", None)
+
+ propagate_traces = client and client.options["propagate_traces"]
+ if not propagate_traces:
+ return
+
+ span = span or self.span
+
+ if client and has_tracing_enabled(client.options) and span is not None:
+ for header in span.iter_headers():
+ yield header
+ else:
+ for header in self.iter_headers():
+ yield header
+
def clear(self):
# type: () -> None
"""Clears the entire scope."""
@@ -589,6 +668,155 @@ def add_breadcrumb(self, crumb=None, hint=None, **kwargs):
while len(self._breadcrumbs) > max_breadcrumbs:
self._breadcrumbs.popleft()
+ def start_transaction(
+ self, transaction=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs
+ ):
+ # type: (Optional[Transaction], str, Any) -> Union[Transaction, NoOpSpan]
+ """
+ Start and return a transaction.
+
+ Start an existing transaction if given, otherwise create and start a new
+ transaction with kwargs.
+
+ This is the entry point to manual tracing instrumentation.
+
+ A tree structure can be built by adding child spans to the transaction,
+ and child spans to other spans. To start a new child span within the
+ transaction or any span, call the respective `.start_child()` method.
+
+ Every child span must be finished before the transaction is finished,
+ otherwise the unfinished spans are discarded.
+
+ When used as context managers, spans and transactions are automatically
+ finished at the end of the `with` block. If not using context managers,
+ call the `.finish()` method.
+
+ When the transaction is finished, it will be sent to Sentry with all its
+ finished child spans.
+
+ For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Transaction`.
+ """
+ hub = kwargs.pop("hub", None)
+ client = kwargs.pop("client", None)
+
+ configuration_instrumenter = client and client.options["instrumenter"]
+
+ if instrumenter != configuration_instrumenter:
+ return NoOpSpan()
+
+ custom_sampling_context = kwargs.pop("custom_sampling_context", {})
+
+ # if we haven't been given a transaction, make one
+ if transaction is None:
+ kwargs.setdefault("hub", hub)
+ transaction = Transaction(**kwargs)
+
+ # use traces_sample_rate, traces_sampler, and/or inheritance to make a
+ # sampling decision
+ sampling_context = {
+ "transaction_context": transaction.to_json(),
+ "parent_sampled": transaction.parent_sampled,
+ }
+ sampling_context.update(custom_sampling_context)
+ transaction._set_initial_sampling_decision(sampling_context=sampling_context)
+
+ profile = Profile(transaction, hub=hub)
+ profile._set_initial_sampling_decision(sampling_context=sampling_context)
+
+ # we don't bother to keep spans if we already know we're not going to
+ # send the transaction
+ if transaction.sampled:
+ max_spans = (
+ client and client.options["_experiments"].get("max_spans")
+ ) or 1000
+ transaction.init_span_recorder(maxlen=max_spans)
+
+ return transaction
+
+ def start_span(self, span=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs):
+ # type: (Optional[Span], str, Any) -> Span
+ """
+ Start a span whose parent is the currently active span or transaction, if any.
+
+ The return value is a :py:class:`sentry_sdk.tracing.Span` instance,
+ typically used as a context manager to start and stop timing in a `with`
+ block.
+
+ Only spans contained in a transaction are sent to Sentry. Most
+ integrations start a transaction at the appropriate time, for example
+ for every incoming HTTP request. Use
+ :py:meth:`sentry_sdk.start_transaction` to start a new transaction when
+ one is not already in progress.
+
+ For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Span`.
+ """
+ client = kwargs.get("client", None)
+
+ configuration_instrumenter = client and client.options["instrumenter"]
+
+ if instrumenter != configuration_instrumenter:
+ return NoOpSpan()
+
+ # THIS BLOCK IS DEPRECATED
+ # TODO: consider removing this in a future release.
+ # This is for backwards compatibility with releases before
+ # start_transaction existed, to allow for a smoother transition.
+ if isinstance(span, Transaction) or "transaction" in kwargs:
+ deprecation_msg = (
+ "Deprecated: use start_transaction to start transactions and "
+ "Transaction.start_child to start spans."
+ )
+
+ if isinstance(span, Transaction):
+ logger.warning(deprecation_msg)
+ return self.start_transaction(span, **kwargs)
+
+ if "transaction" in kwargs:
+ logger.warning(deprecation_msg)
+ name = kwargs.pop("transaction")
+ return self.start_transaction(name=name, **kwargs)
+
+ # THIS BLOCK IS DEPRECATED
+ # We do not pass a span into start_span in our code base, so I deprecate this.
+ if span is not None:
+ deprecation_msg = "Deprecated: passing a span into `start_span` is deprecated and will be removed in the future."
+ logger.warning(deprecation_msg)
+ return span
+
+ kwargs.pop("client")
+
+ active_span = self.span
+ if active_span is not None:
+ new_child_span = active_span.start_child(**kwargs)
+ return new_child_span
+
+ # If there is already a trace_id in the propagation context, use it.
+ # This does not need to be done for `start_child` above because it takes
+ # the trace_id from the parent span.
+ if "trace_id" not in kwargs:
+ traceparent = self.get_traceparent()
+ trace_id = traceparent.split("-")[0] if traceparent else None
+ if trace_id is not None:
+ kwargs["trace_id"] = trace_id
+
+ return Span(**kwargs)
+
+ def continue_trace(self, environ_or_headers, op=None, name=None, source=None):
+ # type: (Dict[str, Any], Optional[str], Optional[str], Optional[str]) -> Transaction
+ """
+ Sets the propagation context from environment or headers and returns a transaction.
+ """
+ self.generate_propagation_context(environ_or_headers)
+
+ transaction = Transaction.continue_from_headers(
+ normalize_incoming_data(environ_or_headers),
+ op=op,
+ name=name,
+ source=source,
+ )
+
+ return transaction
+
def capture_event(self, event, hint=None, client=None, scope=None, **scope_kwargs):
# type: (Event, Optional[Hint], Optional[sentry_sdk.Client], Optional[Scope], Any) -> Optional[str]
"""