Skip to content

Commit 4329383

Browse files
authored
fix(Django): Avoid evaluating complex Django object in span.data/span.attributes (#4804)
### Description When rendering templates of the Django Admin in Django 5.0+ the template context includes also a `QuerySet` with lots of log items. In our `serialize()` function this was not properly serialized leading to evaluating the `QuerySet` (read: running the SQL query). This change updates the `serialize()` to also correctly serialize `QuerySet`s in `span.data` #### Issues * resolves: #4604 * resolves: PY-1781
1 parent 6c2a996 commit 4329383

File tree

4 files changed

+77
-3
lines changed

4 files changed

+77
-3
lines changed

sentry_sdk/serializer.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,16 @@ def _is_databag():
187187

188188
return False
189189

190+
def _is_span_attribute():
191+
# type: () -> Optional[bool]
192+
try:
193+
if path[0] == "spans" and path[2] == "data":
194+
return True
195+
except IndexError:
196+
return None
197+
198+
return False
199+
190200
def _is_request_body():
191201
# type: () -> Optional[bool]
192202
try:
@@ -282,7 +292,8 @@ def _serialize_node_impl(
282292
)
283293
return None
284294

285-
if is_databag and global_repr_processors:
295+
is_span_attribute = _is_span_attribute()
296+
if (is_databag or is_span_attribute) and global_repr_processors:
286297
hints = {"memo": memo, "remaining_depth": remaining_depth}
287298
for processor in global_repr_processors:
288299
result = processor(obj, hints)

tests/integrations/django/myapp/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def path(path, *args, **kwargs):
5858
path("template-test", views.template_test, name="template_test"),
5959
path("template-test2", views.template_test2, name="template_test2"),
6060
path("template-test3", views.template_test3, name="template_test3"),
61+
path("template-test4", views.template_test4, name="template_test4"),
6162
path("postgres-select", views.postgres_select, name="postgres_select"),
6263
path("postgres-select-slow", views.postgres_select_orm, name="postgres_select_orm"),
6364
path(

tests/integrations/django/myapp/views.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,29 @@ def template_test3(request, *args, **kwargs):
208208
return render(request, "trace_meta.html", {})
209209

210210

211+
@csrf_exempt
212+
def template_test4(request, *args, **kwargs):
213+
User.objects.create_user("john", "[email protected]", "johnpassword")
214+
my_queryset = User.objects.all() # noqa
215+
216+
template_context = {
217+
"user_age": 25,
218+
"complex_context": my_queryset,
219+
"complex_list": [1, 2, 3, my_queryset],
220+
"complex_dict": {
221+
"a": 1,
222+
"d": my_queryset,
223+
},
224+
"none_context": None,
225+
}
226+
227+
return TemplateResponse(
228+
request,
229+
"user_name.html",
230+
template_context,
231+
)
232+
233+
211234
@csrf_exempt
212235
def postgres_select(request, *args, **kwargs):
213236
from django.db import connections

tests/integrations/django/test_basic.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import inspect
22
import json
33
import os
4+
import pytest
45
import re
56
import sys
6-
import pytest
7+
78
from functools import partial
89
from unittest.mock import patch
910

@@ -15,8 +16,8 @@
1516
from django.core.management import execute_from_command_line
1617
from django.db.utils import OperationalError, ProgrammingError, DataError
1718
from django.http.request import RawPostDataException
18-
from django.utils.functional import SimpleLazyObject
1919
from django.template.context import make_context
20+
from django.utils.functional import SimpleLazyObject
2021

2122
try:
2223
from django.urls import reverse
@@ -956,6 +957,44 @@ def test_render_spans(sentry_init, client, capture_events, render_span_tree):
956957
assert expected_line in render_span_tree(transaction)
957958

958959

960+
@pytest.mark.skipif(DJANGO_VERSION < (1, 9), reason="Requires Django >= 1.9")
961+
@pytest.mark.forked
962+
@pytest_mark_django_db_decorator()
963+
def test_render_spans_queryset_in_data(sentry_init, client, capture_events):
964+
sentry_init(
965+
integrations=[
966+
DjangoIntegration(
967+
cache_spans=False,
968+
middleware_spans=False,
969+
signals_spans=False,
970+
)
971+
],
972+
traces_sample_rate=1.0,
973+
)
974+
events = capture_events()
975+
976+
client.get(reverse("template_test4"))
977+
978+
(transaction,) = events
979+
template_context = transaction["spans"][-1]["data"]["context"]
980+
981+
assert template_context["user_age"] == 25
982+
assert template_context["complex_context"].startswith(
983+
"<QuerySet from django.db.models.query at "
984+
)
985+
assert template_context["complex_dict"]["a"] == 1
986+
assert template_context["complex_dict"]["d"].startswith(
987+
"<QuerySet from django.db.models.query at "
988+
)
989+
assert template_context["none_context"] is None
990+
assert template_context["complex_list"][0] == 1
991+
assert template_context["complex_list"][1] == 2
992+
assert template_context["complex_list"][2] == 3
993+
assert template_context["complex_list"][3].startswith(
994+
"<QuerySet from django.db.models.query at "
995+
)
996+
997+
959998
if DJANGO_VERSION >= (1, 10):
960999
EXPECTED_MIDDLEWARE_SPANS = """\
9611000
- op="http.server": description=null

0 commit comments

Comments
 (0)