From f2a4a3aedf53126e747149f05bf7e5eef2e6f761 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 29 Nov 2023 16:08:06 +0100 Subject: [PATCH 01/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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 802021ef63537ea1609d0f274f596b70a282edf4 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 4 Dec 2023 16:05:15 +0100 Subject: [PATCH 18/43] Moved start_transaction, start_span and continue_trace from Hub to Scope --- sentry_sdk/hub.py | 100 ++++------------------------ sentry_sdk/scope.py | 154 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 166 insertions(+), 88 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 17df5d6da3..25893f248a 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 FALSE_VALUES +from sentry_sdk.consts import FALSE_VALUES, INSTRUMENTER from sentry_sdk._functools import wraps from sentry_sdk.session import Session from sentry_sdk.tracing_utils import ( @@ -18,6 +18,7 @@ from sentry_sdk.tracing import ( BAGGAGE_HEADER_NAME, SENTRY_TRACE_HEADER_NAME, + NoOpSpan, Transaction, ) from sentry_sdk._types import TYPE_CHECKING @@ -34,6 +35,7 @@ from typing import Optional from typing import Tuple from typing import TypeVar + from typing import Union from sentry_sdk._types import ( Breadcrumb, @@ -636,6 +638,156 @@ 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`. + """ + hub = kwargs.pop("hub", None) + client = kwargs.pop("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) + + 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", hub) + + active_span = self.scope.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.""" From 5cf788f8a71b96f4e008e0a586511bd655424798 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 4 Dec 2023 17:18:50 +0100 Subject: [PATCH 19/43] Fixed import --- 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 25893f248a..be2e79711d 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -8,6 +8,7 @@ from sentry_sdk._compat import datetime_utcnow 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, @@ -48,7 +49,6 @@ Type, ) - from sentry_sdk.profiler import Profile from sentry_sdk.tracing import Span F = TypeVar("F", bound=Callable[..., Any]) From fec7bad91505ac29a3bac7eafe2e9d40679d4b04 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 4 Dec 2023 17:23:33 +0100 Subject: [PATCH 20/43] 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 be2e79711d..579d77aea6 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -756,7 +756,7 @@ def start_span(self, span=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs): kwargs.setdefault("hub", hub) - active_span = self.scope.span + active_span = self.span if active_span is not None: new_child_span = active_span.start_child(**kwargs) return new_child_span From 34d91acf9441d94a1a7ead2e2c024776b5d38737 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 5 Dec 2023 10:27:23 +0100 Subject: [PATCH 21/43] fixed import --- 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 579d77aea6..fb4b7e7da1 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -20,6 +20,7 @@ BAGGAGE_HEADER_NAME, SENTRY_TRACE_HEADER_NAME, NoOpSpan, + Span, Transaction, ) from sentry_sdk._types import TYPE_CHECKING @@ -49,8 +50,6 @@ Type, ) - from sentry_sdk.tracing import Span - F = TypeVar("F", bound=Callable[..., Any]) T = TypeVar("T") From 04204607b6f8c4c524f6fa3217d7fc74abd61c53 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 5 Dec 2023 12:37:21 +0100 Subject: [PATCH 22/43] Fixed deprecated transaction creation --- sentry_sdk/scope.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index fb4b7e7da1..d88a98dfad 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -719,8 +719,7 @@ def start_span(self, span=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs): For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Span`. """ - hub = kwargs.pop("hub", None) - client = kwargs.pop("client", None) + client = kwargs.get("client", None) configuration_instrumenter = client and client.options["instrumenter"] @@ -739,7 +738,7 @@ def start_span(self, span=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs): if isinstance(span, Transaction): logger.warning(deprecation_msg) - return self.start_transaction(span) + return self.start_transaction(span, **kwargs) if "transaction" in kwargs: logger.warning(deprecation_msg) @@ -753,7 +752,7 @@ def start_span(self, span=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs): logger.warning(deprecation_msg) return span - kwargs.setdefault("hub", hub) + kwargs.pop("client") active_span = self.span if active_span is not None: From 0e3241e5bf0ce685fb5729225216ab5b765ab785 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 7 Dec 2023 10:45:24 +0100 Subject: [PATCH 23/43] Add support for reset() to our threadlocal contextvars --- sentry_sdk/utils.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 39890d9649..6419ba5d0b 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -4,6 +4,7 @@ import logging import math import os +import random import re import subprocess import sys @@ -1242,18 +1243,29 @@ def _make_threadlocal_contextvars(local): class ContextVar(object): # Super-limited impl of ContextVar - def __init__(self, name): - # type: (str) -> None + def __init__(self, name, default=None): + # type: (str, Any) -> None self._name = name + self._default = default self._local = local() + self._original_local = local() - def get(self, default): + def get(self, default=None): # type: (Any) -> Any - return getattr(self._local, "value", default) + return getattr(self._local, "value", default or self._default) def set(self, value): # type: (Any) -> None - self._local.value = value + token = str(random.getrandbits(64)) + original_value = self.get() + setattr(self._original_local, token, original_value) + setattr(self._local, "value", value) + return token + + def reset(self, token): + # type: (Any) -> None + setattr(self._local, "value", getattr(self._original_local, token)) + setattr(self._original_local, token, None) return ContextVar From b06f315a080644cdba2c55b6fe83a5d1705fe316 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 7 Dec 2023 10:52:10 +0100 Subject: [PATCH 24/43] Add reset to our threadlocal contextvars and add copy_context --- sentry_sdk/utils.py | 21 ++++++++++++++++----- tests/utils/test_contextvars.py | 2 +- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 6419ba5d0b..8d34694828 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -1270,6 +1270,17 @@ def reset(self, token): return ContextVar +def _make_noop_copy_context(): + class NoOpContext: + def run(self, func, *args, **kwargs): + return func(*args, **kwargs) + + def copy_context(): + return NoOpContext() + + return copy_context + + def _get_contextvars(): # type: () -> Tuple[bool, type] """ @@ -1287,7 +1298,7 @@ def _get_contextvars(): # `aiocontextvars` is absolutely required for functional # contextvars on Python 3.6. try: - from aiocontextvars import ContextVar + from aiocontextvars import ContextVar, copy_context return True, ContextVar except ImportError: @@ -1295,9 +1306,9 @@ def _get_contextvars(): else: # On Python 3.7 contextvars are functional. try: - from contextvars import ContextVar + from contextvars import ContextVar, copy_context - return True, ContextVar + return True, ContextVar, copy_context except ImportError: pass @@ -1305,10 +1316,10 @@ def _get_contextvars(): from threading import local - return False, _make_threadlocal_contextvars(local) + return False, _make_threadlocal_contextvars(local), _make_noop_copy_context() -HAS_REAL_CONTEXTVARS, ContextVar = _get_contextvars() +HAS_REAL_CONTEXTVARS, ContextVar, copy_context = _get_contextvars() CONTEXTVARS_ERROR_MESSAGE = """ diff --git a/tests/utils/test_contextvars.py b/tests/utils/test_contextvars.py index a6d296bb1f..faf33e8580 100644 --- a/tests/utils/test_contextvars.py +++ b/tests/utils/test_contextvars.py @@ -12,7 +12,7 @@ def test_leaks(maybe_monkeypatched_threading): from sentry_sdk import utils - _, ContextVar = utils._get_contextvars() # noqa: N806 + _, ContextVar, _ = utils._get_contextvars() # noqa: N806 ts = [] From 6439f5e4217b74af78b9892c3ab34f0a5cbeb8ee Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 7 Dec 2023 11:05:05 +0100 Subject: [PATCH 25/43] Make linter happy --- sentry_sdk/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 8d34694828..d704b0d178 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -1259,12 +1259,12 @@ def set(self, value): token = str(random.getrandbits(64)) original_value = self.get() setattr(self._original_local, token, original_value) - setattr(self._local, "value", value) + self._local.value = value return token def reset(self, token): # type: (Any) -> None - setattr(self._local, "value", getattr(self._original_local, token)) + self._local.value = getattr(self._original_local, token) setattr(self._original_local, token, None) return ContextVar From 2a0147588736d650f0e94f23bb33ffcc1747d59e Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 7 Dec 2023 12:04:16 +0100 Subject: [PATCH 26/43] Fixed return value --- sentry_sdk/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index d704b0d178..05a76dcdb3 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -1300,7 +1300,7 @@ def _get_contextvars(): try: from aiocontextvars import ContextVar, copy_context - return True, ContextVar + return True, ContextVar, copy_context except ImportError: pass else: From 948703877c63cb677fdb72fbca6c967ed19a022d Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 11 Dec 2023 12:54:34 +0100 Subject: [PATCH 27/43] (1) Move `add_breadcrumb` and session function from Hub to Scope (#2578) Moved some functionality from Hub to Scope or Client: - moved `add_breadcrumb` from Hub to Scope - moved session functions from Hub to Scope - moved `get_integration1` from Hub to Client. This is preparation work for refactoring how we deal with Hubs and Scopes in the future. Each commit is moving one function, so it should be easy to review commit by commit. --- sentry_sdk/client.py | 19 +++++++++ sentry_sdk/hub.py | 62 +++++---------------------- sentry_sdk/scope.py | 99 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 124 insertions(+), 56 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..032ccd09e7 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 @@ -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, @@ -294,18 +293,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): @@ -430,31 +420,9 @@ 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["client"] = client - 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 @@ -712,12 +680,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, ) @@ -725,13 +690,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 @@ -740,9 +699,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 @@ -750,8 +708,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 5096eccce0..8e9724b4c5 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -5,7 +5,10 @@ import uuid from sentry_sdk.attachments import Attachment +from sentry_sdk._compat import datetime_utcnow +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 ( Baggage, extract_sentrytrace_data, @@ -20,9 +23,6 @@ from sentry_sdk._types import TYPE_CHECKING from sentry_sdk.utils import logger, capture_internal_exceptions -from sentry_sdk.consts import FALSE_VALUES - - if TYPE_CHECKING: from typing import Any from typing import Dict @@ -36,6 +36,7 @@ from sentry_sdk._types import ( Breadcrumb, + BreadcrumbHint, Event, EventProcessor, ErrorProcessor, @@ -46,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") @@ -517,6 +517,97 @@ 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. + """ + 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) + 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 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 bb3250a564b47306a6a9c96c6b85a427d74e5e2f Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 11 Dec 2023 15:19:06 +0100 Subject: [PATCH 28/43] _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 29/43] 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 4ed5f8a57f70535ba12096992b280fec1c8e52b0 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 11 Dec 2023 17:35:47 +0100 Subject: [PATCH 30/43] Typing --- sentry_sdk/utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 65903511ac..f93a84d7e6 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -1261,7 +1261,7 @@ def get(self, default=None): return getattr(self._local, "value", default or self._default) def set(self, value): - # type: (Any) -> None + # type: (Any) -> Any token = str(random.getrandbits(64)) original_value = self.get() setattr(self._original_local, token, original_value) @@ -1277,18 +1277,21 @@ def reset(self, token): def _make_noop_copy_context(): + # type: () -> Callable[[], Any] class NoOpContext: def run(self, func, *args, **kwargs): + # type: (Callable[..., Any], *Any, **Any) -> Any return func(*args, **kwargs) def copy_context(): + # type: () -> NoOpContext return NoOpContext() return copy_context def _get_contextvars(): - # type: () -> Tuple[bool, type] + # type: () -> Tuple[bool, type, Callable[[], Any]] """ Figure out the "right" contextvars installation to use. Returns a `contextvars.ContextVar`-like class with a limited API. From c78b1f55c77a5347827e2032190f93e5e0a1e2e8 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 13 Dec 2023 14:06:42 +0100 Subject: [PATCH 31/43] 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 32/43] 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 33/43] 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 34/43] 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 35/43] 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 36/43] 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 37/43] 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 38/43] 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 39/43] 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 40/43] 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 41/43] 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 42/43] 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 a83f2caf8fd278ae3f1ee1ab47dd437527b0aae2 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 14 Dec 2023 14:40:05 +0100 Subject: [PATCH 43/43] Linting fix --- sentry_sdk/scope.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 1340cceb58..424e2bbb7d 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -54,7 +54,6 @@ ExcInfo, Hint, Type, - Union, ) import sentry_sdk