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
Copy Markdown
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
Copy Markdown
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)

Comment thread
alexander-alderman-webb marked this conversation as resolved.
Comment thread
alexander-alderman-webb marked this conversation as resolved.

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
Comment thread
alexander-alderman-webb marked this conversation as resolved.
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"),
Comment thread
alexander-alderman-webb marked this conversation as resolved.
(["executing"], "sentry.python"),
(["modules"], "sentry.python"),
(["pure_eval"], "sentry.python"),
Expand Down
Loading