Skip to content

Commit 7df152b

Browse files
authored
Change code.filepath frame picking logic (#2568)
- search for the frame directly from the execute wrappers - honor `in_app_include` and `in_app_exclude` - fix Python 2 compatibility (`co_filename` is not always absolute)
1 parent ddf37a3 commit 7df152b

File tree

6 files changed

+163
-31
lines changed

6 files changed

+163
-31
lines changed

sentry_sdk/integrations/asyncpg.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from sentry_sdk.consts import OP, SPANDATA
99
from sentry_sdk.integrations import Integration, DidNotEnable
1010
from sentry_sdk.tracing import Span
11-
from sentry_sdk.tracing_utils import record_sql_queries
11+
from sentry_sdk.tracing_utils import add_query_source, record_sql_queries
1212
from sentry_sdk.utils import parse_version, capture_internal_exceptions
1313

1414
try:
@@ -66,8 +66,14 @@ async def _inner(*args: Any, **kwargs: Any) -> T:
6666
return await f(*args, **kwargs)
6767

6868
query = args[1]
69-
with record_sql_queries(hub, None, query, None, None, executemany=False):
69+
with record_sql_queries(
70+
hub, None, query, None, None, executemany=False
71+
) as span:
7072
res = await f(*args, **kwargs)
73+
74+
with capture_internal_exceptions():
75+
add_query_source(hub, span)
76+
7177
return res
7278

7379
return _inner
@@ -118,6 +124,7 @@ async def _inner(*args: Any, **kwargs: Any) -> T:
118124
with _record(hub, None, query, params_list, executemany=executemany) as span:
119125
_set_db_data(span, args[0])
120126
res = await f(*args, **kwargs)
127+
121128
return res
122129

123130
return _inner

sentry_sdk/integrations/django/__init__.py

+14-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from sentry_sdk.scope import add_global_event_processor
1616
from sentry_sdk.serializer import add_global_repr_processor
1717
from sentry_sdk.tracing import SOURCE_FOR_STYLE, TRANSACTION_SOURCE_URL
18-
from sentry_sdk.tracing_utils import record_sql_queries
18+
from sentry_sdk.tracing_utils import add_query_source, record_sql_queries
1919
from sentry_sdk.utils import (
2020
AnnotatedValue,
2121
HAS_REAL_CONTEXTVARS,
@@ -638,7 +638,12 @@ def execute(self, sql, params=None):
638638
self.mogrify,
639639
options,
640640
)
641-
return real_execute(self, sql, params)
641+
result = real_execute(self, sql, params)
642+
643+
with capture_internal_exceptions():
644+
add_query_source(hub, span)
645+
646+
return result
642647

643648
def executemany(self, sql, param_list):
644649
# type: (CursorWrapper, Any, List[Any]) -> Any
@@ -650,7 +655,13 @@ def executemany(self, sql, param_list):
650655
hub, self.cursor, sql, param_list, paramstyle="format", executemany=True
651656
) as span:
652657
_set_db_data(span, self)
653-
return real_executemany(self, sql, param_list)
658+
659+
result = real_executemany(self, sql, param_list)
660+
661+
with capture_internal_exceptions():
662+
add_query_source(hub, span)
663+
664+
return result
654665

655666
def connect(self):
656667
# type: (BaseDatabaseWrapper) -> None

sentry_sdk/integrations/sqlalchemy.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
66
from sentry_sdk.db.explain_plan.sqlalchemy import attach_explain_plan_to_span
77
from sentry_sdk.hub import Hub
88
from sentry_sdk.integrations import Integration, DidNotEnable
9-
from sentry_sdk.tracing_utils import record_sql_queries
10-
11-
from sentry_sdk.utils import parse_version
9+
from sentry_sdk.tracing_utils import add_query_source, record_sql_queries
10+
from sentry_sdk.utils import capture_internal_exceptions, parse_version
1211

1312
try:
1413
from sqlalchemy.engine import Engine # type: ignore
@@ -84,6 +83,10 @@ def _before_cursor_execute(
8483

8584
def _after_cursor_execute(conn, cursor, statement, parameters, context, *args):
8685
# type: (Any, Any, Any, Any, Any, *Any) -> None
86+
hub = Hub.current
87+
if hub.get_integration(SqlalchemyIntegration) is None:
88+
return
89+
8790
ctx_mgr = getattr(
8891
context, "_sentry_sql_span_manager", None
8992
) # type: Optional[ContextManager[Any]]
@@ -92,6 +95,11 @@ def _after_cursor_execute(conn, cursor, statement, parameters, context, *args):
9295
context._sentry_sql_span_manager = None
9396
ctx_mgr.__exit__(None, None, None)
9497

98+
span = context._sentry_sql_span
99+
if span is not None:
100+
with capture_internal_exceptions():
101+
add_query_source(hub, span)
102+
95103

96104
def _handle_error(context, *args):
97105
# type: (Any, *Any) -> None

sentry_sdk/tracing.py

-2
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,6 @@ def finish(self, hub=None, end_timestamp=None):
488488
self.timestamp = datetime_utcnow()
489489

490490
maybe_create_breadcrumbs_from_span(hub, self)
491-
add_additional_span_data(hub, self)
492491

493492
return None
494493

@@ -1021,7 +1020,6 @@ async def my_async_function():
10211020
from sentry_sdk.tracing_utils import (
10221021
Baggage,
10231022
EnvironHeaders,
1024-
add_additional_span_data,
10251023
extract_sentrytrace_data,
10261024
has_tracing_enabled,
10271025
maybe_create_breadcrumbs_from_span,

sentry_sdk/tracing_utils.py

+19-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import contextlib
2+
import os
23
import re
34
import sys
45

@@ -11,6 +12,7 @@
1112
to_string,
1213
is_sentry_url,
1314
_is_external_source,
15+
_module_in_list,
1416
)
1517
from sentry_sdk._compat import PY2, iteritems
1618
from sentry_sdk._types import TYPE_CHECKING
@@ -190,29 +192,44 @@ def add_query_source(hub, span):
190192
return
191193

192194
project_root = client.options["project_root"]
195+
in_app_include = client.options.get("in_app_include")
196+
in_app_exclude = client.options.get("in_app_exclude")
193197

194198
# Find the correct frame
195199
frame = sys._getframe() # type: Union[FrameType, None]
196200
while frame is not None:
197201
try:
198202
abs_path = frame.f_code.co_filename
203+
if abs_path and PY2:
204+
abs_path = os.path.abspath(abs_path)
199205
except Exception:
200206
abs_path = ""
201207

202208
try:
203-
namespace = frame.f_globals.get("__name__")
209+
namespace = frame.f_globals.get("__name__") # type: Optional[str]
204210
except Exception:
205211
namespace = None
206212

207213
is_sentry_sdk_frame = namespace is not None and namespace.startswith(
208214
"sentry_sdk."
209215
)
216+
217+
should_be_included = not _is_external_source(abs_path)
218+
if namespace is not None:
219+
if in_app_exclude and _module_in_list(namespace, in_app_exclude):
220+
should_be_included = False
221+
if in_app_include and _module_in_list(namespace, in_app_include):
222+
# in_app_include takes precedence over in_app_exclude, so doing it
223+
# at the end
224+
should_be_included = True
225+
210226
if (
211227
abs_path.startswith(project_root)
212-
and not _is_external_source(abs_path)
228+
and should_be_included
213229
and not is_sentry_sdk_frame
214230
):
215231
break
232+
216233
frame = frame.f_back
217234
else:
218235
frame = None
@@ -250,15 +267,6 @@ def add_query_source(hub, span):
250267
span.set_data(SPANDATA.CODE_FUNCTION, frame.f_code.co_name)
251268

252269

253-
def add_additional_span_data(hub, span):
254-
# type: (sentry_sdk.Hub, sentry_sdk.tracing.Span) -> None
255-
"""
256-
Adds additional data to the span
257-
"""
258-
if span.op == OP.DB:
259-
add_query_source(hub, span)
260-
261-
262270
def extract_sentrytrace_data(header):
263271
# type: (Optional[str]) -> Optional[Dict[str, Union[str, bool, None]]]
264272
"""

tests/integrations/django/test_db_query_data.py

+110-10
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22

33
import pytest
44

5+
from django import VERSION as DJANGO_VERSION
6+
from django.db import connections
7+
58
try:
69
from django.urls import reverse
710
except ImportError:
811
from django.core.urlresolvers import reverse
912

10-
from django.db import connections
11-
1213
from werkzeug.test import Client
1314

14-
from sentry_sdk._compat import PY2
1515
from sentry_sdk.consts import SPANDATA
1616
from sentry_sdk.integrations.django import DjangoIntegration
1717

@@ -102,24 +102,124 @@ def test_query_source(sentry_init, client, capture_events):
102102
assert type(data.get(SPANDATA.CODE_LINENO)) == int
103103
assert data.get(SPANDATA.CODE_LINENO) > 0
104104

105-
if PY2:
105+
assert (
106+
data.get(SPANDATA.CODE_NAMESPACE)
107+
== "tests.integrations.django.myapp.views"
108+
)
109+
assert data.get(SPANDATA.CODE_FILEPATH).endswith(
110+
"tests/integrations/django/myapp/views.py"
111+
)
112+
assert data.get(SPANDATA.CODE_FUNCTION) == "postgres_select_orm"
113+
114+
break
115+
else:
116+
raise AssertionError("No db span found")
117+
118+
119+
@pytest.mark.forked
120+
@pytest_mark_django_db_decorator(transaction=True)
121+
def test_query_source_with_in_app_exclude(sentry_init, client, capture_events):
122+
sentry_init(
123+
integrations=[DjangoIntegration()],
124+
send_default_pii=True,
125+
traces_sample_rate=1.0,
126+
enable_db_query_source=True,
127+
db_query_source_threshold_ms=0,
128+
in_app_exclude=["tests.integrations.django.myapp.views"],
129+
)
130+
131+
if "postgres" not in connections:
132+
pytest.skip("postgres tests disabled")
133+
134+
# trigger Django to open a new connection by marking the existing one as None.
135+
connections["postgres"].connection = None
136+
137+
events = capture_events()
138+
139+
_, status, _ = unpack_werkzeug_response(client.get(reverse("postgres_select_orm")))
140+
assert status == "200 OK"
141+
142+
(event,) = events
143+
for span in event["spans"]:
144+
if span.get("op") == "db" and "auth_user" in span.get("description"):
145+
data = span.get("data", {})
146+
147+
assert SPANDATA.CODE_LINENO in data
148+
assert SPANDATA.CODE_NAMESPACE in data
149+
assert SPANDATA.CODE_FILEPATH in data
150+
assert SPANDATA.CODE_FUNCTION in data
151+
152+
assert type(data.get(SPANDATA.CODE_LINENO)) == int
153+
assert data.get(SPANDATA.CODE_LINENO) > 0
154+
155+
if DJANGO_VERSION >= (1, 11):
106156
assert (
107157
data.get(SPANDATA.CODE_NAMESPACE)
108-
== "tests.integrations.django.test_db_query_data"
158+
== "tests.integrations.django.myapp.settings"
109159
)
110160
assert data.get(SPANDATA.CODE_FILEPATH).endswith(
111-
"tests/integrations/django/test_db_query_data.py"
161+
"tests/integrations/django/myapp/settings.py"
112162
)
113-
assert data.get(SPANDATA.CODE_FUNCTION) == "test_query_source"
163+
assert data.get(SPANDATA.CODE_FUNCTION) == "middleware"
114164
else:
115165
assert (
116166
data.get(SPANDATA.CODE_NAMESPACE)
117-
== "tests.integrations.django.myapp.views"
167+
== "tests.integrations.django.test_db_query_data"
118168
)
119169
assert data.get(SPANDATA.CODE_FILEPATH).endswith(
120-
"tests/integrations/django/myapp/views.py"
170+
"tests/integrations/django/test_db_query_data.py"
121171
)
122-
assert data.get(SPANDATA.CODE_FUNCTION) == "postgres_select_orm"
172+
assert (
173+
data.get(SPANDATA.CODE_FUNCTION)
174+
== "test_query_source_with_in_app_exclude"
175+
)
176+
177+
break
178+
else:
179+
raise AssertionError("No db span found")
180+
181+
182+
@pytest.mark.forked
183+
@pytest_mark_django_db_decorator(transaction=True)
184+
def test_query_source_with_in_app_include(sentry_init, client, capture_events):
185+
sentry_init(
186+
integrations=[DjangoIntegration()],
187+
send_default_pii=True,
188+
traces_sample_rate=1.0,
189+
enable_db_query_source=True,
190+
db_query_source_threshold_ms=0,
191+
in_app_include=["django"],
192+
)
193+
194+
if "postgres" not in connections:
195+
pytest.skip("postgres tests disabled")
196+
197+
# trigger Django to open a new connection by marking the existing one as None.
198+
connections["postgres"].connection = None
199+
200+
events = capture_events()
201+
202+
_, status, _ = unpack_werkzeug_response(client.get(reverse("postgres_select_orm")))
203+
assert status == "200 OK"
204+
205+
(event,) = events
206+
for span in event["spans"]:
207+
if span.get("op") == "db" and "auth_user" in span.get("description"):
208+
data = span.get("data", {})
209+
210+
assert SPANDATA.CODE_LINENO in data
211+
assert SPANDATA.CODE_NAMESPACE in data
212+
assert SPANDATA.CODE_FILEPATH in data
213+
assert SPANDATA.CODE_FUNCTION in data
214+
215+
assert type(data.get(SPANDATA.CODE_LINENO)) == int
216+
assert data.get(SPANDATA.CODE_LINENO) > 0
217+
218+
assert data.get(SPANDATA.CODE_NAMESPACE) == "django.db.models.sql.compiler"
219+
assert data.get(SPANDATA.CODE_FILEPATH).endswith(
220+
"django/db/models/sql/compiler.py"
221+
)
222+
assert data.get(SPANDATA.CODE_FUNCTION) == "execute_sql"
123223
break
124224
else:
125225
raise AssertionError("No db span found")

0 commit comments

Comments
 (0)