Skip to content

Commit

Permalink
Use executing to infer code qualname (#749)
Browse files Browse the repository at this point in the history
See #748
  • Loading branch information
alexmojaki authored Jul 13, 2020
1 parent 0ee6a25 commit 5c34ead
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 8 deletions.
2 changes: 2 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ ignore_missing_imports = True
ignore_missing_imports = True
[mypy-asgiref.*]
ignore_missing_imports = True
[mypy-executing.*]
ignore_missing_imports = True
2 changes: 1 addition & 1 deletion sentry_sdk/integrations/django/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def process_django_templates(event, hint):
for i in reversed(range(len(frames))):
f = frames[i]
if (
f.get("function") in ("parse", "render")
f.get("function") in ("Parser.parse", "parse", "render")
and f.get("module") == "django.template.base"
):
i += 1
Expand Down
68 changes: 68 additions & 0 deletions sentry_sdk/integrations/executing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from __future__ import absolute_import

from sentry_sdk import Hub
from sentry_sdk._types import MYPY
from sentry_sdk.integrations import Integration, DidNotEnable
from sentry_sdk.scope import add_global_event_processor
from sentry_sdk.utils import walk_exception_chain, iter_stacks

if MYPY:
from typing import Optional

from sentry_sdk._types import Event, Hint

try:
import executing
except ImportError:
raise DidNotEnable("executing is not installed")


class ExecutingIntegration(Integration):
identifier = "executing"

@staticmethod
def setup_once():
# type: () -> None

@add_global_event_processor
def add_executing_info(event, hint):
# type: (Event, Optional[Hint]) -> Optional[Event]
if Hub.current.get_integration(ExecutingIntegration) is None:
return event

if hint is None:
return event

exc_info = hint.get("exc_info", None)

if exc_info is None:
return event

exception = event.get("exception", None)

if exception is None:
return event

values = exception.get("values", None)

if values is None:
return event

for exception, (_exc_type, _exc_value, exc_tb) in zip(
reversed(values), walk_exception_chain(exc_info)
):
sentry_frames = [
frame
for frame in exception.get("stacktrace", {}).get("frames", [])
if frame.get("function")
]
tbs = list(iter_stacks(exc_tb))
if len(sentry_frames) != len(tbs):
continue

for sentry_frame, tb in zip(sentry_frames, tbs):
frame = tb.tb_frame
source = executing.Source.for_frame(frame)
sentry_frame["function"] = source.code_qualname(frame.f_code)

return event
1 change: 1 addition & 0 deletions sentry_sdk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

from sentry_sdk._types import ExcInfo, EndpointType


epoch = datetime(1970, 1, 1)


Expand Down
1 change: 1 addition & 0 deletions test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ pytest-cov==2.8.1
gevent
eventlet
newrelic
executing
26 changes: 19 additions & 7 deletions tests/integrations/django/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from django.core.management import execute_from_command_line
from django.db.utils import OperationalError, ProgrammingError, DataError

from sentry_sdk.integrations.executing import ExecutingIntegration

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


def test_template_exception(sentry_init, client, capture_events):
sentry_init(integrations=[DjangoIntegration()])
@pytest.mark.parametrize("with_executing_integration", [[], [ExecutingIntegration()]])
def test_template_exception(
sentry_init, client, capture_events, with_executing_integration
):
sentry_init(integrations=[DjangoIntegration()] + with_executing_integration)
events = capture_events()

content, status, headers = client.get(reverse("template_exc"))
Expand Down Expand Up @@ -437,11 +441,19 @@ def test_template_exception(sentry_init, client, capture_events):
filenames = [
(f.get("function"), f.get("module")) for f in exception["stacktrace"]["frames"]
]
assert filenames[-3:] == [
(u"parse", u"django.template.base"),
(None, None),
(u"invalid_block_tag", u"django.template.base"),
]

if with_executing_integration:
assert filenames[-3:] == [
(u"Parser.parse", u"django.template.base"),
(None, None),
(u"Parser.invalid_block_tag", u"django.template.base"),
]
else:
assert filenames[-3:] == [
(u"parse", u"django.template.base"),
(None, None),
(u"invalid_block_tag", u"django.template.base"),
]


@pytest.mark.parametrize(
Expand Down
31 changes: 31 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
capture_exception,
capture_event,
)
from sentry_sdk.integrations.executing import ExecutingIntegration
from sentry_sdk.transport import Transport
from sentry_sdk._compat import reraise, text_type, PY2
from sentry_sdk.utils import HAS_CHAINED_EXCEPTIONS
Expand Down Expand Up @@ -216,6 +217,35 @@ def test_with_locals_disabled(sentry_init, capture_events):
)


@pytest.mark.parametrize("integrations", [[], [ExecutingIntegration()]])
def test_function_names(sentry_init, capture_events, integrations):
sentry_init(integrations=integrations)
events = capture_events()

def foo():
try:
bar()
except Exception:
capture_exception()

def bar():
1 / 0

foo()

(event,) = events
(thread,) = event["exception"]["values"]
functions = [x["function"] for x in thread["stacktrace"]["frames"]]

if integrations:
assert functions == [
"test_function_names.<locals>.foo",
"test_function_names.<locals>.bar",
]
else:
assert functions == ["foo", "bar"]


def test_attach_stacktrace_enabled(sentry_init, capture_events):
sentry_init(attach_stacktrace=True)
events = capture_events()
Expand All @@ -231,6 +261,7 @@ def bar():
(event,) = events
(thread,) = event["threads"]["values"]
functions = [x["function"] for x in thread["stacktrace"]["frames"]]

assert functions[-2:] == ["foo", "bar"]


Expand Down

0 comments on commit 5c34ead

Please sign in to comment.