From 073d83522a9d2c5f56d6faa0536061a652d217d0 Mon Sep 17 00:00:00 2001 From: Tim de Beer Date: Fri, 14 Nov 2025 11:08:46 +0100 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=90=9B=20[commonground-api-common#134?= =?UTF-8?q?]=20Ensure=20exception=20handler=20sends=20exception=20to=20Sen?= =?UTF-8?q?try?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/objects/utils/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/objects/utils/views.py b/src/objects/utils/views.py index 7aacca5c..6febcc98 100644 --- a/src/objects/utils/views.py +++ b/src/objects/utils/views.py @@ -1,6 +1,7 @@ from django.db.utils import DatabaseError from django.utils.translation import gettext_lazy as _ +import sentry_sdk import structlog from open_api_framework.conf.utils import config from rest_framework import status @@ -37,6 +38,8 @@ def exception_handler(exc, context): ) event = "api.database_exception" + sentry_sdk.capture_exception(exc) + response = Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR, data=data) logger.exception(event, exc_info=exc) From 5fddde7c17153763298b6cfa4d2911b2345075e1 Mon Sep 17 00:00:00 2001 From: Tim de Beer Date: Fri, 14 Nov 2025 11:10:47 +0100 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9C=85=20[maykinmedia/commonground-api-c?= =?UTF-8?q?ommon#134]=20Add=20test=20for=20exception=20handler=20to=20forw?= =?UTF-8?q?ard=20exc=20to=20Sentry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../utils/tests/test_exception_handler.py | 53 +++++++++++++++++++ src/objects/utils/views.py | 1 + 2 files changed, 54 insertions(+) create mode 100644 src/objects/utils/tests/test_exception_handler.py diff --git a/src/objects/utils/tests/test_exception_handler.py b/src/objects/utils/tests/test_exception_handler.py new file mode 100644 index 00000000..c933cccb --- /dev/null +++ b/src/objects/utils/tests/test_exception_handler.py @@ -0,0 +1,53 @@ +import logging # noqa: TID251 +from unittest.mock import patch + +import sentry_sdk +from rest_framework.test import APITestCase +from sentry_sdk.integrations.logging import LoggingIntegration +from sentry_sdk.transport import Transport + +from ..views import exception_handler + + +class InMemoryTransport(Transport): + """ + Mock transport class to test if Sentry works + """ + + def __init__(self, options): + self.envelopes = [] + + def capture_envelope(self, envelope): + self.envelopes.append(envelope) + + +class ExceptionHandlerTests(APITestCase): + @patch.dict("os.environ", {"DEBUG": "no"}) + def test_error_is_forwarded_to_sentry(self): + transport = InMemoryTransport({}) + sentry_sdk.init( + dsn="https://12345@sentry.local/1234", + transport=transport, + integrations=[ + LoggingIntegration( + level=logging.INFO, + # Avoid sending logger.exception calls to Sentry + event_level=None, + ), + ], + ) + assert len(transport.envelopes) == 0 + + exc = Exception("Something went wrong") + + result = exception_handler(exc, context={}) + + self.assertIsNotNone(result) + + # Error should be forwarded to sentry + assert len(transport.envelopes) == 1 + + event = transport.envelopes[0] + assert event.items[0].payload.json["level"] == "error" + exception = event.items[0].payload.json["exception"]["values"][-1] + assert exception["value"] == "Something went wrong" diff --git a/src/objects/utils/views.py b/src/objects/utils/views.py index 6febcc98..b65d3b10 100644 --- a/src/objects/utils/views.py +++ b/src/objects/utils/views.py @@ -38,6 +38,7 @@ def exception_handler(exc, context): ) event = "api.database_exception" + # make sure the exception still ends up in Sentry sentry_sdk.capture_exception(exc) response = Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR, data=data)