Skip to content
Merged
1 change: 1 addition & 0 deletions sentry_sdk/integrations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def iter_default_integrations(with_auto_enabling_integrations):
"sentry_sdk.integrations.modules.ModulesIntegration",
"sentry_sdk.integrations.stdlib.StdlibIntegration",
"sentry_sdk.integrations.threading.ThreadingIntegration",
"sentry_sdk.integrations.unraisablehook.UnraisablehookIntegration",
]

_AUTO_ENABLING_INTEGRATIONS = [
Expand Down
53 changes: 53 additions & 0 deletions sentry_sdk/integrations/unraisablehook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import sys

import sentry_sdk
from sentry_sdk.utils import (
capture_internal_exceptions,
event_from_exception,
)
from sentry_sdk.integrations import Integration

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Callable
from typing import Any


class UnraisablehookIntegration(Integration):
identifier = "unraisable"

@staticmethod
def setup_once():
# type: () -> None
sys.unraisablehook = _make_unraisable(sys.unraisablehook)


def _make_unraisable(old_unraisablehook):
# type: (Callable[[sys.UnraisableHookArgs], Any]) -> Callable[[sys.UnraisableHookArgs], Any]
def sentry_sdk_unraisablehook(unraisable):
# type: (sys.UnraisableHookArgs) -> None
integration = sentry_sdk.get_client().get_integration(UnraisablehookIntegration)

# Note: If we replace this with ensure_integration_enabled then
# we break the exceptiongroup backport;
# See: https://github.com/getsentry/sentry-python/issues/3097
if integration is None:
return old_unraisablehook(unraisable)

if unraisable.exc_value and unraisable.exc_traceback:
with capture_internal_exceptions():
event, hint = event_from_exception(
(
unraisable.exc_type,
unraisable.exc_value,
unraisable.exc_traceback,
),
client_options=sentry_sdk.get_client().options,
mechanism={"type": "unraisablehook", "handled": False},
)
sentry_sdk.capture_event(event, hint=hint)

return old_unraisablehook(unraisable)

return sentry_sdk_unraisablehook
49 changes: 49 additions & 0 deletions tests/integrations/unraisablehook/test_unraisablehook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import pytest
import sys
import subprocess

from textwrap import dedent


TEST_PARAMETERS = [("", "HttpTransport")]

if sys.version_info >= (3, 8):
TEST_PARAMETERS.append(('_experiments={"transport_http2": True}', "Http2Transport"))


@pytest.mark.parametrize("options, transport", TEST_PARAMETERS)
def test_excepthook(tmpdir, options, transport):
app = tmpdir.join("app.py")
app.write(
dedent(
"""
from sentry_sdk import init, transport
class Undeletable:
def __del__(self):
1 / 0
def capture_envelope(self, envelope):
print("capture_envelope was called")
event = envelope.get_event()
if event is not None:
print(event)
transport.{transport}.capture_envelope = capture_envelope
init("http://foobar@localhost/123", {options})
undeletable = Undeletable()
del undeletable
""".format(
transport=transport, options=options
)
)
)

output = subprocess.check_output(
[sys.executable, str(app)], stderr=subprocess.STDOUT
)

assert b"ZeroDivisionError" in output
assert b"capture_envelope was called" in output
1 change: 1 addition & 0 deletions tests/test_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,7 @@ def foo(event, hint):
(["celery"], "sentry.python"),
(["dedupe"], "sentry.python"),
(["excepthook"], "sentry.python"),
(["unraisablehook"], "sentry.python"),
(["executing"], "sentry.python"),
(["modules"], "sentry.python"),
(["pure_eval"], "sentry.python"),
Expand Down
Loading