Skip to content

Use executing to infer code qualname #749

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jul 13, 2020
Merged
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