Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
153 changes: 0 additions & 153 deletions sentry_sdk/spotlight.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
"<script>window.__spotlight = {{ initOptions: {{ sidecarUrl: '{spotlight_url}', fullPage: false }} }};</script>\n"
'<script type="module" crossorigin src="{spotlight_js_url}"></script>\n'
)
SPOTLIGHT_ERROR_PAGE_SNIPPET = (
'<html><base href="{spotlight_url}">\n'
'<script>window.__spotlight = {{ initOptions: {{ fullPage: true, startFrom: "/errors/{event_id}" }}}};</script>\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 `</body>` 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(
"<html>",
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"))
Expand All @@ -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)

Expand Down
55 changes: 0 additions & 55 deletions tests/integrations/django/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down