diff --git a/sentry_sdk/debug.py b/sentry_sdk/debug.py index fe8ae50cea..257fa6e75c 100644 --- a/sentry_sdk/debug.py +++ b/sentry_sdk/debug.py @@ -2,6 +2,7 @@ import logging from sentry_sdk import utils +from sentry_sdk.api import get_client from sentry_sdk.hub import Hub from sentry_sdk.utils import logger from sentry_sdk.client import _client_init_debug @@ -13,10 +14,8 @@ def filter(self, record): # type: (LogRecord) -> bool if _client_init_debug.get(False): return True - hub = Hub.current - if hub is not None and hub.client is not None: - return hub.client.options["debug"] - return False + + return get_client().options["debug"] def init_debug_support(): diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index 45afb56cc9..90a87c8890 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -6,7 +6,7 @@ from sentry_sdk._compat import with_metaclass from sentry_sdk.consts import INSTRUMENTER from sentry_sdk.scope import Scope -from sentry_sdk.client import Client +from sentry_sdk.client import Client, NoopClient from sentry_sdk.tracing import ( NoOpSpan, Span, @@ -266,21 +266,19 @@ def get_integration( If the return value is not `None` the hub is guaranteed to have a client attached. """ - client = self.client - if client is not None: - return client.get_integration(name_or_class) + Scope.get_client().get_integration(name_or_class) @property def client(self): - # type: () -> Optional[Client] + # type: () -> Optional[NoopClient] """Returns the current client on the hub.""" - return self._stack[-1][0] + return Scope.get_client() @property def scope(self): # type: () -> Scope """Returns the current scope on the hub.""" - return self._stack[-1][1] + return Scope.get_current_scope() def last_event_id(self): # type: () -> Optional[str] @@ -292,8 +290,7 @@ def bind_client( ): # type: (...) -> None """Binds a new client to the hub.""" - top = self._stack[-1] - self._stack[-1] = (new, top[1]) + Scope.get_global_scope().set_client(new) def capture_event(self, event, hint=None, scope=None, **scope_kwargs): # type: (Event, Optional[Hint], Optional[Scope], Any) -> Optional[str] @@ -313,12 +310,8 @@ def capture_event(self, event, hint=None, scope=None, **scope_kwargs): For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. The `scope` and `scope_kwargs` parameters are mutually exclusive. """ - client, top_scope = self._stack[-1] - if client is None: - return None - - last_event_id = top_scope.capture_event( - event, hint, client=client, scope=scope, **scope_kwargs + last_event_id = Scope.get_current_scope().capture_event( + event, hint, scope=scope, **scope_kwargs ) is_transaction = event.get("type") == "transaction" @@ -347,12 +340,8 @@ def capture_message(self, message, level=None, scope=None, **scope_kwargs): :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). """ - client, top_scope = self._stack[-1] - if client is None: - return None - - last_event_id = top_scope.capture_message( - message, level=level, client=client, scope=scope, **scope_kwargs + last_event_id = Scope.get_current_scope().capture_message( + message, level=level, scope=scope, **scope_kwargs ) if last_event_id is not None: @@ -377,12 +366,8 @@ def capture_exception(self, error=None, scope=None, **scope_kwargs): :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). """ - client, top_scope = self._stack[-1] - if client is None: - return None - - last_event_id = top_scope.capture_exception( - error, client=client, scope=scope, **scope_kwargs + last_event_id = Scope.get_current_scope().capture_exception( + error, scope=scope, **scope_kwargs ) if last_event_id is not None: @@ -414,14 +399,7 @@ def add_breadcrumb(self, crumb=None, hint=None, **kwargs): :param hint: An optional value that can be used by `before_breadcrumb` to customize the breadcrumbs that are emitted. """ - client, scope = self._stack[-1] - if client is None: - logger.info("Dropped breadcrumb because no client bound") - return - - kwargs["client"] = client - - scope.add_breadcrumb(crumb, hint, **kwargs) + Scope.get_isolation_scope().add_breadcrumb(crumb, hint, **kwargs) def start_span(self, span=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs): # type: (Optional[Span], str, Any) -> Span @@ -440,12 +418,12 @@ def start_span(self, span=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs): For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Span`. """ - client, scope = self._stack[-1] - + # TODO: fix hub kwargs["hub"] = self - kwargs["client"] = client - return scope.start_span(span=span, instrumenter=instrumenter, **kwargs) + return Scope.get_isolation_scope().start_span( + span=span, instrumenter=instrumenter, **kwargs + ) def start_transaction( self, transaction=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs @@ -475,12 +453,10 @@ def start_transaction( For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Transaction`. """ - client, scope = self._stack[-1] - + # TODO: fix hub kwargs["hub"] = self - kwargs["client"] = client - return scope.start_transaction( + return Scope.get_isolation_scope().start_transaction( transaction=transaction, instrumenter=instrumenter, **kwargs ) @@ -489,9 +465,7 @@ def continue_trace(self, environ_or_headers, op=None, name=None, source=None): """ Sets the propagation context from environment or headers and returns a transaction. """ - scope = self._stack[-1][1] - - return scope.continue_trace( + return Scope.get_isolation_scope().continue_trace( environ_or_headers=environ_or_headers, op=op, name=name, source=source ) @@ -580,25 +554,19 @@ def configure_scope( # noqa :returns: If no callback is provided, returns a context manager that returns the scope. """ - - client, scope = self._stack[-1] + scope = Scope.get_isolation_scope() if continue_trace: - scope.generate_propagation_context() + Scope.get_isolation_scope().generate_propagation_context() if callback is not None: - if client is not None: - callback(scope) - - return None + # TODO: used to return None when client is None. Check if this changes behavior. + callback(scope) @contextmanager def inner(): # type: () -> Generator[Scope, None, None] - if client is not None: - yield scope - else: - yield Scope() + yield scope return inner() @@ -607,17 +575,14 @@ def start_session( ): # type: (...) -> None """Starts a new session.""" - client, scope = self._stack[-1] - scope.start_session( - client=client, + Scope.get_isolation_scope().start_session( session_mode=session_mode, ) def end_session(self): # type: (...) -> None """Ends the current session if there is one.""" - client, scope = self._stack[-1] - scope.end_session(client=client) + Scope.get_isolation_scope().end_session() def stop_auto_session_tracking(self): # type: (...) -> None @@ -626,8 +591,7 @@ def stop_auto_session_tracking(self): This temporarily session tracking for the current scope when called. To resume session tracking call `resume_auto_session_tracking`. """ - client, scope = self._stack[-1] - scope.stop_auto_session_tracking(client=client) + Scope.get_isolation_scope().stop_auto_session_tracking() def resume_auto_session_tracking(self): # type: (...) -> None @@ -635,8 +599,7 @@ def resume_auto_session_tracking(self): disabled earlier. This requires that generally automatic session tracking is enabled. """ - scope = self._stack[-1][1] - scope.resume_auto_session_tracking() + Scope.get_isolation_scope().resume_auto_session_tracking() def flush( self, @@ -647,25 +610,21 @@ def flush( """ Alias for :py:meth:`sentry_sdk.Client.flush` """ - client, scope = self._stack[-1] - if client is not None: - return client.flush(timeout=timeout, callback=callback) + return Scope.get_client().flush(timeout=timeout, callback=callback) def get_traceparent(self): # type: () -> Optional[str] """ Returns the traceparent either from the active span or from the scope. """ - client, scope = self._stack[-1] - return scope.get_traceparent(client=client) + return Scope.get_current_scope().get_traceparent() def get_baggage(self): # type: () -> Optional[str] """ Returns Baggage either from the active span or from the scope. """ - client, scope = self._stack[-1] - baggage = scope.get_baggage(client=client) + baggage = Scope.get_current_scope().get_baggage() if baggage is not None: return baggage.serialize() @@ -679,9 +638,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, scope = self._stack[-1] - - return scope.iter_trace_propagation_headers(span=span, client=client) + return Scope.get_current_scope().iter_trace_propagation_headers( + span=span, + ) def trace_propagation_meta(self, span=None): # type: (Optional[Span]) -> str @@ -694,8 +653,9 @@ def trace_propagation_meta(self, span=None): "The parameter `span` in trace_propagation_meta() is deprecated and will be removed in the future." ) - client, scope = self._stack[-1] - return scope.trace_propagation_meta(span=span, client=client) + return Scope.get_current_scope().trace_propagation_meta( + span=span, + ) GLOBAL_HUB = Hub() diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index b109a82342..ebd255fad7 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -258,7 +258,7 @@ def get_global_scope(cls): def get_client(cls): # type: () -> Union[sentry_sdk.Client, NoopClient] """ - Returns the current :py:class:`sentry_sdk.Client`. + Returns the currently used :py:class:`sentry_sdk.Client`. This checks the current scope, the isolation scope and the global scope for a client. If no client is available a :py:class:`sentry_sdk.client.NoopClient` is returned. @@ -443,7 +443,7 @@ def get_traceparent(self, *args, **kwargs): Returns the Sentry "sentry-trace" header (aka the traceparent) from the currently active span or the scopes Propagation Context. """ - client = kwargs.pop("client", None) + client = Scope.get_client() # If we have an active span, return traceparent from there if ( @@ -464,7 +464,7 @@ def get_traceparent(self, *args, **kwargs): def get_baggage(self, *args, **kwargs): # type: (Any, Any) -> Optional[Baggage] - client = kwargs.pop("client", None) + client = Scope.get_client() # If we have an active span, return baggage from there if ( @@ -514,18 +514,16 @@ def trace_propagation_meta(self, *args, **kwargs): "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) + sentry_trace = self.get_traceparent() if sentry_trace is not None: meta += '' % ( SENTRY_TRACE_HEADER_NAME, sentry_trace, ) - baggage = self.get_baggage(client=client) + baggage = self.get_baggage() if baggage is not None: meta += '' % ( BAGGAGE_HEADER_NAME, @@ -556,16 +554,14 @@ def iter_trace_propagation_headers(self, *args, **kwargs): 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: + client = Scope.get_client() + if not client.options.get("propagate_traces"): return + span = kwargs.pop("span", None) span = span or self.span - if client and has_tracing_enabled(client.options) and span is not None: + if has_tracing_enabled(client.options) and span is not None: for header in span.iter_headers(): yield header else: @@ -796,12 +792,14 @@ def add_breadcrumb(self, crumb=None, hint=None, **kwargs): :param hint: An optional value that can be used by `before_breadcrumb` to customize the breadcrumbs that are emitted. """ - client = kwargs.pop("client", None) - if client is None: + client = Scope.get_client() + + if not client.is_active(): + logger.info("Dropped breadcrumb because no client bound") return - before_breadcrumb = client.options.get("before_breadcrumb") - max_breadcrumbs = client.options.get("max_breadcrumbs") + before_breadcrumb = client.options["before_breadcrumb"] + max_breadcrumbs = client.options["max_breadcrumbs"] crumb = dict(crumb or ()) # type: Breadcrumb crumb.update(kwargs) @@ -828,8 +826,8 @@ def add_breadcrumb(self, crumb=None, hint=None, **kwargs): while len(self._breadcrumbs) > max_breadcrumbs: self._breadcrumbs.popleft() - 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] + def capture_event(self, event, hint=None, scope=None, **scope_kwargs): + # type: (Event, Optional[Hint], Optional[Scope], Any) -> Optional[str] """ Captures an event. @@ -850,18 +848,13 @@ def capture_event(self, event, hint=None, client=None, scope=None, **scope_kwarg :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). """ - if client is None: - return None - if scope_kwargs is not None: scope = _merge_scopes(self, scope, scope_kwargs) - return client.capture_event(event=event, hint=hint, scope=scope) + return Scope.get_client().capture_event(event=event, hint=hint, scope=scope) - def capture_message( - self, message, level=None, client=None, scope=None, **scope_kwargs - ): - # type: (str, Optional[str], Optional[sentry_sdk.Client], Optional[Scope], Any) -> Optional[str] + def capture_message(self, message, level=None, scope=None, **scope_kwargs): + # type: (str, Optional[str], Optional[Scope], Any) -> Optional[str] """ Captures a message. @@ -880,9 +873,6 @@ def capture_message( :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). """ - if client is None: - return None - if level is None: level = "info" @@ -891,10 +881,10 @@ def capture_message( "level": level, } - return self.capture_event(event, client=client, scope=scope, **scope_kwargs) + return self.capture_event(event, scope=scope, **scope_kwargs) - def capture_exception(self, error=None, client=None, scope=None, **scope_kwargs): - # type: (Optional[Union[BaseException, ExcInfo]], Optional[sentry_sdk.Client], Optional[Scope], Any) -> Optional[str] + def capture_exception(self, error=None, scope=None, **scope_kwargs): + # type: (Optional[Union[BaseException, ExcInfo]], Optional[Scope], Any) -> Optional[str] """Captures an exception. :param error: An exception to capture. If `None`, `sys.exc_info()` will be used. @@ -910,20 +900,17 @@ def capture_exception(self, error=None, client=None, scope=None, **scope_kwargs) :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). """ - if client is None: - return None - if error is not None: exc_info = exc_info_from_error(error) else: exc_info = sys.exc_info() - event, hint = event_from_exception(exc_info, client_options=client.options) + event, hint = event_from_exception( + exc_info, client_options=Scope.get_client().options + ) try: - return self.capture_event( - event, hint=hint, client=client, scope=scope, **scope_kwargs - ) + return self.capture_event(event, hint=hint, scope=scope, **scope_kwargs) except Exception: self._capture_internal_exception(sys.exc_info()) @@ -970,9 +957,9 @@ def start_transaction( For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Transaction`. """ hub = kwargs.pop("hub", None) - client = kwargs.pop("client", None) + client = Scope.get_client() - configuration_instrumenter = client and client.options["instrumenter"] + configuration_instrumenter = client.options.get("instrumenter") if instrumenter != configuration_instrumenter: return NoOpSpan() @@ -1023,9 +1010,9 @@ def start_span(self, span=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs): For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Span`. """ - client = kwargs.get("client", None) + client = Scope.get_client() - configuration_instrumenter = client and client.options["instrumenter"] + configuration_instrumenter = client.options.get("instrumenter") if instrumenter != configuration_instrumenter: return NoOpSpan() @@ -1056,8 +1043,6 @@ def start_span(self, span=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs): 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) @@ -1093,14 +1078,14 @@ def continue_trace(self, environ_or_headers, op=None, name=None, source=None): def start_session(self, *args, **kwargs): # type: (*Any, **Any) -> None """Starts a new session.""" - client = kwargs.pop("client", None) session_mode = kwargs.pop("session_mode", "application") - self.end_session(client=client) + self.end_session() + client = Scope.get_client() self._session = Session( - release=client.options["release"] if client else None, - environment=client.options["environment"] if client else None, + release=client.options.get("release"), + environment=client.options.get("environment"), user=self._user, session_mode=session_mode, ) @@ -1108,15 +1093,12 @@ def start_session(self, *args, **kwargs): def end_session(self, *args, **kwargs): # type: (*Any, **Any) -> None """Ends the current session if there is one.""" - client = kwargs.pop("client", None) - session = self._session self._session = None if session is not None: session.close() - if client is not None: - client.capture_session(session) + Scope.get_client().capture_session(session) def stop_auto_session_tracking(self, *args, **kwargs): # type: (*Any, **Any) -> None @@ -1125,10 +1107,7 @@ def stop_auto_session_tracking(self, *args, **kwargs): This temporarily session tracking for the current scope when called. To resume session tracking call `resume_auto_session_tracking`. """ - client = kwargs.pop("client", None) - - self.end_session(client=client) - + self.end_session() self._force_auto_session_tracking = False def resume_auto_session_tracking(self):