Skip to content

Commit bb20fc6

Browse files
authored
Better setting of in-app in stack frames (#1894)
How the in_app flag is set in stack trace frames (in set_in_app_in_frames()): - If there is already in_app set, it is left untouched. - If there is a module in the frame and it is in the in_app_includes -> in_app=True - If there is a module in the frame and it is in the in_app_excludes -> in_app=False - If there is an abs_path in the frame and the path is in /side-packages/ or /dist-packages/ -> in_app=False - If there is an abs_path in the frame and it starts with the current working directory of the process -> in_app=True - If nothing of the above is true, there will be no in_app set. Fixes #1754 Fixes #320
1 parent 778fde0 commit bb20fc6

File tree

7 files changed

+447
-65
lines changed

7 files changed

+447
-65
lines changed

sentry_sdk/client.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ def _get_options(*args, **kwargs):
9090
if rv["instrumenter"] is None:
9191
rv["instrumenter"] = INSTRUMENTER.SENTRY
9292

93+
if rv["project_root"] is None:
94+
try:
95+
project_root = os.getcwd()
96+
except Exception:
97+
project_root = None
98+
99+
rv["project_root"] = project_root
100+
93101
return rv
94102

95103

@@ -103,6 +111,7 @@ class _Client(object):
103111
def __init__(self, *args, **kwargs):
104112
# type: (*Any, **Any) -> None
105113
self.options = get_options(*args, **kwargs) # type: Dict[str, Any]
114+
106115
self._init_impl()
107116

108117
def __getstate__(self):
@@ -222,7 +231,10 @@ def _prepare_event(
222231
event["platform"] = "python"
223232

224233
event = handle_in_app(
225-
event, self.options["in_app_exclude"], self.options["in_app_include"]
234+
event,
235+
self.options["in_app_exclude"],
236+
self.options["in_app_include"],
237+
self.options["project_root"],
226238
)
227239

228240
# Postprocess the event here so that annotated types do

sentry_sdk/consts.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ def __init__(
123123
proxy_headers=None, # type: Optional[Dict[str, str]]
124124
instrumenter=INSTRUMENTER.SENTRY, # type: Optional[str]
125125
before_send_transaction=None, # type: Optional[TransactionProcessor]
126+
project_root=None, # type: Optional[str]
126127
):
127128
# type: (...) -> None
128129
pass

sentry_sdk/profiler.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@
2727
from sentry_sdk._types import MYPY
2828
from sentry_sdk.utils import (
2929
filename_for_module,
30-
handle_in_app_impl,
3130
logger,
3231
nanosecond_time,
32+
set_in_app_in_frames,
3333
)
3434

3535
if MYPY:
@@ -627,14 +627,14 @@ def process(self):
627627
}
628628

629629
def to_json(self, event_opt, options):
630-
# type: (Any, Dict[str, Any]) -> Dict[str, Any]
630+
# type: (Any, Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
631631
profile = self.process()
632632

633-
handle_in_app_impl(
633+
set_in_app_in_frames(
634634
profile["frames"],
635635
options["in_app_exclude"],
636636
options["in_app_include"],
637-
default_in_app=False, # Do not default a frame to `in_app: True`
637+
options["project_root"],
638638
)
639639

640640
return {

sentry_sdk/utils.py

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -762,44 +762,54 @@ def iter_event_frames(event):
762762
yield frame
763763

764764

765-
def handle_in_app(event, in_app_exclude=None, in_app_include=None):
766-
# type: (Dict[str, Any], Optional[List[str]], Optional[List[str]]) -> Dict[str, Any]
765+
def handle_in_app(event, in_app_exclude=None, in_app_include=None, project_root=None):
766+
# type: (Dict[str, Any], Optional[List[str]], Optional[List[str]], Optional[str]) -> Dict[str, Any]
767767
for stacktrace in iter_event_stacktraces(event):
768-
handle_in_app_impl(
768+
set_in_app_in_frames(
769769
stacktrace.get("frames"),
770770
in_app_exclude=in_app_exclude,
771771
in_app_include=in_app_include,
772+
project_root=project_root,
772773
)
773774

774775
return event
775776

776777

777-
def handle_in_app_impl(frames, in_app_exclude, in_app_include, default_in_app=True):
778-
# type: (Any, Optional[List[str]], Optional[List[str]], bool) -> Optional[Any]
778+
def set_in_app_in_frames(frames, in_app_exclude, in_app_include, project_root=None):
779+
# type: (Any, Optional[List[str]], Optional[List[str]], Optional[str]) -> Optional[Any]
779780
if not frames:
780781
return None
781782

782-
any_in_app = False
783783
for frame in frames:
784-
in_app = frame.get("in_app")
785-
if in_app is not None:
786-
if in_app:
787-
any_in_app = True
784+
# if frame has already been marked as in_app, skip it
785+
current_in_app = frame.get("in_app")
786+
if current_in_app is not None:
788787
continue
789788

790789
module = frame.get("module")
791-
if not module:
792-
continue
793-
elif _module_in_set(module, in_app_include):
790+
791+
# check if module in frame is in the list of modules to include
792+
if _module_in_list(module, in_app_include):
794793
frame["in_app"] = True
795-
any_in_app = True
796-
elif _module_in_set(module, in_app_exclude):
794+
continue
795+
796+
# check if module in frame is in the list of modules to exclude
797+
if _module_in_list(module, in_app_exclude):
797798
frame["in_app"] = False
799+
continue
798800

799-
if default_in_app and not any_in_app:
800-
for frame in frames:
801-
if frame.get("in_app") is None:
802-
frame["in_app"] = True
801+
# if frame has no abs_path, skip further checks
802+
abs_path = frame.get("abs_path")
803+
if abs_path is None:
804+
continue
805+
806+
if _is_external_source(abs_path):
807+
frame["in_app"] = False
808+
continue
809+
810+
if _is_in_project_root(abs_path, project_root):
811+
frame["in_app"] = True
812+
continue
803813

804814
return frames
805815

@@ -847,13 +857,39 @@ def event_from_exception(
847857
)
848858

849859

850-
def _module_in_set(name, set):
860+
def _module_in_list(name, items):
851861
# type: (str, Optional[List[str]]) -> bool
852-
if not set:
862+
if name is None:
863+
return False
864+
865+
if not items:
853866
return False
854-
for item in set or ():
867+
868+
for item in items:
855869
if item == name or name.startswith(item + "."):
856870
return True
871+
872+
return False
873+
874+
875+
def _is_external_source(abs_path):
876+
# type: (str) -> bool
877+
# check if frame is in 'site-packages' or 'dist-packages'
878+
external_source = (
879+
re.search(r"[\\/](?:dist|site)-packages[\\/]", abs_path) is not None
880+
)
881+
return external_source
882+
883+
884+
def _is_in_project_root(abs_path, project_root):
885+
# type: (str, Optional[str]) -> bool
886+
if project_root is None:
887+
return False
888+
889+
# check if path is in the project root
890+
if abs_path.startswith(project_root):
891+
return True
892+
857893
return False
858894

859895

tests/integrations/django/test_basic.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,6 @@ def test_template_exception(
601601

602602
assert template_frame["post_context"] == ["11\n", "12\n", "13\n", "14\n", "15\n"]
603603
assert template_frame["lineno"] == 10
604-
assert template_frame["in_app"]
605604
assert template_frame["filename"].endswith("error.html")
606605

607606
filenames = [

tests/test_client.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,6 @@ def test_attach_stacktrace_in_app(sentry_init, capture_events):
401401
pytest_frames = [f for f in frames if f["module"].startswith("_pytest")]
402402
assert pytest_frames
403403
assert all(f["in_app"] is False for f in pytest_frames)
404-
assert any(f["in_app"] for f in frames)
405404

406405

407406
def test_attach_stacktrace_disabled(sentry_init, capture_events):

0 commit comments

Comments
 (0)