Skip to content
Merged
6 changes: 6 additions & 0 deletions sentry_sdk/integrations/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from abc import ABC, abstractmethod
from threading import Lock
import sys

from sentry_sdk.utils import logger

Expand Down Expand Up @@ -73,6 +74,11 @@ def iter_default_integrations(with_auto_enabling_integrations):
"sentry_sdk.integrations.threading.ThreadingIntegration",
]

if sys.version_info >= (3, 8):
_DEFAULT_INTEGRATIONS.append(
"sentry_sdk.integrations.unraisablehook.UnraisablehookIntegration"
)

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd not make this default just yet, let's give it some time to soak. We can turn it on by default in 3.0 on the potel-base branch -- could you prepare a PR for that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes sounds good! #4749

_AUTO_ENABLING_INTEGRATIONS = [
"sentry_sdk.integrations.aiohttp.AioHttpIntegration",
"sentry_sdk.integrations.anthropic.AnthropicIntegration",
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 = "unraisablehook"

@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_unraisablehook(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