From 6ef03be202b5b99422f3837ea9798d115fb56017 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 3 Sep 2025 13:39:14 +0100 Subject: [PATCH 1/4] breaking: Remove Spotlight Django integration We are moving away from Spotlight's overlay mode with its new versions so removing this integration from the SDK too. --- MIGRATION_GUIDE.md | 1 + sentry_sdk/spotlight.py | 153 ------------------------ tests/integrations/django/test_basic.py | 55 --------- 3 files changed, 1 insertion(+), 208 deletions(-) diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index ab0ef7e37a..86d8853be3 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -222,6 +222,7 @@ sentry_sdk.init( - PyMongo: The integration no longer sets tags. The data is still accessible via span attributes. - PyMongo: The integration doesn't set `operation_ids` anymore. The individual IDs (`operation_id`, `request_id`, `session_id`) are now accessible as separate span attributes. - Django: Dropped support for Django versions below 2.0. +- Django: Removed Spotlight integration for Django. - trytond: Dropped support for trytond versions below 5.0. - Falcon: Dropped support for Falcon versions below 3.0. - eventlet: Dropped support for eventlet completely. diff --git a/sentry_sdk/spotlight.py b/sentry_sdk/spotlight.py index 976879dc84..5fd950847a 100644 --- a/sentry_sdk/spotlight.py +++ b/sentry_sdk/spotlight.py @@ -63,145 +63,6 @@ def capture_envelope(self, envelope: Envelope) -> None: # to avoid overflowing the variable if Spotlight never becomes reachable -try: - from django.utils.deprecation import MiddlewareMixin - from django.http import HttpResponseServerError, HttpResponse, HttpRequest - from django.conf import settings - - SPOTLIGHT_JS_ENTRY_PATH = "/assets/main.js" - SPOTLIGHT_JS_SNIPPET_PATTERN = ( - "\n" - '\n' - ) - SPOTLIGHT_ERROR_PAGE_SNIPPET = ( - '\n' - '\n' - ) - CHARSET_PREFIX = "charset=" - BODY_TAG_NAME = "body" - BODY_CLOSE_TAG_POSSIBILITIES = tuple( - "".format("".join(chars)) - for chars in product(*zip(BODY_TAG_NAME.upper(), BODY_TAG_NAME.lower())) - ) - - class SpotlightMiddleware(MiddlewareMixin): # type: ignore[misc] - _spotlight_script: Optional[str] = None - _spotlight_url: Optional[str] = None - - def __init__(self, get_response: Callable[..., HttpResponse]) -> None: - super().__init__(get_response) - - import sentry_sdk.api - - self.sentry_sdk = sentry_sdk.api - - spotlight_client = self.sentry_sdk.get_client().spotlight - if spotlight_client is None: - sentry_logger.warning( - "Cannot find Spotlight client from SpotlightMiddleware, disabling the middleware." - ) - return None - # Spotlight URL has a trailing `/stream` part at the end so split it off - self._spotlight_url = urllib.parse.urljoin(spotlight_client.url, "../") - - @property - def spotlight_script(self) -> Optional[str]: - if self._spotlight_url is not None and self._spotlight_script is None: - try: - spotlight_js_url = urllib.parse.urljoin( - self._spotlight_url, SPOTLIGHT_JS_ENTRY_PATH - ) - req = urllib.request.Request( - spotlight_js_url, - method="HEAD", - ) - urllib.request.urlopen(req) - self._spotlight_script = SPOTLIGHT_JS_SNIPPET_PATTERN.format( - spotlight_url=self._spotlight_url, - spotlight_js_url=spotlight_js_url, - ) - except urllib.error.URLError as err: - sentry_logger.debug( - "Cannot get Spotlight JS to inject at %s. SpotlightMiddleware will not be very useful.", - spotlight_js_url, - exc_info=err, - ) - - return self._spotlight_script - - def process_response( - self, _request: HttpRequest, response: HttpResponse - ) -> Optional[HttpResponse]: - content_type_header = tuple( - p.strip() - for p in response.headers.get("Content-Type", "").lower().split(";") - ) - content_type = content_type_header[0] - if len(content_type_header) > 1 and content_type_header[1].startswith( - CHARSET_PREFIX - ): - encoding = content_type_header[1][len(CHARSET_PREFIX) :] - else: - encoding = "utf-8" - - if ( - self.spotlight_script is not None - and not response.streaming - and content_type == "text/html" - ): - content_length = len(response.content) - injection = self.spotlight_script.encode(encoding) - injection_site = next( - ( - idx - for idx in ( - response.content.rfind(body_variant.encode(encoding)) - for body_variant in BODY_CLOSE_TAG_POSSIBILITIES - ) - if idx > -1 - ), - content_length, - ) - - # This approach works even when we don't have a `` tag - response.content = ( - response.content[:injection_site] - + injection - + response.content[injection_site:] - ) - - if response.has_header("Content-Length"): - response.headers["Content-Length"] = content_length + len(injection) - - return response - - def process_exception( - self, _request: HttpRequest, exception: Exception - ) -> Optional[HttpResponseServerError]: - if not settings.DEBUG or not self._spotlight_url: - return None - - try: - spotlight = ( - urllib.request.urlopen(self._spotlight_url).read().decode("utf-8") - ) - except urllib.error.URLError: - return None - else: - event_id = self.sentry_sdk.capture_exception(exception) - return HttpResponseServerError( - spotlight.replace( - "", - SPOTLIGHT_ERROR_PAGE_SNIPPET.format( - spotlight_url=self._spotlight_url, event_id=event_id - ), - ) - ) - -except ImportError: - settings = None - - def setup_spotlight(options: Dict[str, Any]) -> Optional[SpotlightClient]: _handler = logging.StreamHandler(sys.stderr) _handler.setFormatter(logging.Formatter(" [spotlight] %(levelname)s: %(message)s")) @@ -216,20 +77,6 @@ def setup_spotlight(options: Dict[str, Any]) -> Optional[SpotlightClient]: if not isinstance(url, str): return None - with capture_internal_exceptions(): - if ( - settings is not None - and settings.DEBUG - and env_to_bool(os.environ.get("SENTRY_SPOTLIGHT_ON_ERROR", "1")) - and env_to_bool(os.environ.get("SENTRY_SPOTLIGHT_MIDDLEWARE", "1")) - ): - middleware = settings.MIDDLEWARE - if DJANGO_SPOTLIGHT_MIDDLEWARE_PATH not in middleware: - settings.MIDDLEWARE = type(middleware)( - chain(middleware, (DJANGO_SPOTLIGHT_MIDDLEWARE_PATH,)) - ) - logger.info("Enabled Spotlight integration for Django") - client = SpotlightClient(url) logger.info("Enabled Spotlight using sidecar at %s", url) diff --git a/tests/integrations/django/test_basic.py b/tests/integrations/django/test_basic.py index 650e1a0bb6..cf9e48fc0c 100644 --- a/tests/integrations/django/test_basic.py +++ b/tests/integrations/django/test_basic.py @@ -1288,61 +1288,6 @@ def test_transaction_http_method_custom(sentry_init, client, capture_events): assert event2["request"]["method"] == "HEAD" -def test_ensures_spotlight_middleware_when_spotlight_is_enabled(sentry_init, settings): - """ - Test that ensures if Spotlight is enabled, relevant SpotlightMiddleware - is added to middleware list in settings. - """ - settings.DEBUG = True - original_middleware = frozenset(settings.MIDDLEWARE) - - sentry_init(integrations=[DjangoIntegration()], spotlight=True) - - added = frozenset(settings.MIDDLEWARE) ^ original_middleware - - assert "sentry_sdk.spotlight.SpotlightMiddleware" in added - - -def test_ensures_no_spotlight_middleware_when_env_killswitch_is_false( - monkeypatch, sentry_init, settings -): - """ - Test that ensures if Spotlight is enabled, but is set to a falsy value - the relevant SpotlightMiddleware is NOT added to middleware list in settings. - """ - settings.DEBUG = True - monkeypatch.setenv("SENTRY_SPOTLIGHT_ON_ERROR", "no") - - original_middleware = frozenset(settings.MIDDLEWARE) - - sentry_init(integrations=[DjangoIntegration()], spotlight=True) - - added = frozenset(settings.MIDDLEWARE) ^ original_middleware - - assert "sentry_sdk.spotlight.SpotlightMiddleware" not in added - - -def test_ensures_no_spotlight_middleware_when_no_spotlight( - monkeypatch, sentry_init, settings -): - """ - Test that ensures if Spotlight is not enabled - the relevant SpotlightMiddleware is NOT added to middleware list in settings. - """ - settings.DEBUG = True - - # We should NOT have the middleware even if the env var is truthy if Spotlight is off - monkeypatch.setenv("SENTRY_SPOTLIGHT_ON_ERROR", "1") - - original_middleware = frozenset(settings.MIDDLEWARE) - - sentry_init(integrations=[DjangoIntegration()], spotlight=False) - - added = frozenset(settings.MIDDLEWARE) ^ original_middleware - - assert "sentry_sdk.spotlight.SpotlightMiddleware" not in added - - def test_get_frame_name_when_in_lazy_object(): allowed_to_init = False From 90cc10dd35143b8f655f2be132b60c783e23b7a4 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 3 Sep 2025 14:22:24 +0100 Subject: [PATCH 2/4] add link to Spotlight 2.0 ticket in migration guide --- MIGRATION_GUIDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 86d8853be3..c680aada9d 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -222,7 +222,7 @@ sentry_sdk.init( - PyMongo: The integration no longer sets tags. The data is still accessible via span attributes. - PyMongo: The integration doesn't set `operation_ids` anymore. The individual IDs (`operation_id`, `request_id`, `session_id`) are now accessible as separate span attributes. - Django: Dropped support for Django versions below 2.0. -- Django: Removed Spotlight integration for Django. +- Django: Removed Spotlight integration for Django. -- See [Spotlight 2.0](https://github.com/getsentry/spotlight/issues/891) for more context. - trytond: Dropped support for trytond versions below 5.0. - Falcon: Dropped support for Falcon versions below 3.0. - eventlet: Dropped support for eventlet completely. From 48873221102a3977082ae90f08ee40827be8b655 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 3 Sep 2025 14:42:43 +0100 Subject: [PATCH 3/4] remove unused imports --- sentry_sdk/spotlight.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/sentry_sdk/spotlight.py b/sentry_sdk/spotlight.py index 5fd950847a..364789f761 100644 --- a/sentry_sdk/spotlight.py +++ b/sentry_sdk/spotlight.py @@ -1,15 +1,9 @@ from __future__ import annotations import io import logging -import os -import urllib.parse -import urllib.request -import urllib.error import urllib3 import sys -from itertools import chain, product - from typing import TYPE_CHECKING if TYPE_CHECKING: From e61053642558e5bfc3abaa3a03bb0180b392661c Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 3 Sep 2025 14:45:07 +0100 Subject: [PATCH 4/4] moar deletion --- sentry_sdk/spotlight.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sentry_sdk/spotlight.py b/sentry_sdk/spotlight.py index 364789f761..25d4287dc3 100644 --- a/sentry_sdk/spotlight.py +++ b/sentry_sdk/spotlight.py @@ -7,12 +7,10 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Any, Callable, Dict, Optional + from typing import Any, Dict, Optional from sentry_sdk.utils import ( logger as sentry_logger, - env_to_bool, - capture_internal_exceptions, ) from sentry_sdk.envelope import Envelope