Skip to content

Commit

Permalink
(2) Move capture_* from Hub to Client (#2555)
Browse files Browse the repository at this point in the history
Moved some functionality from Hub to Client:

- Capture Event:
  - moved `capture_event` from Hub to Client
  - created new `capture_event` in Scope that calls `capture_event` in Client
  - made `capture_event` in Hub call the new `capture_event` in Scope

- Capture Exception:
  - created new `capture_exception` in Scope
  - made `capture_exception` in Hub call the new one in Scope

- Capture Message:
  - created new `capture_message` in Scope
  - made `capture_message` in Hub call the new one in Scope

- renamed `**scope_args` to `**scope_kwargs` because it contains keyword arguments.
- moved `_update_scope` from Hub to Scope and renamed it to `_merge_scopes` 

This is preparation work for refactoring how we deal with Hubs and Scopes in the future.
Its properly easier to reason about this change when checking out the branch than looking at the diff.
  • Loading branch information
antonpirker authored Dec 19, 2023
1 parent eff8f78 commit 79e15f5
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 82 deletions.
3 changes: 3 additions & 0 deletions docs/apidocs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ API Docs
.. autoclass:: sentry_sdk.Client
:members:

.. autoclass:: sentry_sdk.client._Client
:members:

.. autoclass:: sentry_sdk.Transport
:members:

Expand Down
12 changes: 6 additions & 6 deletions sentry_sdk/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,31 +82,31 @@ def capture_event(
event, # type: Event
hint=None, # type: Optional[Hint]
scope=None, # type: Optional[Any]
**scope_args # type: Any
**scope_kwargs # 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_kwargs)


@hubmethod
def capture_message(
message, # type: str
level=None, # type: Optional[str]
scope=None, # type: Optional[Any]
**scope_args # type: Any
**scope_kwargs # 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_kwargs)


@hubmethod
def capture_exception(
error=None, # type: Optional[Union[BaseException, ExcInfo]]
scope=None, # type: Optional[Any]
**scope_args # type: Any
**scope_kwargs # type: Any
):
# type: (...) -> Optional[str]
return Hub.current.capture_exception(error, scope=scope, **scope_args)
return Hub.current.capture_exception(error, scope=scope, **scope_kwargs)


@hubmethod
Expand Down
6 changes: 4 additions & 2 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`. (Was created for better intelisense support)
"""

def __init__(self, *args, **kwargs):
Expand Down Expand Up @@ -560,8 +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 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.
"""
Expand Down
120 changes: 63 additions & 57 deletions sentry_sdk/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
)

from sentry_sdk.utils import (
exc_info_from_error,
event_from_exception,
logger,
ContextVar,
)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -322,76 +302,100 @@ 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_kwargs):
# type: (Event, Optional[Hint], Optional[Scope], Any) -> Optional[str]
"""
Captures an event.
Alias of :py:meth:`sentry_sdk.Client.capture_event`.
Alias of :py:meth:`sentry_sdk.Scope.capture_event`.
:param event: A ready-made event that can be directly sent to Sentry.
:param scope_args: For supported `**scope_args` see
:py:meth:`sentry_sdk.Scope.update_from_kwargs`.
: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]
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

last_event_id = top_scope.capture_event(
event, hint, client=client, scope=scope, **scope_kwargs
)

def capture_message(self, message, level=None, scope=None, **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_kwargs):
# type: (str, Optional[str], Optional[Scope], Any) -> Optional[str]
"""
Captures a message.
:param message: The string to send as the message.
Alias of :py:meth:`sentry_sdk.Scope.capture_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_args: For supported `**scope_args` 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 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

last_event_id = top_scope.capture_message(
message, level=level, client=client, scope=scope, **scope_kwargs
)

def capture_exception(self, error=None, scope=None, **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_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.
Alias of :py:meth:`sentry_sdk.Scope.capture_exception`.
:param error: An exception to capture. 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_args: For supported `**scope_args` 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`).
"""
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())
last_event_id = top_scope.capture_exception(
error, client=client, scope=scope, **scope_kwargs
)

return None
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
Expand All @@ -401,6 +405,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)
Expand Down
Loading

0 comments on commit 79e15f5

Please sign in to comment.