diff --git a/sentry_sdk/integrations/django/__init__.py b/sentry_sdk/integrations/django/__init__.py index e68f0cacef..ba2cc6a0ad 100644 --- a/sentry_sdk/integrations/django/__init__.py +++ b/sentry_sdk/integrations/django/__init__.py @@ -652,8 +652,8 @@ def execute(self, sql, params=None): _set_db_data(span, self) result = real_execute(self, sql, params) - with capture_internal_exceptions(): - add_query_source(span) + with capture_internal_exceptions(): + add_query_source(span) return result @@ -672,8 +672,8 @@ def executemany(self, sql, param_list): result = real_executemany(self, sql, param_list) - with capture_internal_exceptions(): - add_query_source(span) + with capture_internal_exceptions(): + add_query_source(span) return result diff --git a/sentry_sdk/integrations/django/templates.py b/sentry_sdk/integrations/django/templates.py index 10e8a924b7..f5309c9cf3 100644 --- a/sentry_sdk/integrations/django/templates.py +++ b/sentry_sdk/integrations/django/templates.py @@ -73,7 +73,9 @@ def rendered_content(self): name=_get_template_name_description(self.template_name), origin=DjangoIntegration.origin, ) as span: - span.set_data("context", self.context_data) + if isinstance(self.context_data, dict): + for k, v in self.context_data.items(): + span.set_data(f"context.{k}", v) return real_rendered_content.fget(self) SimpleTemplateResponse.rendered_content = rendered_content @@ -101,7 +103,8 @@ def render(request, template_name, context=None, *args, **kwargs): name=_get_template_name_description(template_name), origin=DjangoIntegration.origin, ) as span: - span.set_data("context", context) + for k, v in context.items(): + span.set_data(f"context.{k}", v) return real_render(request, template_name, context, *args, **kwargs) django.shortcuts.render = render diff --git a/tests/integrations/django/test_basic.py b/tests/integrations/django/test_basic.py index b05a7c3521..2043758949 100644 --- a/tests/integrations/django/test_basic.py +++ b/tests/integrations/django/test_basic.py @@ -8,7 +8,6 @@ from werkzeug.test import Client from django import VERSION as DJANGO_VERSION -from django.contrib.auth.models import User from django.core.management import execute_from_command_line from django.db.utils import OperationalError, ProgrammingError, DataError from django.http.request import RawPostDataException @@ -288,6 +287,9 @@ def test_user_captured(sentry_init, client, capture_events): def test_queryset_repr(sentry_init, capture_events): sentry_init(integrations=[DjangoIntegration()]) events = capture_events() + + from django.contrib.auth.models import User + User.objects.create_user("john", "lennon@thebeatles.com", "johnpassword") try: @@ -374,7 +376,7 @@ def test_sql_queries(sentry_init, capture_events, with_integration): crumb = event["breadcrumbs"]["values"][-1] assert crumb["message"] == "SELECT count(*) FROM people_person WHERE foo = %s" - assert crumb["data"]["db.params"] == [123] + assert crumb["data"]["db.params"] == "[123]" @pytest.mark.forked @@ -409,7 +411,7 @@ def test_sql_dict_query_params(sentry_init, capture_events): assert crumb["message"] == ( "SELECT count(*) FROM people_person WHERE foo = %(my_foo)s" ) - assert crumb["data"]["db.params"] == {"my_foo": 10} + assert crumb["data"]["db.params"] == str({"my_foo": 10}) @pytest.mark.forked @@ -471,7 +473,7 @@ def test_sql_psycopg2_string_composition(sentry_init, capture_events, query): (event,) = events crumb = event["breadcrumbs"]["values"][-1] assert crumb["message"] == ('SELECT %(my_param)s FROM "foobar"') - assert crumb["data"]["db.params"] == {"my_param": 10} + assert crumb["data"]["db.params"] == str({"my_param": 10}) @pytest.mark.forked @@ -524,7 +526,7 @@ def test_sql_psycopg2_placeholders(sentry_init, capture_events): { "category": "query", "data": { - "db.params": {"first_var": "fizz", "second_var": "not a date"}, + "db.params": str({"first_var": "fizz", "second_var": "not a date"}), "db.paramstyle": "format", }, "message": 'insert into my_test_table ("foo", "bar") values (%(first_var)s, ' @@ -928,6 +930,11 @@ def test_render_spans(sentry_init, client, capture_events, render_span_tree): transaction = events[0] assert expected_line in render_span_tree(transaction) + render_span = next( + span for span in transaction["spans"] if span["op"] == "template.render" + ) + assert "context.user_age" in render_span["data"] + if DJANGO_VERSION >= (1, 10): EXPECTED_MIDDLEWARE_SPANS = """\ diff --git a/tests/integrations/django/test_cache_module.py b/tests/integrations/django/test_cache_module.py index 263f9f36f8..03e4925ab0 100644 --- a/tests/integrations/django/test_cache_module.py +++ b/tests/integrations/django/test_cache_module.py @@ -511,7 +511,9 @@ def test_cache_spans_item_size(sentry_init, client, capture_events, use_django_c @pytest.mark.forked @pytest_mark_django_db_decorator() -def test_cache_spans_get_many(sentry_init, capture_events, use_django_caching): +def test_cache_spans_get_many( + sentry_init, capture_events, use_django_caching, render_span_tree +): sentry_init( integrations=[ DjangoIntegration( @@ -528,7 +530,7 @@ def test_cache_spans_get_many(sentry_init, capture_events, use_django_caching): from django.core.cache import cache - with sentry_sdk.start_transaction(): + with sentry_sdk.start_transaction(name="caches"): cache.get_many([f"S{id}", f"S{id+1}"]) cache.set(f"S{id}", "Sensitive1") cache.get_many([f"S{id}", f"S{id+1}"]) @@ -536,31 +538,26 @@ def test_cache_spans_get_many(sentry_init, capture_events, use_django_caching): (transaction,) = events assert len(transaction["spans"]) == 7 - assert transaction["spans"][0]["op"] == "cache.get" - assert transaction["spans"][0]["description"] == f"S{id}, S{id+1}" - - assert transaction["spans"][1]["op"] == "cache.get" - assert transaction["spans"][1]["description"] == f"S{id}" - - assert transaction["spans"][2]["op"] == "cache.get" - assert transaction["spans"][2]["description"] == f"S{id+1}" - - assert transaction["spans"][3]["op"] == "cache.put" - assert transaction["spans"][3]["description"] == f"S{id}" - - assert transaction["spans"][4]["op"] == "cache.get" - assert transaction["spans"][4]["description"] == f"S{id}, S{id+1}" - - assert transaction["spans"][5]["op"] == "cache.get" - assert transaction["spans"][5]["description"] == f"S{id}" - - assert transaction["spans"][6]["op"] == "cache.get" - assert transaction["spans"][6]["description"] == f"S{id+1}" + assert ( + render_span_tree(transaction) + == f"""\ +- op="caches": description=null + - op="cache.get": description="S{id}, S{id+1}" + - op="cache.get": description="S{id}" + - op="cache.get": description="S{id+1}" + - op="cache.put": description="S{id}" + - op="cache.get": description="S{id}, S{id+1}" + - op="cache.get": description="S{id}" + - op="cache.get": description="S{id+1}"\ +""" # noqa: E221 + ) @pytest.mark.forked @pytest_mark_django_db_decorator() -def test_cache_spans_set_many(sentry_init, capture_events, use_django_caching): +def test_cache_spans_set_many( + sentry_init, capture_events, use_django_caching, render_span_tree +): sentry_init( integrations=[ DjangoIntegration( @@ -577,24 +574,23 @@ def test_cache_spans_set_many(sentry_init, capture_events, use_django_caching): from django.core.cache import cache - with sentry_sdk.start_transaction(): + with sentry_sdk.start_transaction(name="caches"): cache.set_many({f"S{id}": "Sensitive1", f"S{id+1}": "Sensitive2"}) cache.get(f"S{id}") (transaction,) = events assert len(transaction["spans"]) == 4 - assert transaction["spans"][0]["op"] == "cache.put" - assert transaction["spans"][0]["description"] == f"S{id}, S{id+1}" - - assert transaction["spans"][1]["op"] == "cache.put" - assert transaction["spans"][1]["description"] == f"S{id}" - - assert transaction["spans"][2]["op"] == "cache.put" - assert transaction["spans"][2]["description"] == f"S{id+1}" - - assert transaction["spans"][3]["op"] == "cache.get" - assert transaction["spans"][3]["description"] == f"S{id}" + assert ( + render_span_tree(transaction) + == f"""\ +- op="caches": description=null + - op="cache.put": description="S{id}, S{id+1}" + - op="cache.put": description="S{id}" + - op="cache.put": description="S{id+1}" + - op="cache.get": description="S{id}"\ +""" # noqa: E221 + ) @pytest.mark.forked diff --git a/tests/integrations/django/test_db_query_data.py b/tests/integrations/django/test_db_query_data.py index 6e49f61085..ccbe6ee28a 100644 --- a/tests/integrations/django/test_db_query_data.py +++ b/tests/integrations/django/test_db_query_data.py @@ -1,6 +1,7 @@ import os import pytest +from contextlib import contextmanager from datetime import datetime from unittest import mock @@ -15,7 +16,7 @@ from freezegun import freeze_time from werkzeug.test import Client -from sentry_sdk import start_transaction +from sentry_sdk import start_transaction, start_span from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations.django import DjangoIntegration from sentry_sdk.tracing_utils import record_sql_queries @@ -347,29 +348,24 @@ def test_no_query_source_if_duration_too_short(sentry_init, client, capture_even events = capture_events() - class fake_record_sql_queries: # noqa: N801 - def __init__(self, *args, **kwargs): - with freeze_time(datetime(2024, 1, 1, microsecond=0)): - with record_sql_queries(*args, **kwargs) as span: - self.span = span - freezer = freeze_time(datetime(2024, 1, 1, microsecond=99999)) - freezer.start() - - freezer.stop() - - def __enter__(self): - return self.span - - def __exit__(self, type, value, traceback): - pass - - with mock.patch( - "sentry_sdk.integrations.django.record_sql_queries", - fake_record_sql_queries, - ): - _, status, _ = unpack_werkzeug_response( - client.get(reverse("postgres_select_orm")) - ) + def fake_start_span(*args, **kwargs): # noqa: N801 + with freeze_time(datetime(2024, 1, 1, microsecond=0)): + return start_span(*args, **kwargs) + + @contextmanager + def fake_record_sql_queries(*args, **kwargs): # noqa: N801 + with freeze_time(datetime(2024, 1, 1, microsecond=99999)): + with record_sql_queries(*args, **kwargs) as span: + yield span + + with mock.patch("sentry_sdk.start_span", fake_start_span): + with mock.patch( + "sentry_sdk.integrations.django.record_sql_queries", + fake_record_sql_queries, + ): + _, status, _ = unpack_werkzeug_response( + client.get(reverse("postgres_select_orm")) + ) assert status == "200 OK" @@ -407,29 +403,24 @@ def test_query_source_if_duration_over_threshold(sentry_init, client, capture_ev events = capture_events() - class fake_record_sql_queries: # noqa: N801 - def __init__(self, *args, **kwargs): - with freeze_time(datetime(2024, 1, 1, microsecond=0)): - with record_sql_queries(*args, **kwargs) as span: - self.span = span - freezer = freeze_time(datetime(2024, 1, 1, microsecond=99999)) - freezer.start() - - freezer.stop() - - def __enter__(self): - return self.span - - def __exit__(self, type, value, traceback): - pass - - with mock.patch( - "sentry_sdk.integrations.django.record_sql_queries", - fake_record_sql_queries, - ): - _, status, _ = unpack_werkzeug_response( - client.get(reverse("postgres_select_orm")) - ) + def fake_start_span(*args, **kwargs): # noqa: N801 + with freeze_time(datetime(2024, 1, 1, microsecond=0)): + return start_span(*args, **kwargs) + + @contextmanager + def fake_record_sql_queries(*args, **kwargs): # noqa: N801 + with freeze_time(datetime(2024, 1, 1, microsecond=100001)): + with record_sql_queries(*args, **kwargs) as span: + yield span + + with mock.patch("sentry_sdk.start_span", fake_start_span): + with mock.patch( + "sentry_sdk.integrations.django.record_sql_queries", + fake_record_sql_queries, + ): + _, status, _ = unpack_werkzeug_response( + client.get(reverse("postgres_select_orm")) + ) assert status == "200 OK" diff --git a/tests/integrations/django/test_transactions.py b/tests/integrations/django/test_transactions.py index 14f8170fc3..0eaf99dc23 100644 --- a/tests/integrations/django/test_transactions.py +++ b/tests/integrations/django/test_transactions.py @@ -21,6 +21,7 @@ included_url_conf = ((re_path(r"^foo/bar/(?P[\w]+)", lambda x: ""),), "") from sentry_sdk.integrations.django.transactions import RavenResolver +from tests.integrations.django.myapp.wsgi import application # noqa: F401 example_url_conf = (