diff --git a/sentry_sdk/integrations/django/__init__.py b/sentry_sdk/integrations/django/__init__.py index ff67b3e39b..2041598fa0 100644 --- a/sentry_sdk/integrations/django/__init__.py +++ b/sentry_sdk/integrations/django/__init__.py @@ -7,7 +7,7 @@ import sentry_sdk from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.scope import add_global_event_processor, should_send_default_pii -from sentry_sdk.serializer import add_global_repr_processor +from sentry_sdk.serializer import add_global_repr_processor, add_repr_sequence_type from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource from sentry_sdk.tracing_utils import add_query_source, record_sql_queries from sentry_sdk.utils import ( @@ -269,6 +269,7 @@ def _django_queryset_repr(value, hint): patch_views() patch_templates() patch_signals() + add_template_context_repr_sequence() if patch_caching is not None: patch_caching() @@ -745,3 +746,13 @@ def _set_db_data(span, cursor_or_db): server_socket_address = connection_params.get("unix_socket") if server_socket_address is not None: span.set_data(SPANDATA.SERVER_SOCKET_ADDRESS, server_socket_address) + + +def add_template_context_repr_sequence(): + # type: () -> None + try: + from django.template.context import BaseContext + + add_repr_sequence_type(BaseContext) + except Exception: + pass diff --git a/sentry_sdk/serializer.py b/sentry_sdk/serializer.py index bc8e38c631..04df9857bd 100644 --- a/sentry_sdk/serializer.py +++ b/sentry_sdk/serializer.py @@ -63,6 +63,14 @@ def add_global_repr_processor(processor): global_repr_processors.append(processor) +sequence_types = [Sequence, Set] # type: List[type] + + +def add_repr_sequence_type(ty): + # type: (type) -> None + sequence_types.append(ty) + + class Memo: __slots__ = ("_ids", "_objs") @@ -332,7 +340,7 @@ def _serialize_node_impl( return rv_dict elif not isinstance(obj, serializable_str_types) and isinstance( - obj, (Set, Sequence) + obj, tuple(sequence_types) ): rv_list = [] diff --git a/tests/integrations/django/test_basic.py b/tests/integrations/django/test_basic.py index 0e3f700105..e96cd09e4f 100644 --- a/tests/integrations/django/test_basic.py +++ b/tests/integrations/django/test_basic.py @@ -10,11 +10,13 @@ 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 from django.utils.functional import SimpleLazyObject +from django.template.context import make_context try: from django.urls import reverse @@ -310,6 +312,27 @@ def test_queryset_repr(sentry_init, capture_events): ) +@pytest.mark.forked +@pytest_mark_django_db_decorator() +def test_context_nested_queryset_repr(sentry_init, capture_events): + sentry_init(integrations=[DjangoIntegration()]) + events = capture_events() + User.objects.create_user("john", "lennon@thebeatles.com", "johnpassword") + + try: + context = make_context({"entries": User.objects.all()}) # noqa + 1 / 0 + except Exception: + capture_exception() + + (event,) = events + + (exception,) = event["exception"]["values"] + assert exception["type"] == "ZeroDivisionError" + (frame,) = exception["stacktrace"]["frames"] + assert "