Skip to content

(2) Move capture_* from Hub to Client #2555

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f2a4a3a
Moved get_integration from Hub to Client
antonpirker Nov 29, 2023
4869365
Moved add_breadcrumb from Hub to Scope
antonpirker Nov 29, 2023
e5f9e9d
Moved session functions from Hub to Scope
antonpirker Nov 29, 2023
a338099
Fixed import
antonpirker Nov 29, 2023
d900a1b
Fixed Python 2.7 syntax
antonpirker Nov 29, 2023
0afb3ab
Merge branch 'master' into antonpirker/refactor-hub
antonpirker Nov 30, 2023
fe77d04
Give the client to the scope function. Want to establish a pattern.
antonpirker Dec 1, 2023
4ba7dce
Merge branch 'antonpirker/refactor-hub' of github.com:getsentry/sentr…
antonpirker Dec 1, 2023
b15613a
Moved capture_* functions from Hub to Client
antonpirker Dec 1, 2023
8331bd0
Renamed scope_args to scope_kwargs
antonpirker Dec 1, 2023
ce1759f
oops
antonpirker Dec 1, 2023
9a08b6e
preserve old behavior when called from client directly (not from hub)
antonpirker Dec 1, 2023
f9b8d5a
update test
antonpirker Dec 1, 2023
d233ae5
Merge branch 'master' into antonpirker/refactor-hub-capture
antonpirker Dec 4, 2023
2932adc
Merge branch 'master' into antonpirker/refactor-hub
antonpirker Dec 4, 2023
3a7af73
Merge branch 'antonpirker/refactor-hub' into antonpirker/refactor-hub…
antonpirker Dec 4, 2023
345c419
Merge branch 'master' into antonpirker/refactor-hub-capture
sentrivana Dec 6, 2023
bb3250a
_update_scope belongs into the scope not the client
antonpirker Dec 11, 2023
3a22dd6
Made top_scope separate parameter
antonpirker Dec 11, 2023
c78b1f5
Hub calls capture_* on scope that calls it on client. Later with new …
antonpirker Dec 13, 2023
0901e72
Merge branch 'feat/new-scopes' into antonpirker/refactor-hub-capture
antonpirker Dec 13, 2023
269fd56
Moved everything to scope and only have capture_event in client
antonpirker Dec 13, 2023
0354065
small fix
antonpirker Dec 13, 2023
3d5825e
Updated test
antonpirker Dec 13, 2023
4e78d1f
Fixed test
antonpirker Dec 13, 2023
ba72216
Fixed test
antonpirker Dec 14, 2023
c5a7ae7
Fixed test for old python
antonpirker Dec 14, 2023
bbb93e6
Formatting
antonpirker Dec 14, 2023
12e632d
Passing the client as separate argument and updating api docs.
antonpirker Dec 14, 2023
eb56a12
ApiDocs
antonpirker Dec 14, 2023
074a9ef
Fix
antonpirker Dec 14, 2023
6733114
Remove condition from _merge_scope (also not present in current imple…
antonpirker Dec 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
102 changes: 99 additions & 3 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
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,
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -544,6 +548,8 @@ 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.
Expand All @@ -552,14 +558,21 @@ 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`.

: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

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")
Expand Down Expand Up @@ -647,6 +660,89 @@ 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]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not add top_scope here.

If you see the RFC, the 3 scopes will be processed by Scope.capture_event.

The managing of the scopes is currently done by the Hub and will be moved to Scope later. I think the client should just get one scope as before and the merging will be moved later from Hub -> 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
):
Expand Down
92 changes: 42 additions & 50 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,86 @@ 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`.

:param scope_args: For supported `**scope_args` see
:param scope_kwargs: For supported `**scope_kwargs` see
: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

def capture_message(self, message, level=None, scope=None, **scope_args):
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:
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.

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_args: For supported `**scope_args` 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`).
"""
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 = client.capture_message(
message, level=level, scope=scope, top_scope=top_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.

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
: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 = 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 = 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

return None
return last_event_id

def _capture_internal_exception(
self, exc_info # type: Any
Expand All @@ -401,6 +391,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
22 changes: 22 additions & 0 deletions sentry_sdk/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,28 @@ 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.
Expand Down
2 changes: 1 addition & 1 deletion tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down