Skip to content

Commit 5c34ead

Browse files
authored
Use executing to infer code qualname (#749)
See #748
1 parent 0ee6a25 commit 5c34ead

File tree

7 files changed

+123
-8
lines changed

7 files changed

+123
-8
lines changed

mypy.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,5 @@ ignore_missing_imports = True
4848
ignore_missing_imports = True
4949
[mypy-asgiref.*]
5050
ignore_missing_imports = True
51+
[mypy-executing.*]
52+
ignore_missing_imports = True

sentry_sdk/integrations/django/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def process_django_templates(event, hint):
157157
for i in reversed(range(len(frames))):
158158
f = frames[i]
159159
if (
160-
f.get("function") in ("parse", "render")
160+
f.get("function") in ("Parser.parse", "parse", "render")
161161
and f.get("module") == "django.template.base"
162162
):
163163
i += 1

sentry_sdk/integrations/executing.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from __future__ import absolute_import
2+
3+
from sentry_sdk import Hub
4+
from sentry_sdk._types import MYPY
5+
from sentry_sdk.integrations import Integration, DidNotEnable
6+
from sentry_sdk.scope import add_global_event_processor
7+
from sentry_sdk.utils import walk_exception_chain, iter_stacks
8+
9+
if MYPY:
10+
from typing import Optional
11+
12+
from sentry_sdk._types import Event, Hint
13+
14+
try:
15+
import executing
16+
except ImportError:
17+
raise DidNotEnable("executing is not installed")
18+
19+
20+
class ExecutingIntegration(Integration):
21+
identifier = "executing"
22+
23+
@staticmethod
24+
def setup_once():
25+
# type: () -> None
26+
27+
@add_global_event_processor
28+
def add_executing_info(event, hint):
29+
# type: (Event, Optional[Hint]) -> Optional[Event]
30+
if Hub.current.get_integration(ExecutingIntegration) is None:
31+
return event
32+
33+
if hint is None:
34+
return event
35+
36+
exc_info = hint.get("exc_info", None)
37+
38+
if exc_info is None:
39+
return event
40+
41+
exception = event.get("exception", None)
42+
43+
if exception is None:
44+
return event
45+
46+
values = exception.get("values", None)
47+
48+
if values is None:
49+
return event
50+
51+
for exception, (_exc_type, _exc_value, exc_tb) in zip(
52+
reversed(values), walk_exception_chain(exc_info)
53+
):
54+
sentry_frames = [
55+
frame
56+
for frame in exception.get("stacktrace", {}).get("frames", [])
57+
if frame.get("function")
58+
]
59+
tbs = list(iter_stacks(exc_tb))
60+
if len(sentry_frames) != len(tbs):
61+
continue
62+
63+
for sentry_frame, tb in zip(sentry_frames, tbs):
64+
frame = tb.tb_frame
65+
source = executing.Source.for_frame(frame)
66+
sentry_frame["function"] = source.code_qualname(frame.f_code)
67+
68+
return event

sentry_sdk/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
from sentry_sdk._types import ExcInfo, EndpointType
3030

31+
3132
epoch = datetime(1970, 1, 1)
3233

3334

test-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ pytest-cov==2.8.1
77
gevent
88
eventlet
99
newrelic
10+
executing

tests/integrations/django/test_basic.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from django.core.management import execute_from_command_line
1010
from django.db.utils import OperationalError, ProgrammingError, DataError
1111

12+
from sentry_sdk.integrations.executing import ExecutingIntegration
1213

1314
try:
1415
from django.urls import reverse
@@ -408,8 +409,11 @@ def test_read_request(sentry_init, client, capture_events):
408409
assert "data" not in event["request"]
409410

410411

411-
def test_template_exception(sentry_init, client, capture_events):
412-
sentry_init(integrations=[DjangoIntegration()])
412+
@pytest.mark.parametrize("with_executing_integration", [[], [ExecutingIntegration()]])
413+
def test_template_exception(
414+
sentry_init, client, capture_events, with_executing_integration
415+
):
416+
sentry_init(integrations=[DjangoIntegration()] + with_executing_integration)
413417
events = capture_events()
414418

415419
content, status, headers = client.get(reverse("template_exc"))
@@ -437,11 +441,19 @@ def test_template_exception(sentry_init, client, capture_events):
437441
filenames = [
438442
(f.get("function"), f.get("module")) for f in exception["stacktrace"]["frames"]
439443
]
440-
assert filenames[-3:] == [
441-
(u"parse", u"django.template.base"),
442-
(None, None),
443-
(u"invalid_block_tag", u"django.template.base"),
444-
]
444+
445+
if with_executing_integration:
446+
assert filenames[-3:] == [
447+
(u"Parser.parse", u"django.template.base"),
448+
(None, None),
449+
(u"Parser.invalid_block_tag", u"django.template.base"),
450+
]
451+
else:
452+
assert filenames[-3:] == [
453+
(u"parse", u"django.template.base"),
454+
(None, None),
455+
(u"invalid_block_tag", u"django.template.base"),
456+
]
445457

446458

447459
@pytest.mark.parametrize(

tests/test_client.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
capture_exception,
1616
capture_event,
1717
)
18+
from sentry_sdk.integrations.executing import ExecutingIntegration
1819
from sentry_sdk.transport import Transport
1920
from sentry_sdk._compat import reraise, text_type, PY2
2021
from sentry_sdk.utils import HAS_CHAINED_EXCEPTIONS
@@ -216,6 +217,35 @@ def test_with_locals_disabled(sentry_init, capture_events):
216217
)
217218

218219

220+
@pytest.mark.parametrize("integrations", [[], [ExecutingIntegration()]])
221+
def test_function_names(sentry_init, capture_events, integrations):
222+
sentry_init(integrations=integrations)
223+
events = capture_events()
224+
225+
def foo():
226+
try:
227+
bar()
228+
except Exception:
229+
capture_exception()
230+
231+
def bar():
232+
1 / 0
233+
234+
foo()
235+
236+
(event,) = events
237+
(thread,) = event["exception"]["values"]
238+
functions = [x["function"] for x in thread["stacktrace"]["frames"]]
239+
240+
if integrations:
241+
assert functions == [
242+
"test_function_names.<locals>.foo",
243+
"test_function_names.<locals>.bar",
244+
]
245+
else:
246+
assert functions == ["foo", "bar"]
247+
248+
219249
def test_attach_stacktrace_enabled(sentry_init, capture_events):
220250
sentry_init(attach_stacktrace=True)
221251
events = capture_events()
@@ -231,6 +261,7 @@ def bar():
231261
(event,) = events
232262
(thread,) = event["threads"]["values"]
233263
functions = [x["function"] for x in thread["stacktrace"]["frames"]]
264+
234265
assert functions[-2:] == ["foo", "bar"]
235266

236267

0 commit comments

Comments
 (0)