Skip to content

Commit dd8bc39

Browse files
committed
test(tracing): Test add_query_source with modules outside of project root
When packages added in `in_app_include` are installed to a location outside of the project root directory, span from those packages are not extended with OTel compatible source code information. Cases include running Python from virtualenv created outside of the project root directory or Python packages installed into the system using package managers. This results in an inconsistency: spans from the same project are be different, depending on the deployment method. The change extends `test_query_source_with_in_app_include` to test the simulation of Django installed outside of the project root. The steps to manually reproduce the issue are as follows (case: a virtual environment created outside of the project root): ```bash docker run --replace --rm --detach \ --name sentry-postgres \ --env POSTGRES_USER=sentry \ --env POSTGRES_PASSWORD=sentry \ --publish 5432:5432 \ postgres distrobox create \ --image ubuntu:24.04 \ --name sentry-test-in_app_include-venv distrobox enter sentry-test-in_app_include-venv python3 -m venv /tmp/.venv-test-in_app_include source /tmp/.venv-test-in_app_include/bin/activate pytest tests/integrations/django/test_db_query_data.py \ -k test_query_source_with_in_app_include # FAIL ``` The steps to manually reproduce the issue are as follows (case: Django is installed through system packages): ```bash docker run --replace --rm --detach \ --name sentry-postgres \ --env POSTGRES_USER=sentry \ --env POSTGRES_PASSWORD=sentry \ --publish 5432:5432 \ postgres distrobox create \ --image ubuntu:24.04 \ --name sentry-test-in_app_include-os distrobox enter sentry-test-in_app_include-os sudo apt install \ python3-django python3-pytest python3-pytest-cov \ python3-pytest-django python3-jsonschema python3-urllib3 \ python3-certifi python3-werkzeug python3-psycopg2 pytest tests/integrations/django/test_db_query_data.py \ -k test_query_source_with_in_app_include # FAIL ```
1 parent 93a3242 commit dd8bc39

File tree

2 files changed

+49
-12
lines changed

2 files changed

+49
-12
lines changed

sentry_sdk/tracing_utils.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,14 @@ def maybe_create_breadcrumbs_from_span(scope, span):
170170
)
171171

172172

173+
def _get_frame_module_abs_path(frame):
174+
# type: (FrameType) -> str
175+
try:
176+
return frame.f_code.co_filename
177+
except Exception:
178+
return ""
179+
180+
173181
def add_query_source(span):
174182
# type: (sentry_sdk.tracing.Span) -> None
175183
"""
@@ -200,10 +208,7 @@ def add_query_source(span):
200208
# Find the correct frame
201209
frame = sys._getframe() # type: Union[FrameType, None]
202210
while frame is not None:
203-
try:
204-
abs_path = frame.f_code.co_filename
205-
except Exception:
206-
abs_path = ""
211+
abs_path = _get_frame_module_abs_path(frame)
207212

208213
try:
209214
namespace = frame.f_globals.get("__name__") # type: Optional[str]
@@ -250,10 +255,7 @@ def add_query_source(span):
250255
if namespace is not None:
251256
span.set_data(SPANDATA.CODE_NAMESPACE, namespace)
252257

253-
try:
254-
filepath = frame.f_code.co_filename
255-
except Exception:
256-
filepath = None
258+
filepath = _get_frame_module_abs_path(frame)
257259
if filepath is not None:
258260
if namespace is not None:
259261
in_app_path = filename_for_module(namespace, filepath)

tests/integrations/django/test_db_query_data.py

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import contextlib
12
import os
23

34
import pytest
45
from datetime import datetime
6+
from pathlib import Path
57
from unittest import mock
68

79
from django import VERSION as DJANGO_VERSION
@@ -15,14 +17,19 @@
1517
from werkzeug.test import Client
1618

1719
from sentry_sdk import start_transaction
20+
from sentry_sdk._types import TYPE_CHECKING
1821
from sentry_sdk.consts import SPANDATA
1922
from sentry_sdk.integrations.django import DjangoIntegration
20-
from sentry_sdk.tracing_utils import record_sql_queries
23+
from sentry_sdk.tracing_utils import _get_frame_module_abs_path, record_sql_queries
24+
from sentry_sdk.utils import _module_in_list
2125

2226
from tests.conftest import unpack_werkzeug_response
2327
from tests.integrations.django.utils import pytest_mark_django_db_decorator
2428
from tests.integrations.django.myapp.wsgi import application
2529

30+
if TYPE_CHECKING:
31+
from types import FrameType
32+
2633

2734
@pytest.fixture
2835
def client():
@@ -283,7 +290,10 @@ def test_query_source_with_in_app_exclude(sentry_init, client, capture_events):
283290

284291
@pytest.mark.forked
285292
@pytest_mark_django_db_decorator(transaction=True)
286-
def test_query_source_with_in_app_include(sentry_init, client, capture_events):
293+
@pytest.mark.parametrize("django_outside_of_project_root", [False, True])
294+
def test_query_source_with_in_app_include(
295+
sentry_init, client, capture_events, django_outside_of_project_root
296+
):
287297
sentry_init(
288298
integrations=[DjangoIntegration()],
289299
send_default_pii=True,
@@ -301,8 +311,33 @@ def test_query_source_with_in_app_include(sentry_init, client, capture_events):
301311

302312
events = capture_events()
303313

304-
_, status, _ = unpack_werkzeug_response(client.get(reverse("postgres_select_orm")))
305-
assert status == "200 OK"
314+
# Simulate Django installation outside of the project root
315+
original_get_frame_module_abs_path = _get_frame_module_abs_path
316+
317+
def patched_get_frame_module_abs_path_function(frame):
318+
# type: (FrameType) -> str
319+
result = original_get_frame_module_abs_path(frame)
320+
namespace = frame.f_globals.get("__name__")
321+
if _module_in_list(namespace, ["django"]):
322+
result = str(
323+
Path("/outside-of-project-root") / frame.f_code.co_filename[1:]
324+
)
325+
return result
326+
327+
patched_get_frame_module_abs_path = (
328+
mock.patch(
329+
"sentry_sdk.tracing_utils._get_frame_module_abs_path",
330+
patched_get_frame_module_abs_path_function,
331+
)
332+
if django_outside_of_project_root
333+
else contextlib.suppress()
334+
)
335+
336+
with patched_get_frame_module_abs_path:
337+
_, status, _ = unpack_werkzeug_response(
338+
client.get(reverse("postgres_select_orm"))
339+
)
340+
assert status == "200 OK"
306341

307342
(event,) = events
308343
for span in event["spans"]:

0 commit comments

Comments
 (0)