Skip to content

Commit 0d706f7

Browse files
feat(integrations): Add unraisable exception integration
Adds an uncaught exception integration, enabled by default. The integration forwards the exception to Sentry only if the exception value and stacktrace are set.
1 parent d9b8f9d commit 0d706f7

File tree

4 files changed

+104
-0
lines changed

4 files changed

+104
-0
lines changed

sentry_sdk/integrations/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def iter_default_integrations(with_auto_enabling_integrations):
7171
"sentry_sdk.integrations.modules.ModulesIntegration",
7272
"sentry_sdk.integrations.stdlib.StdlibIntegration",
7373
"sentry_sdk.integrations.threading.ThreadingIntegration",
74+
"sentry_sdk.integrations.unraisablehook.UnraisablehookIntegration",
7475
]
7576

7677
_AUTO_ENABLING_INTEGRATIONS = [
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import sys
2+
3+
import sentry_sdk
4+
from sentry_sdk.utils import (
5+
capture_internal_exceptions,
6+
event_from_exception,
7+
)
8+
from sentry_sdk.integrations import Integration
9+
10+
from typing import TYPE_CHECKING
11+
12+
if TYPE_CHECKING:
13+
from typing import Callable
14+
from typing import Any
15+
16+
17+
class UnraisablehookIntegration(Integration):
18+
identifier = "unraisable"
19+
20+
@staticmethod
21+
def setup_once():
22+
# type: () -> None
23+
sys.unraisablehook = _make_unraisable(sys.unraisablehook)
24+
25+
26+
def _make_unraisable(old_unraisablehook):
27+
# type: (Callable[[sys.UnraisableHookArgs], Any]) -> Callable[[sys.UnraisableHookArgs], Any]
28+
def sentry_sdk_unraisablehook(unraisable):
29+
# type: (sys.UnraisableHookArgs) -> None
30+
integration = sentry_sdk.get_client().get_integration(UnraisablehookIntegration)
31+
32+
# Note: If we replace this with ensure_integration_enabled then
33+
# we break the exceptiongroup backport;
34+
# See: https://github.com/getsentry/sentry-python/issues/3097
35+
if integration is None:
36+
return old_unraisablehook(unraisable)
37+
38+
if unraisable.exc_value and unraisable.exc_traceback:
39+
with capture_internal_exceptions():
40+
event, hint = event_from_exception(
41+
(
42+
unraisable.exc_type,
43+
unraisable.exc_value,
44+
unraisable.exc_traceback,
45+
),
46+
client_options=sentry_sdk.get_client().options,
47+
mechanism={"type": "unraisablehook", "handled": False},
48+
)
49+
sentry_sdk.capture_event(event, hint=hint)
50+
51+
return old_unraisablehook(unraisable)
52+
53+
return sentry_sdk_unraisablehook
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import pytest
2+
import sys
3+
import subprocess
4+
5+
from textwrap import dedent
6+
7+
8+
TEST_PARAMETERS = [("", "HttpTransport")]
9+
10+
if sys.version_info >= (3, 8):
11+
TEST_PARAMETERS.append(('_experiments={"transport_http2": True}', "Http2Transport"))
12+
13+
14+
@pytest.mark.parametrize("options, transport", TEST_PARAMETERS)
15+
def test_excepthook(tmpdir, options, transport):
16+
app = tmpdir.join("app.py")
17+
app.write(
18+
dedent(
19+
"""
20+
from sentry_sdk import init, transport
21+
22+
class Undeletable:
23+
def __del__(self):
24+
1 / 0
25+
26+
def capture_envelope(self, envelope):
27+
print("capture_envelope was called")
28+
event = envelope.get_event()
29+
if event is not None:
30+
print(event)
31+
32+
transport.{transport}.capture_envelope = capture_envelope
33+
34+
init("http://foobar@localhost/123", {options})
35+
36+
undeletable = Undeletable()
37+
del undeletable
38+
""".format(
39+
transport=transport, options=options
40+
)
41+
)
42+
)
43+
44+
output = subprocess.check_output(
45+
[sys.executable, str(app)], stderr=subprocess.STDOUT
46+
)
47+
48+
assert b"ZeroDivisionError" in output
49+
assert b"capture_envelope was called" in output

tests/test_basics.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,7 @@ def foo(event, hint):
870870
(["celery"], "sentry.python"),
871871
(["dedupe"], "sentry.python"),
872872
(["excepthook"], "sentry.python"),
873+
(["unraisablehook"], "sentry.python"),
873874
(["executing"], "sentry.python"),
874875
(["modules"], "sentry.python"),
875876
(["pure_eval"], "sentry.python"),

0 commit comments

Comments
 (0)