Skip to content

Commit 7c6f107

Browse files
authored
feat: Add mechanism data to event (#62)
1 parent 4dff56a commit 7c6f107

File tree

11 files changed

+121
-54
lines changed

11 files changed

+121
-54
lines changed

sentry_sdk/integrations/_wsgi.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import json
22
import sys
33

4-
from sentry_sdk import capture_exception
54
from sentry_sdk.hub import Hub, _should_send_default_pii, _get_client_options
6-
from sentry_sdk.utils import AnnotatedValue, capture_internal_exceptions
5+
from sentry_sdk.utils import (
6+
AnnotatedValue,
7+
capture_internal_exceptions,
8+
event_from_exception,
9+
)
710
from sentry_sdk._compat import reraise, implements_iterator
811

912

@@ -155,14 +158,24 @@ def run_wsgi_app(app, environ, start_response):
155158
try:
156159
rv = app(environ, start_response)
157160
except Exception:
158-
einfo = sys.exc_info()
159-
capture_exception(einfo)
161+
einfo = _capture_exception(hub)
160162
hub.pop_scope_unsafe()
161163
reraise(*einfo)
162164

163165
return _ScopePoppingResponse(hub, rv)
164166

165167

168+
def _capture_exception(hub):
169+
exc_info = sys.exc_info()
170+
event, hint = event_from_exception(
171+
exc_info,
172+
with_locals=hub.client.options["with_locals"],
173+
mechanism={"type": "wsgi", "handled": False},
174+
)
175+
hub.capture_event(event, hint=hint)
176+
return exc_info
177+
178+
166179
@implements_iterator
167180
class _ScopePoppingResponse(object):
168181
__slots__ = ("_response", "_hub")
@@ -175,9 +188,7 @@ def __iter__(self):
175188
try:
176189
self._response = iter(self._response)
177190
except Exception:
178-
einfo = sys.exc_info()
179-
capture_exception(einfo)
180-
reraise(*einfo)
191+
reraise(*_capture_exception(self.hub))
181192
return self
182193

183194
def __next__(self):
@@ -186,19 +197,15 @@ def __next__(self):
186197
except StopIteration:
187198
raise
188199
except Exception:
189-
einfo = sys.exc_info()
190-
capture_exception(einfo)
191-
reraise(*einfo)
200+
reraise(*_capture_exception(self.hub))
192201

193202
def close(self):
194203
self._hub.pop_scope_unsafe()
195204
if hasattr(self._response, "close"):
196205
try:
197206
self._response.close()
198207
except Exception:
199-
einfo = sys.exc_info()
200-
capture_exception(einfo)
201-
reraise(*einfo)
208+
reraise(*_capture_exception(self.hub))
202209

203210

204211
def _make_wsgi_event_processor(environ):

sentry_sdk/integrations/celery.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from celery.exceptions import SoftTimeLimitExceeded
55

66
from sentry_sdk import Hub
7-
from sentry_sdk.utils import capture_internal_exceptions
7+
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
88
from sentry_sdk.integrations import Integration
99

1010

@@ -33,9 +33,18 @@ def _process_failure_signal(self, sender, task_id, einfo, **kw):
3333
getattr(sender, "name", sender),
3434
]
3535

36-
hub.capture_exception(einfo.exc_info)
36+
self._capture_event(hub, einfo.exc_info)
3737
else:
38-
hub.capture_exception(einfo.exc_info)
38+
self._capture_event(hub, einfo.exc_info)
39+
40+
def _capture_event(self, hub, exc_info):
41+
event, hint = event_from_exception(
42+
exc_info,
43+
with_locals=hub.client.options["with_locals"],
44+
mechanism={"type": "celery", "handled": False},
45+
)
46+
47+
hub.capture_event(event, hint=hint)
3948

4049
def _handle_task_prerun(self, sender, task, **kw):
4150
with capture_internal_exceptions():

sentry_sdk/integrations/django.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import absolute_import
22

3+
import sys
34
import weakref
45

56
from django import VERSION as DJANGO_VERSION
@@ -10,9 +11,9 @@
1011
except ImportError:
1112
from django.core.urlresolvers import resolve
1213

13-
from sentry_sdk import capture_exception, configure_scope
14+
from sentry_sdk import Hub, configure_scope
1415
from sentry_sdk.hub import _should_send_default_pii
15-
from sentry_sdk.utils import capture_internal_exceptions
16+
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
1617
from sentry_sdk.integrations import Integration
1718
from sentry_sdk.integrations._wsgi import RequestExtractor, run_wsgi_app
1819

@@ -90,7 +91,14 @@ def event_processor(event, hint):
9091

9192

9293
def _got_request_exception(request=None, **kwargs):
93-
capture_exception()
94+
hub = Hub.current
95+
event, hint = event_from_exception(
96+
sys.exc_info(),
97+
with_locals=hub.client.options["with_locals"],
98+
mechanism={"type": "django", "handled": False},
99+
)
100+
101+
hub.capture_event(event, hint=hint)
94102

95103

96104
class DjangoRequestExtractor(RequestExtractor):

sentry_sdk/integrations/excepthook.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import sys
22

3-
from sentry_sdk import capture_exception
4-
from sentry_sdk.utils import capture_internal_exceptions
3+
from sentry_sdk import Hub
4+
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
55
from sentry_sdk.integrations import Integration
66

77

@@ -20,7 +20,13 @@ def install(self):
2020
def _make_excepthook(old_excepthook):
2121
def sentry_sdk_excepthook(exctype, value, traceback):
2222
with capture_internal_exceptions():
23-
capture_exception((exctype, value, traceback))
23+
hub = Hub.current
24+
event, hint = event_from_exception(
25+
(exctype, value, traceback),
26+
with_locals=hub.client.options["with_locals"],
27+
mechanism={"type": "excepthook", "handled": False},
28+
)
29+
hub.capture_event(event, hint=hint)
2430

2531
return old_excepthook(exctype, value, traceback)
2632

sentry_sdk/integrations/flask.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import weakref
44

5-
from sentry_sdk import Hub, capture_exception, configure_scope
5+
from sentry_sdk import Hub, configure_scope
66
from sentry_sdk.hub import _should_send_default_pii
7-
from sentry_sdk.utils import capture_internal_exceptions
7+
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
88
from sentry_sdk.integrations import Integration
99
from sentry_sdk.integrations._wsgi import RequestExtractor, run_wsgi_app
1010

@@ -105,7 +105,14 @@ def size_of_file(self, file):
105105

106106

107107
def _capture_exception(sender, exception, **kwargs):
108-
capture_exception(exception)
108+
hub = Hub.current
109+
event, hint = event_from_exception(
110+
exception,
111+
with_locals=hub.client.options["with_locals"],
112+
mechanism={"type": "flask", "handled": False},
113+
)
114+
115+
hub.capture_event(event, hint=hint)
109116

110117

111118
def _make_request_event_processor(app, weak_request):

sentry_sdk/integrations/logging.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import logging
55
import datetime
66

7-
from sentry_sdk import capture_event, add_breadcrumb
87
from sentry_sdk.utils import (
98
to_string,
109
event_from_exception,
@@ -72,17 +71,19 @@ def _emit(self, record):
7271
if not self.can_record(record):
7372
return
7473

75-
if self._should_create_event(record):
76-
hub = Hub.current
77-
if hub.client is None:
78-
return
74+
hub = Hub.current
75+
if hub.client is None:
76+
return
7977

78+
if self._should_create_event(record):
8079
with capture_internal_exceptions():
8180
hint = None
8281
# exc_info might be None or (None, None, None)
8382
if record.exc_info is not None and record.exc_info[0] is not None:
8483
event, hint = event_from_exception(
85-
record.exc_info, with_locals=hub.client.options["with_locals"]
84+
record.exc_info,
85+
with_locals=hub.client.options["with_locals"],
86+
mechanism={"type": "logging", "handled": True},
8687
)
8788
else:
8889
event = {}
@@ -94,10 +95,10 @@ def _emit(self, record):
9495
"params": record.args,
9596
}
9697

97-
capture_event(event, hint=hint)
98+
hub.capture_event(event, hint=hint)
9899

99100
with capture_internal_exceptions():
100-
add_breadcrumb(
101+
hub.add_breadcrumb(
101102
self._breadcrumb_from_record(record), hint={"log_record": record}
102103
)
103104

sentry_sdk/utils.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -322,21 +322,36 @@ def stacktrace_from_traceback(tb, with_locals=True):
322322
return {"frames": [frame_from_traceback(tb, with_locals) for tb in iter_stacks(tb)]}
323323

324324

325-
def single_exception_from_error_tuple(exc_type, exc_value, tb, with_locals=True):
325+
def get_errno(exc_value):
326+
return getattr(exc_value, "errno", None)
327+
328+
329+
def single_exception_from_error_tuple(
330+
exc_type, exc_value, tb, with_locals=True, mechanism=None
331+
):
332+
errno = get_errno(exc_value)
333+
if errno is not None:
334+
mechanism = mechanism or {}
335+
mechanism_meta = mechanism.setdefault("meta", {})
336+
mechanism_meta.setdefault("errno", {"code": errno})
337+
326338
return {
327339
"module": get_type_module(exc_type),
328340
"type": get_type_name(exc_type),
329341
"value": safe_str(exc_value),
330342
"stacktrace": stacktrace_from_traceback(tb, with_locals),
343+
"mechanism": mechanism,
331344
}
332345

333346

334-
def exceptions_from_error_tuple(exc_info, with_locals=True):
347+
def exceptions_from_error_tuple(exc_info, with_locals=True, mechanism=None):
335348
exc_type, exc_value, tb = exc_info
336349
rv = []
337350
while exc_type is not None:
338351
rv.append(
339-
single_exception_from_error_tuple(exc_type, exc_value, tb, with_locals)
352+
single_exception_from_error_tuple(
353+
exc_type, exc_value, tb, with_locals, mechanism
354+
)
340355
)
341356
cause = getattr(exc_value, "__cause__", None)
342357
if cause is None:
@@ -414,13 +429,15 @@ def exc_info_from_error(error):
414429
return exc_type, exc_value, tb
415430

416431

417-
def event_from_exception(exc_info, with_locals=False, processors=None):
432+
def event_from_exception(exc_info, with_locals=False, processors=None, mechanism=None):
418433
exc_info = exc_info_from_error(exc_info)
419434
hint = event_hint_with_exc_info(exc_info)
420435
return (
421436
{
422437
"level": "error",
423-
"exception": {"values": exceptions_from_error_tuple(exc_info, with_locals)},
438+
"exception": {
439+
"values": exceptions_from_error_tuple(exc_info, with_locals, mechanism)
440+
},
424441
},
425442
hint,
426443
)

tests/integrations/celery/test_celery.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ def celery(sentry_init):
1616

1717

1818
def test_simple(capture_events, celery):
19-
2019
events = capture_events()
2120

2221
@celery.task(name="dummy_task")
@@ -31,6 +30,9 @@ def dummy_task(x, y):
3130
exception, = event["exception"]["values"]
3231
assert exception["type"] == "ZeroDivisionError"
3332

33+
event, = events
34+
assert event["exception"]["values"][0]["mechanism"]["type"] == "celery"
35+
3436

3537
def test_ignore_expected(capture_events, celery):
3638
events = capture_events()

tests/integrations/conftest.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import sys
21
import pytest
32
import sentry_sdk
43

@@ -7,19 +6,16 @@
76
def capture_exceptions(monkeypatch):
87
def inner():
98
errors = set()
10-
old_capture_exception = sentry_sdk.Hub.current.capture_exception
9+
old_capture_event = sentry_sdk.Hub.current.capture_event
1110

12-
def capture_exception(error=None):
13-
given_error = error
14-
error = error or sys.exc_info()[1]
15-
if isinstance(error, tuple):
16-
error = error[1]
17-
errors.add(error)
18-
return old_capture_exception(given_error)
11+
def capture_event(event, hint=None):
12+
if hint:
13+
if "exc_info" in hint:
14+
error = hint["exc_info"][1]
15+
errors.add(error)
16+
return old_capture_event(event, hint=hint)
1917

20-
monkeypatch.setattr(
21-
sentry_sdk.Hub.current, "capture_exception", capture_exception
22-
)
18+
monkeypatch.setattr(sentry_sdk.Hub.current, "capture_event", capture_event)
2319
return errors
2420

2521
return inner

tests/integrations/django/test_basic.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,17 @@ def client(monkeypatch_test_transport):
2323
return Client(application)
2424

2525

26-
def test_view_exceptions(client, capture_exceptions):
26+
def test_view_exceptions(client, capture_exceptions, capture_events):
2727
exceptions = capture_exceptions()
28+
events = capture_events()
2829
client.get(reverse("view_exc"))
2930

3031
error, = exceptions
3132
assert isinstance(error, ZeroDivisionError)
3233

34+
event, = events
35+
assert event["exception"]["values"][0]["mechanism"]["type"] == "django"
36+
3337

3438
def test_middleware_exceptions(client, capture_exceptions):
3539
exceptions = capture_exceptions()

0 commit comments

Comments
 (0)