Skip to content

Commit 38cc2a8

Browse files
authored
breaking: Remove Spotlight Django integration (#4747)
We are moving away from Spotlight's overlay mode with its new versions so removing this integration from the SDK too. Ref getsentry/spotlight#891
1 parent ebd77f2 commit 38cc2a8

File tree

3 files changed

+2
-217
lines changed

3 files changed

+2
-217
lines changed

MIGRATION_GUIDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ sentry_sdk.init(
222222
- PyMongo: The integration no longer sets tags. The data is still accessible via span attributes.
223223
- 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.
224224
- Django: Dropped support for Django versions below 2.0.
225+
- Django: Removed Spotlight integration for Django. -- See [Spotlight 2.0](https://github.com/getsentry/spotlight/issues/891) for more context.
225226
- trytond: Dropped support for trytond versions below 5.0.
226227
- Falcon: Dropped support for Falcon versions below 3.0.
227228
- eventlet: Dropped support for eventlet completely.

sentry_sdk/spotlight.py

Lines changed: 1 addition & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,16 @@
11
from __future__ import annotations
22
import io
33
import logging
4-
import os
5-
import urllib.parse
6-
import urllib.request
7-
import urllib.error
84
import urllib3
95
import sys
106

11-
from itertools import chain, product
12-
137
from typing import TYPE_CHECKING
148

159
if TYPE_CHECKING:
16-
from typing import Any, Callable, Dict, Optional
10+
from typing import Any, Dict, Optional
1711

1812
from sentry_sdk.utils import (
1913
logger as sentry_logger,
20-
env_to_bool,
21-
capture_internal_exceptions,
2214
)
2315
from sentry_sdk.envelope import Envelope
2416

@@ -63,145 +55,6 @@ def capture_envelope(self, envelope: Envelope) -> None:
6355
# to avoid overflowing the variable if Spotlight never becomes reachable
6456

6557

66-
try:
67-
from django.utils.deprecation import MiddlewareMixin
68-
from django.http import HttpResponseServerError, HttpResponse, HttpRequest
69-
from django.conf import settings
70-
71-
SPOTLIGHT_JS_ENTRY_PATH = "/assets/main.js"
72-
SPOTLIGHT_JS_SNIPPET_PATTERN = (
73-
"<script>window.__spotlight = {{ initOptions: {{ sidecarUrl: '{spotlight_url}', fullPage: false }} }};</script>\n"
74-
'<script type="module" crossorigin src="{spotlight_js_url}"></script>\n'
75-
)
76-
SPOTLIGHT_ERROR_PAGE_SNIPPET = (
77-
'<html><base href="{spotlight_url}">\n'
78-
'<script>window.__spotlight = {{ initOptions: {{ fullPage: true, startFrom: "/errors/{event_id}" }}}};</script>\n'
79-
)
80-
CHARSET_PREFIX = "charset="
81-
BODY_TAG_NAME = "body"
82-
BODY_CLOSE_TAG_POSSIBILITIES = tuple(
83-
"</{}>".format("".join(chars))
84-
for chars in product(*zip(BODY_TAG_NAME.upper(), BODY_TAG_NAME.lower()))
85-
)
86-
87-
class SpotlightMiddleware(MiddlewareMixin): # type: ignore[misc]
88-
_spotlight_script: Optional[str] = None
89-
_spotlight_url: Optional[str] = None
90-
91-
def __init__(self, get_response: Callable[..., HttpResponse]) -> None:
92-
super().__init__(get_response)
93-
94-
import sentry_sdk.api
95-
96-
self.sentry_sdk = sentry_sdk.api
97-
98-
spotlight_client = self.sentry_sdk.get_client().spotlight
99-
if spotlight_client is None:
100-
sentry_logger.warning(
101-
"Cannot find Spotlight client from SpotlightMiddleware, disabling the middleware."
102-
)
103-
return None
104-
# Spotlight URL has a trailing `/stream` part at the end so split it off
105-
self._spotlight_url = urllib.parse.urljoin(spotlight_client.url, "../")
106-
107-
@property
108-
def spotlight_script(self) -> Optional[str]:
109-
if self._spotlight_url is not None and self._spotlight_script is None:
110-
try:
111-
spotlight_js_url = urllib.parse.urljoin(
112-
self._spotlight_url, SPOTLIGHT_JS_ENTRY_PATH
113-
)
114-
req = urllib.request.Request(
115-
spotlight_js_url,
116-
method="HEAD",
117-
)
118-
urllib.request.urlopen(req)
119-
self._spotlight_script = SPOTLIGHT_JS_SNIPPET_PATTERN.format(
120-
spotlight_url=self._spotlight_url,
121-
spotlight_js_url=spotlight_js_url,
122-
)
123-
except urllib.error.URLError as err:
124-
sentry_logger.debug(
125-
"Cannot get Spotlight JS to inject at %s. SpotlightMiddleware will not be very useful.",
126-
spotlight_js_url,
127-
exc_info=err,
128-
)
129-
130-
return self._spotlight_script
131-
132-
def process_response(
133-
self, _request: HttpRequest, response: HttpResponse
134-
) -> Optional[HttpResponse]:
135-
content_type_header = tuple(
136-
p.strip()
137-
for p in response.headers.get("Content-Type", "").lower().split(";")
138-
)
139-
content_type = content_type_header[0]
140-
if len(content_type_header) > 1 and content_type_header[1].startswith(
141-
CHARSET_PREFIX
142-
):
143-
encoding = content_type_header[1][len(CHARSET_PREFIX) :]
144-
else:
145-
encoding = "utf-8"
146-
147-
if (
148-
self.spotlight_script is not None
149-
and not response.streaming
150-
and content_type == "text/html"
151-
):
152-
content_length = len(response.content)
153-
injection = self.spotlight_script.encode(encoding)
154-
injection_site = next(
155-
(
156-
idx
157-
for idx in (
158-
response.content.rfind(body_variant.encode(encoding))
159-
for body_variant in BODY_CLOSE_TAG_POSSIBILITIES
160-
)
161-
if idx > -1
162-
),
163-
content_length,
164-
)
165-
166-
# This approach works even when we don't have a `</body>` tag
167-
response.content = (
168-
response.content[:injection_site]
169-
+ injection
170-
+ response.content[injection_site:]
171-
)
172-
173-
if response.has_header("Content-Length"):
174-
response.headers["Content-Length"] = content_length + len(injection)
175-
176-
return response
177-
178-
def process_exception(
179-
self, _request: HttpRequest, exception: Exception
180-
) -> Optional[HttpResponseServerError]:
181-
if not settings.DEBUG or not self._spotlight_url:
182-
return None
183-
184-
try:
185-
spotlight = (
186-
urllib.request.urlopen(self._spotlight_url).read().decode("utf-8")
187-
)
188-
except urllib.error.URLError:
189-
return None
190-
else:
191-
event_id = self.sentry_sdk.capture_exception(exception)
192-
return HttpResponseServerError(
193-
spotlight.replace(
194-
"<html>",
195-
SPOTLIGHT_ERROR_PAGE_SNIPPET.format(
196-
spotlight_url=self._spotlight_url, event_id=event_id
197-
),
198-
)
199-
)
200-
201-
except ImportError:
202-
settings = None
203-
204-
20558
def setup_spotlight(options: Dict[str, Any]) -> Optional[SpotlightClient]:
20659
_handler = logging.StreamHandler(sys.stderr)
20760
_handler.setFormatter(logging.Formatter(" [spotlight] %(levelname)s: %(message)s"))
@@ -216,20 +69,6 @@ def setup_spotlight(options: Dict[str, Any]) -> Optional[SpotlightClient]:
21669
if not isinstance(url, str):
21770
return None
21871

219-
with capture_internal_exceptions():
220-
if (
221-
settings is not None
222-
and settings.DEBUG
223-
and env_to_bool(os.environ.get("SENTRY_SPOTLIGHT_ON_ERROR", "1"))
224-
and env_to_bool(os.environ.get("SENTRY_SPOTLIGHT_MIDDLEWARE", "1"))
225-
):
226-
middleware = settings.MIDDLEWARE
227-
if DJANGO_SPOTLIGHT_MIDDLEWARE_PATH not in middleware:
228-
settings.MIDDLEWARE = type(middleware)(
229-
chain(middleware, (DJANGO_SPOTLIGHT_MIDDLEWARE_PATH,))
230-
)
231-
logger.info("Enabled Spotlight integration for Django")
232-
23372
client = SpotlightClient(url)
23473
logger.info("Enabled Spotlight using sidecar at %s", url)
23574

tests/integrations/django/test_basic.py

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,61 +1288,6 @@ def test_transaction_http_method_custom(sentry_init, client, capture_events):
12881288
assert event2["request"]["method"] == "HEAD"
12891289

12901290

1291-
def test_ensures_spotlight_middleware_when_spotlight_is_enabled(sentry_init, settings):
1292-
"""
1293-
Test that ensures if Spotlight is enabled, relevant SpotlightMiddleware
1294-
is added to middleware list in settings.
1295-
"""
1296-
settings.DEBUG = True
1297-
original_middleware = frozenset(settings.MIDDLEWARE)
1298-
1299-
sentry_init(integrations=[DjangoIntegration()], spotlight=True)
1300-
1301-
added = frozenset(settings.MIDDLEWARE) ^ original_middleware
1302-
1303-
assert "sentry_sdk.spotlight.SpotlightMiddleware" in added
1304-
1305-
1306-
def test_ensures_no_spotlight_middleware_when_env_killswitch_is_false(
1307-
monkeypatch, sentry_init, settings
1308-
):
1309-
"""
1310-
Test that ensures if Spotlight is enabled, but is set to a falsy value
1311-
the relevant SpotlightMiddleware is NOT added to middleware list in settings.
1312-
"""
1313-
settings.DEBUG = True
1314-
monkeypatch.setenv("SENTRY_SPOTLIGHT_ON_ERROR", "no")
1315-
1316-
original_middleware = frozenset(settings.MIDDLEWARE)
1317-
1318-
sentry_init(integrations=[DjangoIntegration()], spotlight=True)
1319-
1320-
added = frozenset(settings.MIDDLEWARE) ^ original_middleware
1321-
1322-
assert "sentry_sdk.spotlight.SpotlightMiddleware" not in added
1323-
1324-
1325-
def test_ensures_no_spotlight_middleware_when_no_spotlight(
1326-
monkeypatch, sentry_init, settings
1327-
):
1328-
"""
1329-
Test that ensures if Spotlight is not enabled
1330-
the relevant SpotlightMiddleware is NOT added to middleware list in settings.
1331-
"""
1332-
settings.DEBUG = True
1333-
1334-
# We should NOT have the middleware even if the env var is truthy if Spotlight is off
1335-
monkeypatch.setenv("SENTRY_SPOTLIGHT_ON_ERROR", "1")
1336-
1337-
original_middleware = frozenset(settings.MIDDLEWARE)
1338-
1339-
sentry_init(integrations=[DjangoIntegration()], spotlight=False)
1340-
1341-
added = frozenset(settings.MIDDLEWARE) ^ original_middleware
1342-
1343-
assert "sentry_sdk.spotlight.SpotlightMiddleware" not in added
1344-
1345-
13461291
def test_get_frame_name_when_in_lazy_object():
13471292
allowed_to_init = False
13481293

0 commit comments

Comments
 (0)