From f2a4a3aedf53126e747149f05bf7e5eef2e6f761 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 29 Nov 2023 16:08:06 +0100 Subject: [PATCH 01/33] Moved get_integration from Hub to Client --- sentry_sdk/client.py | 19 +++++++++++++++++++ sentry_sdk/hub.py | 11 +---------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index 8aad751470..846fc0a7b6 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -43,7 +43,10 @@ from typing import Dict from typing import Optional from typing import Sequence + from typing import Type + from typing import Union + from sentry_sdk.integrations import Integration from sentry_sdk.scope import Scope from sentry_sdk._types import Event, Hint from sentry_sdk.session import Session @@ -653,6 +656,22 @@ def capture_session( else: self.session_flusher.add_session(session) + def get_integration( + self, name_or_class # type: Union[str, Type[Integration]] + ): + # type: (...) -> Any + """Returns the integration for this client by name or class. + If the client does not have that integration then `None` is returned. + """ + if isinstance(name_or_class, str): + integration_name = name_or_class + elif name_or_class.identifier is not None: + integration_name = name_or_class.identifier + else: + raise ValueError("Integration has no name") + + return self.integrations.get(integration_name) + def close( self, timeout=None, # type: Optional[float] diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index 2525dc56f1..5777704bb0 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -294,18 +294,9 @@ def get_integration( If the return value is not `None` the hub is guaranteed to have a client attached. """ - if isinstance(name_or_class, str): - integration_name = name_or_class - elif name_or_class.identifier is not None: - integration_name = name_or_class.identifier - else: - raise ValueError("Integration has no name") - client = self.client if client is not None: - rv = client.integrations.get(integration_name) - if rv is not None: - return rv + return client.get_integration(name_or_class) @property def client(self): From 4869365e344711032b8b5bf9cf369fc9dcf47478 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 29 Nov 2023 16:22:03 +0100 Subject: [PATCH 02/33] Moved add_breadcrumb from Hub to Scope --- sentry_sdk/hub.py | 29 ++++------------------------- sentry_sdk/scope.py | 42 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index 5777704bb0..2a4ad8041f 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -3,7 +3,7 @@ from contextlib import contextmanager -from sentry_sdk._compat import datetime_utcnow, with_metaclass +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 @@ -421,31 +421,10 @@ def add_breadcrumb(self, crumb=None, hint=None, **kwargs): logger.info("Dropped breadcrumb because no client bound") return - crumb = dict(crumb or ()) # type: Breadcrumb - crumb.update(kwargs) - if not crumb: - return - - hint = dict(hint or ()) # type: Hint - - if crumb.get("timestamp") is None: - crumb["timestamp"] = datetime_utcnow() - if crumb.get("type") is None: - crumb["type"] = "default" - - if client.options["before_breadcrumb"] is not None: - new_crumb = client.options["before_breadcrumb"](crumb, hint) - else: - new_crumb = crumb - - if new_crumb is not None: - scope._breadcrumbs.append(new_crumb) - else: - logger.info("before breadcrumb dropped breadcrumb (%s)", crumb) + kwargs["before_breadcrumb"] = client.options.get("before_breadcrumb") + kwargs["max_breadcrumbs"] = client.options.get("max_breadcrumbs") - max_breadcrumbs = client.options["max_breadcrumbs"] # type: int - while len(scope._breadcrumbs) > max_breadcrumbs: - scope._breadcrumbs.popleft() + scope.add_breadcrumb(crumb, hint, **kwargs) def start_span(self, span=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs): # type: (Optional[Span], str, Any) -> Span diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 5096eccce0..f3e5eb1f22 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -5,6 +5,7 @@ import uuid from sentry_sdk.attachments import Attachment +from sentry_sdk._compat import datetime_utcnow from sentry_sdk._functools import wraps from sentry_sdk.tracing_utils import ( Baggage, @@ -20,7 +21,7 @@ from sentry_sdk._types import TYPE_CHECKING from sentry_sdk.utils import logger, capture_internal_exceptions -from sentry_sdk.consts import FALSE_VALUES +from sentry_sdk.consts import DEFAULT_MAX_BREADCRUMBS, FALSE_VALUES if TYPE_CHECKING: @@ -36,6 +37,7 @@ from sentry_sdk._types import ( Breadcrumb, + BreadcrumbHint, Event, EventProcessor, ErrorProcessor, @@ -517,6 +519,44 @@ def add_attachment( ) ) + def add_breadcrumb(self, crumb=None, hint=None, **kwargs): + # type: (Optional[Breadcrumb], Optional[BreadcrumbHint], Any) -> None + """ + Adds a breadcrumb. + + :param crumb: Dictionary with the data as the sentry v7/v8 protocol expects. + + :param hint: An optional value that can be used by `before_breadcrumb` + to customize the breadcrumbs that are emitted. + """ + before_breadcrumb = kwargs.pop("before_breadcrumb") + max_breadcrumbs = kwargs.pop("max_breadcrumbs", DEFAULT_MAX_BREADCRUMBS) + + crumb = dict(crumb or ()) # type: Breadcrumb + crumb.update(kwargs) + if not crumb: + return + + hint = dict(hint or ()) # type: Hint + + if crumb.get("timestamp") is None: + crumb["timestamp"] = datetime_utcnow() + if crumb.get("type") is None: + crumb["type"] = "default" + + if before_breadcrumb is not None: + new_crumb = before_breadcrumb(crumb, hint) + else: + new_crumb = crumb + + if new_crumb is not None: + self._breadcrumbs.append(new_crumb) + else: + logger.info("before breadcrumb dropped breadcrumb (%s)", crumb) + + while len(self._breadcrumbs) > max_breadcrumbs: + self._breadcrumbs.popleft() + def add_event_processor( self, func # type: EventProcessor ): From e5f9e9d829405884f108eb07ff08a338e31230f3 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 29 Nov 2023 16:46:51 +0100 Subject: [PATCH 03/33] Moved session functions from Hub to Scope --- sentry_sdk/hub.py | 23 +++++--------------- sentry_sdk/scope.py | 53 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 17 deletions(-) diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index 2a4ad8041f..380686c013 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -15,7 +15,6 @@ BAGGAGE_HEADER_NAME, SENTRY_TRACE_HEADER_NAME, ) -from sentry_sdk.session import Session from sentry_sdk.tracing_utils import ( has_tracing_enabled, normalize_incoming_data, @@ -682,12 +681,9 @@ def start_session( ): # type: (...) -> None """Starts a new session.""" - self.end_session() client, scope = self._stack[-1] - scope._session = Session( - release=client.options["release"] if client else None, - environment=client.options["environment"] if client else None, - user=scope._user, + scope.start_session( + client=client, session_mode=session_mode, ) @@ -695,13 +691,7 @@ def end_session(self): # type: (...) -> None """Ends the current session if there is one.""" client, scope = self._stack[-1] - session = scope._session - self.scope._session = None - - if session is not None: - session.close() - if client is not None: - client.capture_session(session) + scope.end_session(client=client) def stop_auto_session_tracking(self): # type: (...) -> None @@ -710,9 +700,8 @@ 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`. """ - self.end_session() client, scope = self._stack[-1] - scope._force_auto_session_tracking = False + scope.stop_auto_session_tracking(client=client) def resume_auto_session_tracking(self): # type: (...) -> None @@ -720,8 +709,8 @@ def resume_auto_session_tracking(self): disabled earlier. This requires that generally automatic session tracking is enabled. """ - client, scope = self._stack[-1] - scope._force_auto_session_tracking = None + scope = self._stack[-1][1] + scope.resume_auto_session_tracking() def flush( self, diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index f3e5eb1f22..422a9cf633 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -557,6 +557,59 @@ def add_breadcrumb(self, crumb=None, hint=None, **kwargs): while len(self._breadcrumbs) > max_breadcrumbs: self._breadcrumbs.popleft() + 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._session = Session( + release=client.options["release"] if client else None, + environment=client.options["environment"] if client else None, + user=self._user, + session_mode=session_mode, + ) + + 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) + + def stop_auto_session_tracking(self, *args, **kwargs): + # type: (*Any, **Any) -> None + """Stops automatic session tracking. + + 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._force_auto_session_tracking = False + + def resume_auto_session_tracking(self): + # type: (...) -> None + """Resumes automatic session tracking for the current scope if + disabled earlier. This requires that generally automatic session + tracking is enabled. + """ + self._force_auto_session_tracking = None + def add_event_processor( self, func # type: EventProcessor ): From a338099466ddd9ea9a8146f7918888a1e9a66381 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 29 Nov 2023 16:55:26 +0100 Subject: [PATCH 04/33] Fixed import --- sentry_sdk/scope.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 422a9cf633..d1e4c53b46 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -6,7 +6,9 @@ from sentry_sdk.attachments import Attachment from sentry_sdk._compat import datetime_utcnow +from sentry_sdk.consts import DEFAULT_MAX_BREADCRUMBS, FALSE_VALUES from sentry_sdk._functools import wraps +from sentry_sdk.session import Session from sentry_sdk.tracing_utils import ( Baggage, extract_sentrytrace_data, @@ -21,9 +23,6 @@ from sentry_sdk._types import TYPE_CHECKING from sentry_sdk.utils import logger, capture_internal_exceptions -from sentry_sdk.consts import DEFAULT_MAX_BREADCRUMBS, FALSE_VALUES - - if TYPE_CHECKING: from typing import Any from typing import Dict @@ -48,7 +47,6 @@ from sentry_sdk.profiler import Profile from sentry_sdk.tracing import Span - from sentry_sdk.session import Session F = TypeVar("F", bound=Callable[..., Any]) T = TypeVar("T") From d900a1bda9a800e4bd70cfb7b8f9572717bfe2f6 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 29 Nov 2023 17:04:34 +0100 Subject: [PATCH 05/33] Fixed Python 2.7 syntax --- sentry_sdk/scope.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index d1e4c53b46..0fc836774a 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -555,11 +555,7 @@ def add_breadcrumb(self, crumb=None, hint=None, **kwargs): while len(self._breadcrumbs) > max_breadcrumbs: self._breadcrumbs.popleft() - def start_session( - self, - *args, - **kwargs, - ): + def start_session(self, *args, **kwargs): # type: (*Any, **Any) -> None """Starts a new session.""" client = kwargs.pop("client", None) From fe77d0499ec10077875e60ba112c25db8b7d2459 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 1 Dec 2023 09:12:24 +0100 Subject: [PATCH 06/33] Give the client to the scope function. Want to establish a pattern. --- sentry_sdk/hub.py | 3 +-- sentry_sdk/scope.py | 10 +++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index 380686c013..032ccd09e7 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -420,8 +420,7 @@ def add_breadcrumb(self, crumb=None, hint=None, **kwargs): logger.info("Dropped breadcrumb because no client bound") return - kwargs["before_breadcrumb"] = client.options.get("before_breadcrumb") - kwargs["max_breadcrumbs"] = client.options.get("max_breadcrumbs") + kwargs["client"] = client scope.add_breadcrumb(crumb, hint, **kwargs) diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 0fc836774a..8e9724b4c5 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -6,7 +6,7 @@ from sentry_sdk.attachments import Attachment from sentry_sdk._compat import datetime_utcnow -from sentry_sdk.consts import DEFAULT_MAX_BREADCRUMBS, FALSE_VALUES +from sentry_sdk.consts import FALSE_VALUES from sentry_sdk._functools import wraps from sentry_sdk.session import Session from sentry_sdk.tracing_utils import ( @@ -527,8 +527,12 @@ 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. """ - before_breadcrumb = kwargs.pop("before_breadcrumb") - max_breadcrumbs = kwargs.pop("max_breadcrumbs", DEFAULT_MAX_BREADCRUMBS) + client = kwargs.pop("client", None) + if client is None: + return + + before_breadcrumb = client.options.get("before_breadcrumb") + max_breadcrumbs = client.options.get("max_breadcrumbs") crumb = dict(crumb or ()) # type: Breadcrumb crumb.update(kwargs) From b15613a22e25e8d183d8517504fe6ac625a57f60 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 1 Dec 2023 11:00:36 +0100 Subject: [PATCH 07/33] Moved capture_* functions from Hub to Client --- sentry_sdk/client.py | 91 +++++++++++++++++++++++++++++++++++++++++++- sentry_sdk/hub.py | 82 ++++++++++++++++++--------------------- 2 files changed, 128 insertions(+), 45 deletions(-) diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index 846fc0a7b6..3e81454639 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -1,5 +1,7 @@ from importlib import import_module +import copy import os +import sys import uuid import random import socket @@ -9,6 +11,8 @@ capture_internal_exceptions, current_stacktrace, disable_capture_event, + event_from_exception, + exc_info_from_error, format_timestamp, get_sdk_name, get_type_name, @@ -48,7 +52,7 @@ from sentry_sdk.integrations import Integration from sentry_sdk.scope import Scope - from sentry_sdk._types import Event, Hint + from sentry_sdk._types import Event, ExcInfo, Hint from sentry_sdk.session import Session @@ -143,6 +147,24 @@ def _get_options(*args, **kwargs): return rv +def _update_scope(base, scope_change, scope_kwargs): + # type: (Scope, Optional[Any], Dict[str, Any]) -> Scope + if scope_change and scope_kwargs: + raise TypeError("cannot provide scope and kwargs") + if scope_change is not None: + final_scope = copy.copy(base) + if callable(scope_change): + scope_change(final_scope) + else: + final_scope.update_from_scope(scope_change) + elif scope_kwargs: + final_scope = copy.copy(base) + final_scope.update_from_kwargs(**scope_kwargs) + else: + final_scope = base + return final_scope + + try: # Python 3.6+ module_not_found_error = ModuleNotFoundError @@ -544,6 +566,7 @@ def capture_event( event, # type: Event hint=None, # type: Optional[Hint] scope=None, # type: Optional[Scope] + **scope_args # type: Any ): # type: (...) -> Optional[str] """Captures an event. @@ -555,11 +578,17 @@ def capture_event( :param scope: An optional scope to use for determining whether this event should be captured. + :param scope_args: For supported `**scope_args` see + :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + :returns: An event ID. May be `None` if there is no DSN set or of if the SDK decided to discard the event for other reasons. In such situations setting `debug=True` on `init()` may help. """ if disable_capture_event.get(False): return None + top_scope = scope_args.pop("top_scope") + scope = _update_scope(top_scope, scope, scope_args) + if hint is None: hint = {} event_id = event.get("event_id") @@ -647,6 +676,66 @@ def capture_event( return event_id + def capture_message(self, message, level=None, scope=None, **scope_args): + # type: (str, Optional[str], Optional[Scope], Any) -> Optional[str] + """ + Captures a message. + + :param message: The string to send as the message. + + :param level: If no level is provided, the default level is `info`. + + :param scope: An optional :py:class:`sentry_sdk.Scope` to use. + + :param scope_args: For supported `**scope_args` see + :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + + :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). + """ + if level is None: + level = "info" + + return self.capture_event( + {"message": message, "level": level}, scope=scope, **scope_args + ) + + def capture_exception(self, error=None, scope=None, **scope_args): + # type: (Optional[Union[BaseException, ExcInfo]], Optional[Scope], Any) -> Optional[str] + """Captures an exception. + + :param error: An exception to catch. If `None`, `sys.exc_info()` will be used. + + :param scope_args: For supported `**scope_args` see + :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + + :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). + """ + 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=self.options) + + try: + return self.capture_event(event, hint=hint, scope=scope, **scope_args) + except Exception: + self._capture_internal_exception(sys.exc_info()) + + return None + + def _capture_internal_exception( + self, exc_info # type: Any + ): + # type: (...) -> Any + """ + Capture an exception that is likely caused by a bug in the SDK + itself. + + These exceptions do not end up in Sentry and are just logged instead. + """ + logger.error("Internal error in sentry_sdk", exc_info=exc_info) + def capture_session( self, session # type: Session ): diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index 032ccd09e7..5c23495371 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -21,8 +21,6 @@ ) from sentry_sdk.utils import ( - exc_info_from_error, - event_from_exception, logger, ContextVar, ) @@ -65,24 +63,6 @@ def overload(x): _local = ContextVar("sentry_current_hub") -def _update_scope(base, scope_change, scope_kwargs): - # type: (Scope, Optional[Any], Dict[str, Any]) -> Scope - if scope_change and scope_kwargs: - raise TypeError("cannot provide scope and kwargs") - if scope_change is not None: - final_scope = copy.copy(base) - if callable(scope_change): - scope_change(final_scope) - else: - final_scope.update_from_scope(scope_change) - elif scope_kwargs: - final_scope = copy.copy(base) - final_scope.update_from_kwargs(**scope_kwargs) - else: - final_scope = base - return final_scope - - def _should_send_default_pii(): # type: () -> bool client = Hub.current.client @@ -333,20 +313,26 @@ def capture_event(self, event, hint=None, scope=None, **scope_args): :py:meth:`sentry_sdk.Scope.update_from_kwargs`. """ client, top_scope = self._stack[-1] - scope = _update_scope(top_scope, scope, scope_args) - if client is not None: - is_transaction = event.get("type") == "transaction" - rv = client.capture_event(event, hint, scope) - if rv is not None and not is_transaction: - self._last_event_id = rv - return rv - return None + if client is None: + return None + + scope_args["top_scope"] = top_scope + + last_event_id = client.capture_event(event, hint, scope, **scope_args) + + is_transaction = event.get("type") == "transaction" + if last_event_id is not None and not is_transaction: + self._last_event_id = last_event_id + + return last_event_id def capture_message(self, message, level=None, scope=None, **scope_args): # type: (str, Optional[str], Optional[Scope], Any) -> Optional[str] """ Captures a message. + Alias of :py:meth:`sentry_sdk.Client.capture_message`. + :param message: The string to send as the message. :param level: If no level is provided, the default level is `info`. @@ -358,18 +344,27 @@ def capture_message(self, message, level=None, scope=None, **scope_args): :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). """ - if self.client is None: + client, top_scope = self._stack[-1] + if client is None: return None - if level is None: - level = "info" - return self.capture_event( - {"message": message, "level": level}, scope=scope, **scope_args + + scope_args["top_scope"] = top_scope + + last_event_id = client.capture_message( + message, level=level, scope=scope, **scope_args ) + if last_event_id is not None: + self._last_event_id = last_event_id + + return last_event_id + def capture_exception(self, error=None, scope=None, **scope_args): # type: (Optional[Union[BaseException, ExcInfo]], Optional[Scope], Any) -> Optional[str] """Captures an exception. + Alias of :py:meth:`sentry_sdk.Client.capture_exception`. + :param error: An exception to catch. If `None`, `sys.exc_info()` will be used. :param scope_args: For supported `**scope_args` see @@ -377,21 +372,18 @@ def capture_exception(self, error=None, scope=None, **scope_args): :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). """ - client = self.client + client, top_scope = self._stack[-1] 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) - try: - return self.capture_event(event, hint=hint, scope=scope, **scope_args) - except Exception: - self._capture_internal_exception(sys.exc_info()) + scope_args["top_scope"] = top_scope - return None + last_event_id = client.capture_exception(error, scope=scope, **scope_args) + + if last_event_id is not None: + self._last_event_id = last_event_id + + return last_event_id def _capture_internal_exception( self, exc_info # type: Any @@ -401,6 +393,8 @@ def _capture_internal_exception( Capture an exception that is likely caused by a bug in the SDK itself. + Duplicated in :py:meth:`sentry_sdk.Client._capture_internal_exception`. + These exceptions do not end up in Sentry and are just logged instead. """ logger.error("Internal error in sentry_sdk", exc_info=exc_info) From 8331bd08ad6416344286235c19d1c076602b982f Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 1 Dec 2023 11:01:47 +0100 Subject: [PATCH 08/33] Renamed scope_args to scope_kwargs --- sentry_sdk/api.py | 12 ++++++------ sentry_sdk/client.py | 20 ++++++++++---------- sentry_sdk/hub.py | 24 ++++++++++++------------ 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/sentry_sdk/api.py b/sentry_sdk/api.py index f0c6a87432..741a418f0b 100644 --- a/sentry_sdk/api.py +++ b/sentry_sdk/api.py @@ -82,10 +82,10 @@ def capture_event( event, # type: Event hint=None, # type: Optional[Hint] scope=None, # type: Optional[Any] - **scope_args # type: Any + **scope_wkargs # type: Any ): # type: (...) -> Optional[str] - return Hub.current.capture_event(event, hint, scope=scope, **scope_args) + return Hub.current.capture_event(event, hint, scope=scope, **scope_wkargs) @hubmethod @@ -93,20 +93,20 @@ def capture_message( message, # type: str level=None, # type: Optional[str] scope=None, # type: Optional[Any] - **scope_args # type: Any + **scope_wkargs # type: Any ): # type: (...) -> Optional[str] - return Hub.current.capture_message(message, level, scope=scope, **scope_args) + return Hub.current.capture_message(message, level, scope=scope, **scope_wkargs) @hubmethod def capture_exception( error=None, # type: Optional[Union[BaseException, ExcInfo]] scope=None, # type: Optional[Any] - **scope_args # type: Any + **scope_wkargs # type: Any ): # type: (...) -> Optional[str] - return Hub.current.capture_exception(error, scope=scope, **scope_args) + return Hub.current.capture_exception(error, scope=scope, **scope_wkargs) @hubmethod diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index 3e81454639..c307b40651 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -566,7 +566,7 @@ def capture_event( event, # type: Event hint=None, # type: Optional[Hint] scope=None, # type: Optional[Scope] - **scope_args # type: Any + **scope_wkargs # type: Any ): # type: (...) -> Optional[str] """Captures an event. @@ -578,7 +578,7 @@ def capture_event( :param scope: An optional scope to use for determining whether this event should be captured. - :param scope_args: For supported `**scope_args` see + :param scope_wkargs: For supported `**scope_wkargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. :returns: An event ID. May be `None` if there is no DSN set or of if the SDK decided to discard the event for other reasons. In such situations setting `debug=True` on `init()` may help. @@ -586,8 +586,8 @@ def capture_event( if disable_capture_event.get(False): return None - top_scope = scope_args.pop("top_scope") - scope = _update_scope(top_scope, scope, scope_args) + top_scope = scope_wkargs.pop("top_scope") + scope = _update_scope(top_scope, scope, scope_wkargs) if hint is None: hint = {} @@ -676,7 +676,7 @@ def capture_event( return event_id - def capture_message(self, message, level=None, scope=None, **scope_args): + def capture_message(self, message, level=None, scope=None, **scope_wkargs): # type: (str, Optional[str], Optional[Scope], Any) -> Optional[str] """ Captures a message. @@ -687,7 +687,7 @@ def capture_message(self, message, level=None, scope=None, **scope_args): :param scope: An optional :py:class:`sentry_sdk.Scope` to use. - :param scope_args: For supported `**scope_args` see + :param scope_wkargs: For supported `**scope_wkargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). @@ -696,16 +696,16 @@ def capture_message(self, message, level=None, scope=None, **scope_args): level = "info" return self.capture_event( - {"message": message, "level": level}, scope=scope, **scope_args + {"message": message, "level": level}, scope=scope, **scope_wkargs ) - def capture_exception(self, error=None, scope=None, **scope_args): + def capture_exception(self, error=None, scope=None, **scope_wkargs): # type: (Optional[Union[BaseException, ExcInfo]], Optional[Scope], Any) -> Optional[str] """Captures an exception. :param error: An exception to catch. If `None`, `sys.exc_info()` will be used. - :param scope_args: For supported `**scope_args` see + :param scope_wkargs: For supported `**scope_wkargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). @@ -718,7 +718,7 @@ def capture_exception(self, error=None, scope=None, **scope_args): event, hint = event_from_exception(exc_info, client_options=self.options) try: - return self.capture_event(event, hint=hint, scope=scope, **scope_args) + return self.capture_event(event, hint=hint, scope=scope, **scope_wkargs) except Exception: self._capture_internal_exception(sys.exc_info()) diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index 5c23495371..fe64d7c959 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -302,23 +302,23 @@ def bind_client( top = self._stack[-1] self._stack[-1] = (new, top[1]) - def capture_event(self, event, hint=None, scope=None, **scope_args): + def capture_event(self, event, hint=None, scope=None, **scope_wkargs): # type: (Event, Optional[Hint], Optional[Scope], Any) -> Optional[str] """ Captures an event. Alias of :py:meth:`sentry_sdk.Client.capture_event`. - :param scope_args: For supported `**scope_args` see + :param scope_wkargs: For supported `**scope_wkargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. """ client, top_scope = self._stack[-1] if client is None: return None - scope_args["top_scope"] = top_scope + scope_wkargs["top_scope"] = top_scope - last_event_id = client.capture_event(event, hint, scope, **scope_args) + last_event_id = client.capture_event(event, hint, scope, **scope_wkargs) is_transaction = event.get("type") == "transaction" if last_event_id is not None and not is_transaction: @@ -326,7 +326,7 @@ def capture_event(self, event, hint=None, scope=None, **scope_args): return last_event_id - def capture_message(self, message, level=None, scope=None, **scope_args): + def capture_message(self, message, level=None, scope=None, **scope_wkargs): # type: (str, Optional[str], Optional[Scope], Any) -> Optional[str] """ Captures a message. @@ -339,7 +339,7 @@ def capture_message(self, message, level=None, scope=None, **scope_args): :param scope: An optional :py:class:`sentry_sdk.Scope` to use. - :param scope_args: For supported `**scope_args` see + :param scope_wkargs: For supported `**scope_wkargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). @@ -348,10 +348,10 @@ def capture_message(self, message, level=None, scope=None, **scope_args): if client is None: return None - scope_args["top_scope"] = top_scope + scope_wkargs["top_scope"] = top_scope last_event_id = client.capture_message( - message, level=level, scope=scope, **scope_args + message, level=level, scope=scope, **scope_wkargs ) if last_event_id is not None: @@ -359,7 +359,7 @@ def capture_message(self, message, level=None, scope=None, **scope_args): return last_event_id - def capture_exception(self, error=None, scope=None, **scope_args): + def capture_exception(self, error=None, scope=None, **scope_wkargs): # type: (Optional[Union[BaseException, ExcInfo]], Optional[Scope], Any) -> Optional[str] """Captures an exception. @@ -367,7 +367,7 @@ def capture_exception(self, error=None, scope=None, **scope_args): :param error: An exception to catch. If `None`, `sys.exc_info()` will be used. - :param scope_args: For supported `**scope_args` see + :param scope_wkargs: For supported `**scope_wkargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). @@ -376,9 +376,9 @@ def capture_exception(self, error=None, scope=None, **scope_args): if client is None: return None - scope_args["top_scope"] = top_scope + scope_wkargs["top_scope"] = top_scope - last_event_id = client.capture_exception(error, scope=scope, **scope_args) + last_event_id = client.capture_exception(error, scope=scope, **scope_wkargs) if last_event_id is not None: self._last_event_id = last_event_id From ce1759f5a8c48a6537f3bd7dad1cdee3a0e661f7 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 1 Dec 2023 11:10:04 +0100 Subject: [PATCH 09/33] oops --- sentry_sdk/api.py | 12 ++++++------ sentry_sdk/client.py | 20 ++++++++++---------- sentry_sdk/hub.py | 24 ++++++++++++------------ 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/sentry_sdk/api.py b/sentry_sdk/api.py index 741a418f0b..ffa525ca66 100644 --- a/sentry_sdk/api.py +++ b/sentry_sdk/api.py @@ -82,10 +82,10 @@ def capture_event( event, # type: Event hint=None, # type: Optional[Hint] scope=None, # type: Optional[Any] - **scope_wkargs # type: Any + **scope_kwargs # type: Any ): # type: (...) -> Optional[str] - return Hub.current.capture_event(event, hint, scope=scope, **scope_wkargs) + return Hub.current.capture_event(event, hint, scope=scope, **scope_kwargs) @hubmethod @@ -93,20 +93,20 @@ def capture_message( message, # type: str level=None, # type: Optional[str] scope=None, # type: Optional[Any] - **scope_wkargs # type: Any + **scope_kwargs # type: Any ): # type: (...) -> Optional[str] - return Hub.current.capture_message(message, level, scope=scope, **scope_wkargs) + return Hub.current.capture_message(message, level, scope=scope, **scope_kwargs) @hubmethod def capture_exception( error=None, # type: Optional[Union[BaseException, ExcInfo]] scope=None, # type: Optional[Any] - **scope_wkargs # type: Any + **scope_kwargs # type: Any ): # type: (...) -> Optional[str] - return Hub.current.capture_exception(error, scope=scope, **scope_wkargs) + return Hub.current.capture_exception(error, scope=scope, **scope_kwargs) @hubmethod diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index c307b40651..b57007fd87 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -566,7 +566,7 @@ def capture_event( event, # type: Event hint=None, # type: Optional[Hint] scope=None, # type: Optional[Scope] - **scope_wkargs # type: Any + **scope_kwargs # type: Any ): # type: (...) -> Optional[str] """Captures an event. @@ -578,7 +578,7 @@ def capture_event( :param scope: An optional scope to use for determining whether this event should be captured. - :param scope_wkargs: For supported `**scope_wkargs` see + :param scope_kwargs: For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. :returns: An event ID. May be `None` if there is no DSN set or of if the SDK decided to discard the event for other reasons. In such situations setting `debug=True` on `init()` may help. @@ -586,8 +586,8 @@ def capture_event( if disable_capture_event.get(False): return None - top_scope = scope_wkargs.pop("top_scope") - scope = _update_scope(top_scope, scope, scope_wkargs) + top_scope = scope_kwargs.pop("top_scope") + scope = _update_scope(top_scope, scope, scope_kwargs) if hint is None: hint = {} @@ -676,7 +676,7 @@ def capture_event( return event_id - def capture_message(self, message, level=None, scope=None, **scope_wkargs): + def capture_message(self, message, level=None, scope=None, **scope_kwargs): # type: (str, Optional[str], Optional[Scope], Any) -> Optional[str] """ Captures a message. @@ -687,7 +687,7 @@ def capture_message(self, message, level=None, scope=None, **scope_wkargs): :param scope: An optional :py:class:`sentry_sdk.Scope` to use. - :param scope_wkargs: For supported `**scope_wkargs` see + :param scope_kwargs: For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). @@ -696,16 +696,16 @@ def capture_message(self, message, level=None, scope=None, **scope_wkargs): level = "info" return self.capture_event( - {"message": message, "level": level}, scope=scope, **scope_wkargs + {"message": message, "level": level}, scope=scope, **scope_kwargs ) - def capture_exception(self, error=None, scope=None, **scope_wkargs): + 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 catch. If `None`, `sys.exc_info()` will be used. - :param scope_wkargs: For supported `**scope_wkargs` see + :param scope_kwargs: For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). @@ -718,7 +718,7 @@ def capture_exception(self, error=None, scope=None, **scope_wkargs): event, hint = event_from_exception(exc_info, client_options=self.options) try: - return self.capture_event(event, hint=hint, scope=scope, **scope_wkargs) + return self.capture_event(event, hint=hint, scope=scope, **scope_kwargs) except Exception: self._capture_internal_exception(sys.exc_info()) diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index fe64d7c959..41c062c918 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -302,23 +302,23 @@ def bind_client( top = self._stack[-1] self._stack[-1] = (new, top[1]) - def capture_event(self, event, hint=None, scope=None, **scope_wkargs): + def capture_event(self, event, hint=None, scope=None, **scope_kwargs): # type: (Event, Optional[Hint], Optional[Scope], Any) -> Optional[str] """ Captures an event. Alias of :py:meth:`sentry_sdk.Client.capture_event`. - :param scope_wkargs: For supported `**scope_wkargs` see + :param scope_kwargs: For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. """ client, top_scope = self._stack[-1] if client is None: return None - scope_wkargs["top_scope"] = top_scope + scope_kwargs["top_scope"] = top_scope - last_event_id = client.capture_event(event, hint, scope, **scope_wkargs) + last_event_id = client.capture_event(event, hint, scope, **scope_kwargs) is_transaction = event.get("type") == "transaction" if last_event_id is not None and not is_transaction: @@ -326,7 +326,7 @@ def capture_event(self, event, hint=None, scope=None, **scope_wkargs): return last_event_id - def capture_message(self, message, level=None, scope=None, **scope_wkargs): + def capture_message(self, message, level=None, scope=None, **scope_kwargs): # type: (str, Optional[str], Optional[Scope], Any) -> Optional[str] """ Captures a message. @@ -339,7 +339,7 @@ def capture_message(self, message, level=None, scope=None, **scope_wkargs): :param scope: An optional :py:class:`sentry_sdk.Scope` to use. - :param scope_wkargs: For supported `**scope_wkargs` see + :param scope_kwargs: For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). @@ -348,10 +348,10 @@ def capture_message(self, message, level=None, scope=None, **scope_wkargs): if client is None: return None - scope_wkargs["top_scope"] = top_scope + scope_kwargs["top_scope"] = top_scope last_event_id = client.capture_message( - message, level=level, scope=scope, **scope_wkargs + message, level=level, scope=scope, **scope_kwargs ) if last_event_id is not None: @@ -359,7 +359,7 @@ def capture_message(self, message, level=None, scope=None, **scope_wkargs): return last_event_id - def capture_exception(self, error=None, scope=None, **scope_wkargs): + def capture_exception(self, error=None, scope=None, **scope_kwargs): # type: (Optional[Union[BaseException, ExcInfo]], Optional[Scope], Any) -> Optional[str] """Captures an exception. @@ -367,7 +367,7 @@ def capture_exception(self, error=None, scope=None, **scope_wkargs): :param error: An exception to catch. If `None`, `sys.exc_info()` will be used. - :param scope_wkargs: For supported `**scope_wkargs` see + :param scope_kwargs: For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). @@ -376,9 +376,9 @@ def capture_exception(self, error=None, scope=None, **scope_wkargs): if client is None: return None - scope_wkargs["top_scope"] = top_scope + scope_kwargs["top_scope"] = top_scope - last_event_id = client.capture_exception(error, scope=scope, **scope_wkargs) + last_event_id = client.capture_exception(error, scope=scope, **scope_kwargs) if last_event_id is not None: self._last_event_id = last_event_id From 9a08b6efc14efe530579d0486b492cefc7b07f03 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 1 Dec 2023 11:27:21 +0100 Subject: [PATCH 10/33] preserve old behavior when called from client directly (not from hub) --- sentry_sdk/client.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index b57007fd87..a9f87e0a01 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -586,8 +586,9 @@ def capture_event( if disable_capture_event.get(False): return None - top_scope = scope_kwargs.pop("top_scope") - scope = _update_scope(top_scope, scope, scope_kwargs) + if scope_kwargs is not None and "top_scope" in scope_kwargs: + top_scope = scope_kwargs.pop("top_scope") + scope = _update_scope(top_scope, scope, scope_kwargs) if hint is None: hint = {} From f9b8d5aea989b14cb0d97ee4f6d2755c770e9af8 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 1 Dec 2023 12:23:51 +0100 Subject: [PATCH 11/33] update test --- tests/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_client.py b/tests/test_client.py index 5a7a5cff16..a99bb05765 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -365,7 +365,7 @@ def raise_it(exc_info): reraise(*exc_info) sentry_init(ignore_errors=[ZeroDivisionError], transport=_TestTransport()) - Hub.current._capture_internal_exception = raise_it + Hub.current.client._capture_internal_exception = raise_it def e(exc): try: From 232ce239861c1d590e60dc49e18e0a4197604429 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 1 Dec 2023 13:43:48 +0100 Subject: [PATCH 12/33] Moved trancing related functions from Hub to Scope --- sentry_sdk/hub.py | 52 +++----------------------- sentry_sdk/scope.py | 90 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 87 insertions(+), 55 deletions(-) diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index 41c062c918..ad74690a63 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -12,13 +12,8 @@ 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.tracing_utils import normalize_incoming_data from sentry_sdk.utils import ( logger, @@ -723,25 +718,14 @@ 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() + return self.scope.get_traceparent(client=self.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() + baggage = self.scope.get_baggage(client=self.client) if baggage is not None: return baggage.serialize() @@ -756,18 +740,8 @@ def iter_trace_propagation_headers(self, span=None): 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 - 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 self.scope.iter_trace_propagation_headers(span=span, client=client) def trace_propagation_meta(self, span=None): # type: (Optional[Span]) -> str @@ -780,23 +754,7 @@ 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 + return self.scope.trace_propagation_meta(span=span) GLOBAL_HUB = Hub() diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 8e9724b4c5..d0bb321cfd 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -25,12 +25,13 @@ 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 @@ -244,11 +245,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 trashparent 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 @@ -258,8 +270,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 @@ -288,6 +310,35 @@ def get_trace_context(self): return trace_context + def trace_propagation_meta(self, span=None): + # type: (Optional[Span]) -> str + """ + Return meta tags which should be injected into HTML templates + to allow propagation of trace information. + """ + if span is not None: + logger.warning( + "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 + def iter_headers(self): # type: () -> Iterator[Tuple[str, str]] """ @@ -303,6 +354,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.""" From fc95050382a9b430672210c5503c6a7bb4cf6f21 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 1 Dec 2023 13:44:18 +0100 Subject: [PATCH 13/33] Sort imports --- sentry_sdk/hub.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index ad74690a63..74d0fc5c31 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -23,18 +23,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 ( From b0b3cec37ca91d126af31841134172f7770954ad Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 4 Dec 2023 13:13:28 +0100 Subject: [PATCH 14/33] Fixed some tests --- sentry_sdk/hub.py | 13 ++++++++----- sentry_sdk/scope.py | 11 +++++++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index 74d0fc5c31..b0a40d72b6 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -718,14 +718,16 @@ def get_traceparent(self): """ Returns the traceparent either from the active span or from the scope. """ - return self.scope.get_traceparent(client=self.client) + 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. """ - baggage = self.scope.get_baggage(client=self.client) + client, scope = self._stack[-1] + baggage = scope.get_baggage(client=client) if baggage is not None: return baggage.serialize() @@ -739,9 +741,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] + client, scope = self._stack[-1] - return self.scope.iter_trace_propagation_headers(span=span, client=client) + return scope.iter_trace_propagation_headers(span=span, client=client) def trace_propagation_meta(self, span=None): # type: (Optional[Span]) -> str @@ -754,7 +756,8 @@ def trace_propagation_meta(self, span=None): "The parameter `span` in trace_propagation_meta() is deprecated and will be removed in the future." ) - return self.scope.trace_propagation_meta(span=span) + 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 d0bb321cfd..e9a44e171c 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -310,27 +310,30 @@ def get_trace_context(self): return trace_context - def trace_propagation_meta(self, span=None): - # type: (Optional[Span]) -> str + 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() + sentry_trace = self.get_traceparent(client=client) if sentry_trace is not None: meta += '' % ( SENTRY_TRACE_HEADER_NAME, sentry_trace, ) - baggage = self.get_baggage() + baggage = self.get_baggage(client=client).serialize() if baggage is not None: meta += '' % ( BAGGAGE_HEADER_NAME, From dec7dbf2b4474190476431d7c7d5ffb83ed68455 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 4 Dec 2023 13:18:42 +0100 Subject: [PATCH 15/33] Fixed test --- sentry_sdk/scope.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index e9a44e171c..17df5d6da3 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -333,11 +333,11 @@ def trace_propagation_meta(self, *args, **kwargs): sentry_trace, ) - baggage = self.get_baggage(client=client).serialize() + baggage = self.get_baggage(client=client) if baggage is not None: meta += '' % ( BAGGAGE_HEADER_NAME, - baggage, + baggage.serialize(), ) return meta From cfd3d54c6c6ce8d6c0d83a6caf33b6140c69fe2f Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 4 Dec 2023 15:33:23 +0100 Subject: [PATCH 16/33] Trying to fix broken tests because some something was released that breaks them --- tox.ini | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tox.ini b/tox.ini index 46477750e9..df970dd7b4 100644 --- a/tox.ini +++ b/tox.ini @@ -241,7 +241,7 @@ deps = linters: werkzeug<2.3.0 # Common - {py3.6,py3.7,py3.8,py3.9,py3.10,py3.11,py3.12}-common: pytest-asyncio + {py3.6,py3.7,py3.8,py3.9,py3.10,py3.11,py3.12}-common: pytest-asyncio<=0.21.1 # See https://github.com/pytest-dev/pytest/issues/9621 # and https://github.com/pytest-dev/pytest-forked/issues/67 # for justification of the upper bound on pytest @@ -265,11 +265,11 @@ deps = arq-v0.23: pydantic<2 arq-latest: arq arq: fakeredis>=2.2.0,<2.8 - arq: pytest-asyncio + arq: pytest-asyncio<=0.21.1 arq: async-timeout # Asgi - asgi: pytest-asyncio + asgi: pytest-asyncio<=0.21.1 asgi: async-asgi-testclient # Asyncpg @@ -329,10 +329,10 @@ deps = django-v{1.8,1.11,2.0}: pytest-django<4.0 django-v{2.2,3.0,3.2,4.0,4.1,4.2,5.0}: pytest-django django-v{4.0,4.1,4.2,5.0}: djangorestframework - django-v{4.0,4.1,4.2,5.0}: pytest-asyncio + django-v{4.0,4.1,4.2,5.0}: pytest-asyncio<=0.21.1 django-v{4.0,4.1,4.2,5.0}: Werkzeug django-latest: djangorestframework - django-latest: pytest-asyncio + django-latest: pytest-asyncio<=0.21.1 django-latest: pytest-django django-latest: Werkzeug django-latest: channels[daphne] @@ -360,7 +360,7 @@ deps = # FastAPI fastapi: httpx fastapi: anyio<4.0.0 # thats a dep of httpx - fastapi: pytest-asyncio + fastapi: pytest-asyncio<=0.21.1 fastapi: python-multipart fastapi: requests fastapi-v{0.79}: fastapi~=0.79.0 @@ -407,7 +407,7 @@ deps = grpc: protobuf grpc: mypy-protobuf grpc: types-protobuf - grpc: pytest-asyncio + grpc: pytest-asyncio<=0.21.1 grpc-v1.21: grpcio-tools~=1.21.0 grpc-v1.30: grpcio-tools~=1.30.0 grpc-v1.40: grpcio-tools~=1.40.0 @@ -466,7 +466,7 @@ deps = # Quart quart: quart-auth - quart: pytest-asyncio + quart: pytest-asyncio<=0.21.1 quart-v0.16: blinker<1.6 quart-v0.16: jinja2<3.1.0 quart-v0.16: Werkzeug<2.1.0 @@ -478,7 +478,7 @@ deps = # Redis redis: fakeredis!=1.7.4 - {py3.7,py3.8,py3.9,py3.10,py3.11}-redis: pytest-asyncio + {py3.7,py3.8,py3.9,py3.10,py3.11}-redis: pytest-asyncio<=0.21.1 redis-v3: redis~=3.0 redis-v4: redis~=4.0 redis-v5: redis~=5.0 @@ -520,7 +520,7 @@ deps = sanic-latest: sanic # Starlette - starlette: pytest-asyncio + starlette: pytest-asyncio<=0.21.1 starlette: python-multipart starlette: requests starlette: httpx @@ -534,7 +534,7 @@ deps = starlette-latest: starlette # Starlite - starlite: pytest-asyncio + starlite: pytest-asyncio<=0.21.1 starlite: python-multipart starlite: requests starlite: cryptography From ea13b55a111c8c4735651e0ca7f83afdde91be13 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 4 Dec 2023 16:04:10 +0100 Subject: [PATCH 17/33] fix aiohttp tests --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index df970dd7b4..a5dc9b7645 100644 --- a/tox.ini +++ b/tox.ini @@ -252,6 +252,8 @@ deps = aiohttp-v3.8: aiohttp~=3.8.0 aiohttp-latest: aiohttp aiohttp: pytest-aiohttp + aiohttp-v3.8: pytest-asyncio<=0.21.1 + aiohttp-latest: pytest-asyncio<=0.21.1 # Ariadne ariadne-v0.20: ariadne~=0.20.0 From bb3250a564b47306a6a9c96c6b85a427d74e5e2f Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 11 Dec 2023 15:19:06 +0100 Subject: [PATCH 18/33] _update_scope belongs into the scope not the client --- sentry_sdk/client.py | 20 +------------------- sentry_sdk/scope.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index a9f87e0a01..1f3545cc0f 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -1,5 +1,4 @@ from importlib import import_module -import copy import os import sys import uuid @@ -7,6 +6,7 @@ import socket from sentry_sdk._compat import datetime_utcnow, string_types, text_type, iteritems +from sentry_sdk.scope import _update_scope from sentry_sdk.utils import ( capture_internal_exceptions, current_stacktrace, @@ -147,24 +147,6 @@ def _get_options(*args, **kwargs): return rv -def _update_scope(base, scope_change, scope_kwargs): - # type: (Scope, Optional[Any], Dict[str, Any]) -> Scope - if scope_change and scope_kwargs: - raise TypeError("cannot provide scope and kwargs") - if scope_change is not None: - final_scope = copy.copy(base) - if callable(scope_change): - scope_change(final_scope) - else: - final_scope.update_from_scope(scope_change) - elif scope_kwargs: - final_scope = copy.copy(base) - final_scope.update_from_kwargs(**scope_kwargs) - else: - final_scope = base - return final_scope - - try: # Python 3.6+ module_not_found_error = ModuleNotFoundError diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 8e9724b4c5..5a0a69af39 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -81,6 +81,24 @@ def wrapper(self, *args, **kwargs): return wrapper # type: ignore +def _update_scope(base, scope_change, scope_kwargs): + # type: (Scope, Optional[Any], Dict[str, Any]) -> Scope + if scope_change and scope_kwargs: + raise TypeError("cannot provide scope and kwargs") + if scope_change is not None: + final_scope = copy(base) + if callable(scope_change): + scope_change(final_scope) + else: + final_scope.update_from_scope(scope_change) + elif scope_kwargs: + final_scope = copy(base) + final_scope.update_from_kwargs(**scope_kwargs) + else: + final_scope = base + return final_scope + + class Scope(object): """The scope holds extra information that should be sent with all events that belong to it. From 3a22dd686a46dcf2f43a730c2daa7e0c36dfd9ff Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 11 Dec 2023 16:11:37 +0100 Subject: [PATCH 19/33] Made top_scope separate parameter --- sentry_sdk/client.py | 48 +++++++++++++++++++++++++++++++++----------- sentry_sdk/hub.py | 16 +++++++-------- sentry_sdk/scope.py | 4 ++++ 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index 1f3545cc0f..91bfc459be 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -548,6 +548,7 @@ def capture_event( event, # type: Event hint=None, # type: Optional[Hint] scope=None, # type: Optional[Scope] + top_scope=None, # type: Optional[Scope] **scope_kwargs # type: Any ): # type: (...) -> Optional[str] @@ -557,8 +558,9 @@ def capture_event( :param hint: Contains metadata about the event that can be read from `before_send`, such as the original exception object or a HTTP request object. - :param scope: An optional scope to use for determining whether this event - should be captured. + :param scope: An optional scope to use for determining whether this event should be captured. + + :param top_scope: An optional top scope that should also be merged into the scope to be applied to the event. :param scope_kwargs: For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. @@ -568,8 +570,7 @@ def capture_event( if disable_capture_event.get(False): return None - if scope_kwargs is not None and "top_scope" in scope_kwargs: - top_scope = scope_kwargs.pop("top_scope") + if scope_kwargs is not None and top_scope is not None: scope = _update_scope(top_scope, scope, scope_kwargs) if hint is None: @@ -659,8 +660,15 @@ def capture_event( return event_id - def capture_message(self, message, level=None, scope=None, **scope_kwargs): - # type: (str, Optional[str], Optional[Scope], Any) -> Optional[str] + def capture_message( + self, + message, # type: str + level=None, # type: Optional[str] + scope=None, # type: Optional[Scope] + top_scope=None, # type: Optional[Scope] + **scope_kwargs # type: Any + ): + # type: (...) -> Optional[str] """ Captures a message. @@ -670,6 +678,8 @@ def capture_message(self, message, level=None, scope=None, **scope_kwargs): :param scope: An optional :py:class:`sentry_sdk.Scope` to use. + :param top_scope: An optional top scope that should also be merged into the scope to be applied to the event. + :param scope_kwargs: For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. @@ -679,17 +689,29 @@ def capture_message(self, message, level=None, scope=None, **scope_kwargs): level = "info" return self.capture_event( - {"message": message, "level": level}, scope=scope, **scope_kwargs + {"message": message, "level": level}, + scope=scope, + top_scope=top_scope, + **scope_kwargs ) - def capture_exception(self, error=None, scope=None, **scope_kwargs): - # type: (Optional[Union[BaseException, ExcInfo]], Optional[Scope], Any) -> Optional[str] + def capture_exception( + self, + error=None, # type: Optional[Union[BaseException, ExcInfo]] + scope=None, # type: Optional[Scope] + top_scope=None, # type: Optional[Scope] + **scope_kwargs # type: Any + ): + # type: (...) -> Optional[str] """Captures an exception. :param error: An exception to catch. If `None`, `sys.exc_info()` will be used. - :param scope_kwargs: For supported `**scope_kwargs` see - :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + :param scope: An optional :py:class:`sentry_sdk.Scope` to use. + + :param top_scope: An optional top scope that should also be merged into the scope to be applied to the event. + + :param scope_kwargs: For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). """ @@ -701,7 +723,9 @@ def capture_exception(self, error=None, scope=None, **scope_kwargs): event, hint = event_from_exception(exc_info, client_options=self.options) try: - return self.capture_event(event, hint=hint, scope=scope, **scope_kwargs) + return self.capture_event( + event, hint=hint, scope=scope, top_scope=top_scope, **scope_kwargs + ) except Exception: self._capture_internal_exception(sys.exc_info()) diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index 41c062c918..abdc523fb2 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -316,9 +316,9 @@ def capture_event(self, event, hint=None, scope=None, **scope_kwargs): if client is None: return None - scope_kwargs["top_scope"] = top_scope - - last_event_id = client.capture_event(event, hint, scope, **scope_kwargs) + last_event_id = client.capture_event( + event, hint, scope=scope, top_scope=top_scope, **scope_kwargs + ) is_transaction = event.get("type") == "transaction" if last_event_id is not None and not is_transaction: @@ -348,10 +348,8 @@ def capture_message(self, message, level=None, scope=None, **scope_kwargs): if client is None: return None - scope_kwargs["top_scope"] = top_scope - last_event_id = client.capture_message( - message, level=level, scope=scope, **scope_kwargs + message, level=level, scope=scope, top_scope=top_scope, **scope_kwargs ) if last_event_id is not None: @@ -376,9 +374,9 @@ def capture_exception(self, error=None, scope=None, **scope_kwargs): if client is None: return None - scope_kwargs["top_scope"] = top_scope - - last_event_id = client.capture_exception(error, scope=scope, **scope_kwargs) + last_event_id = client.capture_exception( + error, scope=scope, top_scope=top_scope, **scope_kwargs + ) if last_event_id is not None: self._last_event_id = last_event_id diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 5a0a69af39..816153d7a9 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -85,17 +85,21 @@ def _update_scope(base, scope_change, scope_kwargs): # type: (Scope, Optional[Any], Dict[str, Any]) -> Scope if scope_change and scope_kwargs: raise TypeError("cannot provide scope and kwargs") + if scope_change is not None: final_scope = copy(base) if callable(scope_change): scope_change(final_scope) else: final_scope.update_from_scope(scope_change) + elif scope_kwargs: final_scope = copy(base) final_scope.update_from_kwargs(**scope_kwargs) + else: final_scope = base + return final_scope From c78b1f55c77a5347827e2032190f93e5e0a1e2e8 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 13 Dec 2023 14:06:42 +0100 Subject: [PATCH 20/33] Hub calls capture_* on scope that calls it on client. Later with new scopes the hub is removed and the root level API directly calls it on the scope --- sentry_sdk/hub.py | 18 ++++++++----- sentry_sdk/scope.py | 62 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index abdc523fb2..753849cd1a 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -316,8 +316,10 @@ def capture_event(self, event, hint=None, scope=None, **scope_kwargs): if client is None: return None - last_event_id = client.capture_event( - event, hint, scope=scope, top_scope=top_scope, **scope_kwargs + scope_kwargs["client"] = client + + last_event_id = top_scope.capture_event( + event, hint, scope=scope, **scope_kwargs ) is_transaction = event.get("type") == "transaction" @@ -348,8 +350,10 @@ def capture_message(self, message, level=None, scope=None, **scope_kwargs): if client is None: return None - last_event_id = client.capture_message( - message, level=level, scope=scope, top_scope=top_scope, **scope_kwargs + scope_kwargs["client"] = client + + last_event_id = top_scope.capture_message( + message, level=level, scope=scope, **scope_kwargs ) if last_event_id is not None: @@ -374,9 +378,9 @@ def capture_exception(self, error=None, scope=None, **scope_kwargs): if client is None: return None - last_event_id = client.capture_exception( - error, scope=scope, top_scope=top_scope, **scope_kwargs - ) + scope_kwargs["client"] = client + + last_event_id = top_scope.capture_exception(error, scope=scope, **scope_kwargs) if last_event_id is not None: self._last_event_id = last_event_id diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 816153d7a9..fbdc8509b2 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -37,12 +37,13 @@ from sentry_sdk._types import ( Breadcrumb, BreadcrumbHint, + ErrorProcessor, Event, EventProcessor, - ErrorProcessor, ExcInfo, Hint, Type, + Union, ) from sentry_sdk.profiler import Profile @@ -581,6 +582,65 @@ 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, scope=None, **scope_kwargs): + # type: (Event, Optional[Hint], Optional[Scope], Any) -> Optional[str] + """ + Captures an event. + + Alias of :py:meth:`sentry_sdk.Client.capture_event`. + + :param scope_kwargs: For supported `**scope_kwargs` see + :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + """ + client = scope_kwargs.pop("client") + + return client.capture_event( + event, hint, scope=scope, top_scope=self, **scope_kwargs + ) + + def capture_message(self, message, level=None, scope=None, **scope_kwargs): + # type: (str, Optional[str], Optional[Scope], Any) -> Optional[str] + """ + Captures a message. + + Alias of :py:meth:`sentry_sdk.Client.capture_message`. + + :param message: The string to send as the message. + + :param level: If no level is provided, the default level is `info`. + + :param scope: An optional :py:class:`sentry_sdk.Scope` to use. + + :param scope_kwargs: For supported `**scope_kwargs` see + :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + + :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). + """ + client = scope_kwargs.pop("client") + + return client.capture_message( + message, level=level, scope=scope, top_scope=self, **scope_kwargs + ) + + def capture_exception(self, error=None, scope=None, **scope_kwargs): + # type: (Optional[Union[BaseException, ExcInfo]], Optional[Scope], Any) -> Optional[str] + """Captures an exception. + + Alias of :py:meth:`sentry_sdk.Client.capture_exception`. + + :param error: An exception to catch. If `None`, `sys.exc_info()` will be used. + + :param scope_kwargs: For supported `**scope_kwargs` see + :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + + :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). + """ + client = scope_kwargs.pop("client") + + return client.capture_exception( + error, scope=scope, top_scope=self, **scope_kwargs + ) + def start_session(self, *args, **kwargs): # type: (*Any, **Any) -> None """Starts a new session.""" From 269fd5661a66953511afe370ba9a543f3496475d Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 13 Dec 2023 15:59:48 +0100 Subject: [PATCH 21/33] Moved everything to scope and only have capture_event in client --- sentry_sdk/client.py | 94 +------------------------------------------- sentry_sdk/scope.py | 56 ++++++++++++++++++++------ 2 files changed, 45 insertions(+), 105 deletions(-) diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index a42bf89d33..7cb90e3317 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -1,18 +1,14 @@ from importlib import import_module import os -import sys import uuid import random import socket from sentry_sdk._compat import datetime_utcnow, string_types, text_type, iteritems -from sentry_sdk.scope import _update_scope from sentry_sdk.utils import ( capture_internal_exceptions, current_stacktrace, disable_capture_event, - event_from_exception, - exc_info_from_error, format_timestamp, get_sdk_name, get_type_name, @@ -52,7 +48,7 @@ from sentry_sdk.integrations import Integration from sentry_sdk.scope import Scope - from sentry_sdk._types import Event, ExcInfo, Hint + from sentry_sdk._types import Event, Hint from sentry_sdk.session import Session @@ -556,8 +552,6 @@ def capture_event( event, # type: Event hint=None, # type: Optional[Hint] scope=None, # type: Optional[Scope] - top_scope=None, # type: Optional[Scope] - **scope_kwargs # type: Any ): # type: (...) -> Optional[str] """Captures an event. @@ -578,9 +572,6 @@ def capture_event( if disable_capture_event.get(False): return None - if scope_kwargs is not None and top_scope is not None: - scope = _update_scope(top_scope, scope, scope_kwargs) - if hint is None: hint = {} event_id = event.get("event_id") @@ -668,89 +659,6 @@ def capture_event( return event_id - def capture_message( - self, - message, # type: str - level=None, # type: Optional[str] - scope=None, # type: Optional[Scope] - top_scope=None, # type: Optional[Scope] - **scope_kwargs # type: Any - ): - # type: (...) -> Optional[str] - """ - Captures a message. - - :param message: The string to send as the message. - - :param level: If no level is provided, the default level is `info`. - - :param scope: An optional :py:class:`sentry_sdk.Scope` to use. - - :param top_scope: An optional top scope that should also be merged into the scope to be applied to the event. - - :param scope_kwargs: For supported `**scope_kwargs` see - :py:meth:`sentry_sdk.Scope.update_from_kwargs`. - - :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). - """ - if level is None: - level = "info" - - return self.capture_event( - {"message": message, "level": level}, - scope=scope, - top_scope=top_scope, - **scope_kwargs - ) - - def capture_exception( - self, - error=None, # type: Optional[Union[BaseException, ExcInfo]] - scope=None, # type: Optional[Scope] - top_scope=None, # type: Optional[Scope] - **scope_kwargs # type: Any - ): - # type: (...) -> Optional[str] - """Captures an exception. - - :param error: An exception to catch. If `None`, `sys.exc_info()` will be used. - - :param scope: An optional :py:class:`sentry_sdk.Scope` to use. - - :param top_scope: An optional top scope that should also be merged into the scope to be applied to the event. - - :param scope_kwargs: For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. - - :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). - """ - 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=self.options) - - try: - return self.capture_event( - event, hint=hint, scope=scope, top_scope=top_scope, **scope_kwargs - ) - except Exception: - self._capture_internal_exception(sys.exc_info()) - - return None - - def _capture_internal_exception( - self, exc_info # type: Any - ): - # type: (...) -> Any - """ - Capture an exception that is likely caused by a bug in the SDK - itself. - - These exceptions do not end up in Sentry and are just logged instead. - """ - logger.error("Internal error in sentry_sdk", exc_info=exc_info) - def capture_session( self, session # type: Session ): diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index fbdc8509b2..5d84f826db 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -2,6 +2,7 @@ from collections import deque from itertools import chain import os +import sys import uuid from sentry_sdk.attachments import Attachment @@ -21,7 +22,12 @@ Transaction, ) from sentry_sdk._types import TYPE_CHECKING -from sentry_sdk.utils import logger, capture_internal_exceptions +from sentry_sdk.utils import ( + event_from_exception, + exc_info_from_error, + logger, + capture_internal_exceptions, +) if TYPE_CHECKING: from typing import Any @@ -82,7 +88,7 @@ def wrapper(self, *args, **kwargs): return wrapper # type: ignore -def _update_scope(base, scope_change, scope_kwargs): +def _merge_scopes(base, scope_change, scope_kwargs): # type: (Scope, Optional[Any], Dict[str, Any]) -> Scope if scope_change and scope_kwargs: raise TypeError("cannot provide scope and kwargs") @@ -594,9 +600,10 @@ def capture_event(self, event, hint=None, scope=None, **scope_kwargs): """ client = scope_kwargs.pop("client") - return client.capture_event( - event, hint, scope=scope, top_scope=self, **scope_kwargs - ) + if scope_kwargs is not None: + scope = _merge_scopes(self, scope, scope_kwargs) + + return client.capture_event(event, hint, scope=scope) def capture_message(self, message, level=None, scope=None, **scope_kwargs): # type: (str, Optional[str], Optional[Scope], Any) -> Optional[str] @@ -616,10 +623,13 @@ 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 = scope_kwargs.pop("client") + if level is None: + level = "info" + + # the client is in scope_kwargs - return client.capture_message( - message, level=level, scope=scope, top_scope=self, **scope_kwargs + return self.capture_event( + {"message": message, "level": level}, scope=scope, **scope_kwargs ) def capture_exception(self, error=None, scope=None, **scope_kwargs): @@ -635,11 +645,33 @@ 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 = scope_kwargs.pop("client") + if error is not None: + exc_info = exc_info_from_error(error) + else: + exc_info = sys.exc_info() - return client.capture_exception( - error, scope=scope, top_scope=self, **scope_kwargs - ) + event, hint = event_from_exception(exc_info, client_options=self.options) + + try: + # the client is in scope_kwargs + + return self.capture_event(event, hint=hint, scope=scope, **scope_kwargs) + except Exception: + self._capture_internal_exception(sys.exc_info()) + + return None + + def _capture_internal_exception( + self, exc_info # type: Any + ): + # type: (...) -> Any + """ + Capture an exception that is likely caused by a bug in the SDK + itself. + + These exceptions do not end up in Sentry and are just logged instead. + """ + logger.error("Internal error in sentry_sdk", exc_info=exc_info) def start_session(self, *args, **kwargs): # type: (*Any, **Any) -> None From 035406552bd473e32da546dcbc0b6a0fef1495d7 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 13 Dec 2023 16:03:08 +0100 Subject: [PATCH 22/33] small fix --- sentry_sdk/scope.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 5d84f826db..f5c8286067 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -650,7 +650,9 @@ def capture_exception(self, error=None, scope=None, **scope_kwargs): else: exc_info = sys.exc_info() - event, hint = event_from_exception(exc_info, client_options=self.options) + event, hint = event_from_exception( + exc_info, client_options=scope_kwargs["client"].options + ) try: # the client is in scope_kwargs From 3d5825e667902852ed19fb5898027799eafaf126 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 13 Dec 2023 16:35:14 +0100 Subject: [PATCH 23/33] Updated test --- tests/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_client.py b/tests/test_client.py index a99bb05765..5a226fe638 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -365,7 +365,7 @@ def raise_it(exc_info): reraise(*exc_info) sentry_init(ignore_errors=[ZeroDivisionError], transport=_TestTransport()) - Hub.current.client._capture_internal_exception = raise_it + Hub.current.scope._capture_internal_exception = raise_it def e(exc): try: From 4e78d1f5bb93e52390797b6fc8ca3877573a694a Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 13 Dec 2023 16:53:20 +0100 Subject: [PATCH 24/33] Fixed test --- sentry_sdk/scope.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index f5c8286067..925208c4b9 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -143,6 +143,7 @@ class Scope(object): "_force_auto_session_tracking", "_profile", "_propagation_context", + "_capture_internal_exception", ) def __init__(self): From ba722163fb480c666f38393b7e5912cf66a977aa Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 14 Dec 2023 10:06:56 +0100 Subject: [PATCH 25/33] Fixed test --- sentry_sdk/scope.py | 1 - tests/test_client.py | 34 +++++++++++++++++++--------------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 925208c4b9..f5c8286067 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -143,7 +143,6 @@ class Scope(object): "_force_auto_session_tracking", "_profile", "_propagation_context", - "_capture_internal_exception", ) def __init__(self): diff --git a/tests/test_client.py b/tests/test_client.py index 5a226fe638..e02175ab92 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -20,7 +20,7 @@ ) from sentry_sdk.integrations.executing import ExecutingIntegration from sentry_sdk.transport import Transport -from sentry_sdk._compat import reraise, text_type, PY2 +from sentry_sdk._compat import text_type, PY2 from sentry_sdk.utils import HAS_CHAINED_EXCEPTIONS from sentry_sdk.utils import logger from sentry_sdk.serializer import MAX_DATABAG_BREADTH @@ -358,24 +358,28 @@ def test_simple_transport(sentry_init): def test_ignore_errors(sentry_init, capture_events): - class MyDivisionError(ZeroDivisionError): - pass - def raise_it(exc_info): - reraise(*exc_info) + with mock.patch( + "sentry_sdk.scope.Scope._capture_internal_exception" + ) as mock_capture_internal_exception: - sentry_init(ignore_errors=[ZeroDivisionError], transport=_TestTransport()) - Hub.current.scope._capture_internal_exception = raise_it + class MyDivisionError(ZeroDivisionError): + pass - def e(exc): - try: - raise exc - except Exception: - capture_exception() + sentry_init(ignore_errors=[ZeroDivisionError], transport=_TestTransport()) + + def e(exc): + try: + raise exc + except Exception: + capture_exception() + + e(ZeroDivisionError()) + e(MyDivisionError()) + e(ValueError()) - e(ZeroDivisionError()) - e(MyDivisionError()) - pytest.raises(EventCapturedError, lambda: e(ValueError())) + mock_capture_internal_exception.assert_called_once() + assert mock_capture_internal_exception.call_args[0][0][0] == EventCapturedError def test_with_locals_deprecation_enabled(sentry_init): From c5a7ae790bc0b332a8596614c537c96071fd7bcf Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 14 Dec 2023 10:54:21 +0100 Subject: [PATCH 26/33] Fixed test for old python --- tests/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_client.py b/tests/test_client.py index e02175ab92..da13cbcb72 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -378,7 +378,7 @@ def e(exc): e(MyDivisionError()) e(ValueError()) - mock_capture_internal_exception.assert_called_once() + assert mock_capture_internal_exception.call_count == 1 assert mock_capture_internal_exception.call_args[0][0][0] == EventCapturedError From bbb93e6c90fbf2801922d49f3579463c518ab217 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 14 Dec 2023 10:57:15 +0100 Subject: [PATCH 27/33] Formatting --- tests/test_client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_client.py b/tests/test_client.py index da13cbcb72..fa55c1111a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -358,7 +358,6 @@ def test_simple_transport(sentry_init): def test_ignore_errors(sentry_init, capture_events): - with mock.patch( "sentry_sdk.scope.Scope._capture_internal_exception" ) as mock_capture_internal_exception: From 12e632d2b356b4c30b0cc1f0ca9b7bc216b8022d Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 14 Dec 2023 12:54:59 +0100 Subject: [PATCH 28/33] Passing the client as separate argument and updating api docs. --- docs/apidocs.rst | 3 ++ sentry_sdk/client.py | 10 ++--- sentry_sdk/hub.py | 52 +++++++++++++++----------- sentry_sdk/scope.py | 87 +++++++++++++++++++++++++++++--------------- 4 files changed, 95 insertions(+), 57 deletions(-) diff --git a/docs/apidocs.rst b/docs/apidocs.rst index dc4117e559..855778484d 100644 --- a/docs/apidocs.rst +++ b/docs/apidocs.rst @@ -11,6 +11,9 @@ API Docs .. autoclass:: sentry_sdk.Client :members: +.. autoclass:: sentry_sdk.client._Client + :members: + .. autoclass:: sentry_sdk.Transport :members: diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index 7cb90e3317..ce2fed258b 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -156,6 +156,8 @@ class _Client(object): forwarding them to sentry through the configured transport. It takes the client options as keyword arguments and optionally the DSN as first argument. + + Alias of :py:class:`Client`. """ def __init__(self, *args, **kwargs): @@ -560,12 +562,8 @@ def capture_event( :param hint: Contains metadata about the event that can be read from `before_send`, such as the original exception object or a HTTP request object. - :param scope: An optional scope to use for determining whether this event should be captured. - - :param top_scope: An optional top scope that should also be merged into the scope to be applied to the event. - - :param scope_kwargs: For supported `**scope_kwargs` see - :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events. + The `scope` and `scope_kwargs` parameters are mutually exclusive. :returns: An event ID. May be `None` if there is no DSN set or of if the SDK decided to discard the event for other reasons. In such situations setting `debug=True` on `init()` may help. """ diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index 753849cd1a..cf748bb8ea 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -307,19 +307,25 @@ def capture_event(self, event, hint=None, scope=None, **scope_kwargs): """ Captures an event. - Alias of :py:meth:`sentry_sdk.Client.capture_event`. + Alias of :py:meth:`sentry_sdk.Scope.capture_event`. - :param scope_kwargs: For supported `**scope_kwargs` see - :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + :param event: A ready-made event that can be directly sent to Sentry. + + :param hint: Contains metadata about the event that can be read from `before_send`, such as the original exception object or a HTTP request object. + + :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events. + The `scope` and `scope_kwargs` parameters are mutually exclusive. + + :param scope_kwargs: Optional data to apply to event. + 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 - scope_kwargs["client"] = client - last_event_id = top_scope.capture_event( - event, hint, scope=scope, **scope_kwargs + event, hint, client=client, scope=scope, **scope_kwargs ) is_transaction = event.get("type") == "transaction" @@ -333,16 +339,18 @@ def capture_message(self, message, level=None, scope=None, **scope_kwargs): """ Captures a message. - Alias of :py:meth:`sentry_sdk.Client.capture_message`. + Alias of :py:meth:`sentry_sdk.Scope.capture_message`. - :param message: The string to send as the message. + :param message: The string to send as the message to Sentry. :param level: If no level is provided, the default level is `info`. - :param scope: An optional :py:class:`sentry_sdk.Scope` to use. + :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events. + The `scope` and `scope_kwargs` parameters are mutually exclusive. - :param scope_kwargs: For supported `**scope_kwargs` see - :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + :param scope_kwargs: Optional data to apply to event. + For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + The `scope` and `scope_kwargs` parameters are mutually exclusive. :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). """ @@ -350,10 +358,8 @@ def capture_message(self, message, level=None, scope=None, **scope_kwargs): if client is None: return None - scope_kwargs["client"] = client - last_event_id = top_scope.capture_message( - message, level=level, scope=scope, **scope_kwargs + message, level=level, client=client, scope=scope, **scope_kwargs ) if last_event_id is not None: @@ -365,12 +371,16 @@ def capture_exception(self, error=None, scope=None, **scope_kwargs): # type: (Optional[Union[BaseException, ExcInfo]], Optional[Scope], Any) -> Optional[str] """Captures an exception. - Alias of :py:meth:`sentry_sdk.Client.capture_exception`. + Alias of :py:meth:`sentry_sdk.Scope.capture_exception`. + + :param error: An exception to capture. If `None`, `sys.exc_info()` will be used. - :param error: An exception to catch. If `None`, `sys.exc_info()` will be used. + :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events. + The `scope` and `scope_kwargs` parameters are mutually exclusive. - :param scope_kwargs: For supported `**scope_kwargs` see - :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + :param scope_kwargs: Optional data to apply to event. + For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + The `scope` and `scope_kwargs` parameters are mutually exclusive. :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). """ @@ -378,9 +388,9 @@ def capture_exception(self, error=None, scope=None, **scope_kwargs): if client is None: return None - scope_kwargs["client"] = client - - last_event_id = top_scope.capture_exception(error, scope=scope, **scope_kwargs) + last_event_id = top_scope.capture_exception( + error, client=client, scope=scope, **scope_kwargs + ) if last_event_id is not None: self._last_event_id = last_event_id diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index f5c8286067..b25e6a98e6 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -52,6 +52,7 @@ Union, ) + import sentry_sdk from sentry_sdk.profiler import Profile from sentry_sdk.tracing import Span @@ -588,76 +589,102 @@ 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, scope=None, **scope_kwargs): - # type: (Event, Optional[Hint], Optional[Scope], Any) -> Optional[str] + 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] """ Captures an event. - Alias of :py:meth:`sentry_sdk.Client.capture_event`. + Merges given scope data and calls :py:meth:`sentry_sdk.Client.capture_event`. - :param scope_kwargs: For supported `**scope_kwargs` see - :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + :param event: A ready-made event that can be directly sent to Sentry. + + :param hint: Contains metadata about the event that can be read from `before_send`, such as the original exception object or a HTTP request object. + + :param client: The client to use for sending the event to Sentry. + + :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events. + The `scope` and `scope_kwargs` parameters are mutually exclusive. + + :param scope_kwargs: Optional data to apply to event. + For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + The `scope` and `scope_kwargs` parameters are mutually exclusive. + + :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). """ - client = scope_kwargs.pop("client") + if client is None: + return None if scope_kwargs is not None: scope = _merge_scopes(self, scope, scope_kwargs) - return client.capture_event(event, hint, scope=scope) + return client.capture_event(event, hint, client=client, scope=scope) - def capture_message(self, message, level=None, scope=None, **scope_kwargs): - # type: (str, Optional[str], Optional[Scope], Any) -> Optional[str] + 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] """ Captures a message. - Alias of :py:meth:`sentry_sdk.Client.capture_message`. - :param message: The string to send as the message. :param level: If no level is provided, the default level is `info`. - :param scope: An optional :py:class:`sentry_sdk.Scope` to use. + :param client: The client to use for sending the event to Sentry. + + :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events. + The `scope` and `scope_kwargs` parameters are mutually exclusive. - :param scope_kwargs: For supported `**scope_kwargs` see - :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + :param scope_kwargs: Optional data to apply to event. + For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + The `scope` and `scope_kwargs` parameters are mutually exclusive. :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" - # the client is in scope_kwargs + event = { + "message": message, + "level": level, + } - return self.capture_event( - {"message": message, "level": level}, scope=scope, **scope_kwargs - ) + return self.capture_event(event, client=client, scope=scope, **scope_kwargs) - def capture_exception(self, error=None, scope=None, **scope_kwargs): - # type: (Optional[Union[BaseException, ExcInfo]], Optional[Scope], Any) -> Optional[str] + 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] """Captures an exception. - Alias of :py:meth:`sentry_sdk.Client.capture_exception`. + :param error: An exception to capture. If `None`, `sys.exc_info()` will be used. + + :param client: The client to use for sending the event to Sentry. - :param error: An exception to catch. If `None`, `sys.exc_info()` will be used. + :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events. + The `scope` and `scope_kwargs` parameters are mutually exclusive. - :param scope_kwargs: For supported `**scope_kwargs` see - :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + :param scope_kwargs: Optional data to apply to event. + For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + The `scope` and `scope_kwargs` parameters are mutually exclusive. :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=scope_kwargs["client"].options - ) + event, hint = event_from_exception(exc_info, client_options=client.options) try: - # the client is in scope_kwargs - - return self.capture_event(event, hint=hint, scope=scope, **scope_kwargs) + return self.capture_event( + event, hint=hint, client=client, scope=scope, **scope_kwargs + ) except Exception: self._capture_internal_exception(sys.exc_info()) From eb56a127d04332fbb159d2b2555a7fed5f1a237b Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 14 Dec 2023 12:55:33 +0100 Subject: [PATCH 29/33] ApiDocs --- sentry_sdk/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index ce2fed258b..70ffdbe2aa 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -157,7 +157,7 @@ class _Client(object): the client options as keyword arguments and optionally the DSN as first argument. - Alias of :py:class:`Client`. + Alias of :py:class:`Client`. (Was created for better intelisense support) """ def __init__(self, *args, **kwargs): From 074a9eff83343fc3fa0b969fdf0f026f5d5a2c1b Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 14 Dec 2023 13:05:47 +0100 Subject: [PATCH 30/33] Fix --- sentry_sdk/scope.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index b25e6a98e6..8682e7face 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -617,7 +617,7 @@ def capture_event(self, event, hint=None, client=None, scope=None, **scope_kwarg if scope_kwargs is not None: scope = _merge_scopes(self, scope, scope_kwargs) - return client.capture_event(event, hint, client=client, scope=scope) + return client.capture_event(event=event, hint=hint, scope=scope) def capture_message( self, message, level=None, client=None, scope=None, **scope_kwargs From ac3955b029009779973495f6cb9b4608cfd07b58 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 14 Dec 2023 13:07:46 +0100 Subject: [PATCH 31/33] Update sentry_sdk/scope.py Co-authored-by: Ivana Kellyerova --- sentry_sdk/scope.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 17df5d6da3..ce735acf42 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -253,7 +253,7 @@ def get_traceparent(self, *args, **kwargs): """ client = kwargs.pop("client", None) - # If we have an active span, return trashparent from there + # If we have an active span, return traceparent from there if ( client is not None and has_tracing_enabled(client.options) From 6733114e347115f3f40494d74b1ec2bdda69fea6 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 19 Dec 2023 08:37:18 +0100 Subject: [PATCH 32/33] Remove condition from _merge_scope (also not present in current implementation) --- sentry_sdk/scope.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 8682e7face..c715847d38 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -614,8 +614,7 @@ def capture_event(self, event, hint=None, client=None, scope=None, **scope_kwarg if client is None: return None - if scope_kwargs is not None: - scope = _merge_scopes(self, scope, scope_kwargs) + scope = _merge_scopes(self, scope, scope_kwargs) return client.capture_event(event=event, hint=hint, scope=scope) From c4ad19ad88243d70e27ad861a1bb5f491a1b17f8 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 19 Dec 2023 09:30:47 +0100 Subject: [PATCH 33/33] (4) Move transaction/span related functions from Hub to Scope (#2564) Moved some functionality from Hub to Client: - moved `start_transaction` from Hub to Scope - moved `start_span` from Hub to Scope - moved `continue_trace` from Hub to Scope This is preparation work for refactoring how we deal with Hubs and Scopes in the future. Depends on: https://github.com/getsentry/sentry-python/pull/2558 (please review the linked one first) --- sentry_sdk/hub.py | 100 ++++------------------------ sentry_sdk/scope.py | 158 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 167 insertions(+), 91 deletions(-) diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index b0a40d72b6..d0cfc313a3 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -7,13 +7,11 @@ 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, ) -from sentry_sdk.tracing_utils import normalize_incoming_data from sentry_sdk.utils import ( logger, @@ -430,54 +428,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 @@ -507,55 +463,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( diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index ce735acf42..2b9fba0237 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -6,8 +6,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, @@ -18,6 +19,8 @@ from sentry_sdk.tracing import ( BAGGAGE_HEADER_NAME, SENTRY_TRACE_HEADER_NAME, + NoOpSpan, + Span, Transaction, ) from sentry_sdk._types import TYPE_CHECKING @@ -34,6 +37,7 @@ from typing import Optional from typing import Tuple from typing import TypeVar + from typing import Union from sentry_sdk._types import ( Breadcrumb, @@ -46,9 +50,6 @@ Type, ) - from sentry_sdk.profiler import Profile - from sentry_sdk.tracing import Span - F = TypeVar("F", bound=Callable[..., Any]) T = TypeVar("T") @@ -636,6 +637,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 start_session(self, *args, **kwargs): # type: (*Any, **Any) -> None """Starts a new session."""