diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md
index 5921ff2992..3c297ea9fd 100644
--- a/MIGRATION_GUIDE.md
+++ b/MIGRATION_GUIDE.md
@@ -37,6 +37,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
- A number of extras (installable via `sentry-sdk[extra-name]`) has been removed. Use the base package (`sentry-sdk`) instead; there is no difference in functionality. The following extras have been removed: `aiohttp`, `anthropic`, `arq`, `asyncpg`, `beam`, `bottle`, `celery`, `celery-redbeat`, `chalice`, `clickhouse-driver`, `django`, `falcon`, `fastapi`, `google-genai`, `httpx`, `huey`, `huggingface_hub`, `langchain`, `langgraph`, `launchdarkly`, `litellm`, `litestar`, `loguru`, `mcp`, `openai`, `openfeature`, `pydantic_ai`, `pymongo`, `pyspark`, `rq`, `sanic`, `sqlalchemy`, `starlette`, `starlite`, `statsig`, `tornado`, `unleash`.
- The `failed_request_status_codes` integration option now only supports a set of integers as input. Lists of integers or containers of integers are no longer supported.
- The deprecated `propagate_traces` option has been removed. Use `trace_propagation_targets` instead, which gives you more power over trace propagation. Note that only the top-level `init` option was removed; the `propagate_traces` option of the Celery integration remains available.
+- Removed Spotlight integration for Django. See [Spotlight 2.0](https://github.com/getsentry/spotlight/issues/891) for more context.
## Deprecated
diff --git a/sentry_sdk/spotlight.py b/sentry_sdk/spotlight.py
index 2dcc86bc47..41877346bc 100644
--- a/sentry_sdk/spotlight.py
+++ b/sentry_sdk/spotlight.py
@@ -3,20 +3,15 @@
import os
import sys
import time
-import urllib.error
-import urllib.parse
-import urllib.request
-from itertools import chain, product
from typing import TYPE_CHECKING
import urllib3
if TYPE_CHECKING:
- from typing import Any, Callable, Dict, Optional, Self
+ from typing import Any, Dict, Optional
from sentry_sdk.envelope import Envelope
from sentry_sdk.utils import (
- capture_internal_exceptions,
env_to_bool,
)
from sentry_sdk.utils import (
@@ -27,7 +22,6 @@
DEFAULT_SPOTLIGHT_URL = "http://localhost:8969/stream"
-DJANGO_SPOTLIGHT_MIDDLEWARE_PATH = "sentry_sdk.spotlight.SpotlightMiddleware"
class SpotlightClient:
@@ -90,145 +84,6 @@ def capture_envelope(self, envelope: "Envelope") -> None:
)
-try:
- from django.conf import settings
- from django.http import HttpRequest, HttpResponse, HttpResponseServerError
- from django.utils.deprecation import MiddlewareMixin
-
- 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: "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: "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: "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: "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 _resolve_spotlight_url(
spotlight_config: "Any", sentry_logger: "Any"
) -> "Optional[str]":
@@ -307,20 +162,6 @@ def setup_spotlight(options: "Dict[str, Any]") -> "Optional[SpotlightClient]":
# Update options with resolved URL for consistency
options["spotlight"] = url
- 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 886477cf5c..3ab1734f1d 100644
--- a/tests/integrations/django/test_basic.py
+++ b/tests/integrations/django/test_basic.py
@@ -2386,61 +2386,6 @@ def test_transaction_http_method_custom(
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