Skip to content

Commit 838c5ea

Browse files
committed
Merge branch 'master' into byk/feat/opportunistic-brotli
2 parents 34ef562 + 00f8140 commit 838c5ea

File tree

3 files changed

+106
-2
lines changed

3 files changed

+106
-2
lines changed

sentry_sdk/client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
from sentry_sdk.metrics import MetricsAggregator
6262
from sentry_sdk.scope import Scope
6363
from sentry_sdk.session import Session
64+
from sentry_sdk.spotlight import SpotlightClient
6465
from sentry_sdk.transport import Transport
6566

6667
I = TypeVar("I", bound=Integration) # noqa: E741
@@ -153,6 +154,8 @@ class BaseClient:
153154
The basic definition of a client that is used for sending data to Sentry.
154155
"""
155156

157+
spotlight = None # type: Optional[SpotlightClient]
158+
156159
def __init__(self, options=None):
157160
# type: (Optional[Dict[str, Any]]) -> None
158161
self.options = (
@@ -385,7 +388,6 @@ def _capture_envelope(envelope):
385388
disabled_integrations=self.options["disabled_integrations"],
386389
)
387390

388-
self.spotlight = None
389391
spotlight_config = self.options.get("spotlight")
390392
if spotlight_config is None and "SENTRY_SPOTLIGHT" in os.environ:
391393
spotlight_env_value = os.environ["SENTRY_SPOTLIGHT"]

sentry_sdk/spotlight.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import io
2+
import os
3+
import urllib.parse
4+
import urllib.request
5+
import urllib.error
26
import urllib3
37

48
from typing import TYPE_CHECKING
59

610
if TYPE_CHECKING:
711
from typing import Any
12+
from typing import Callable
813
from typing import Dict
914
from typing import Optional
1015

11-
from sentry_sdk.utils import logger
16+
from sentry_sdk.utils import logger, env_to_bool
1217
from sentry_sdk.envelope import Envelope
1318

1419

@@ -46,6 +51,47 @@ def capture_envelope(self, envelope):
4651
logger.warning(str(e))
4752

4853

54+
try:
55+
from django.http import HttpResponseServerError
56+
from django.conf import settings
57+
58+
class SpotlightMiddleware:
59+
def __init__(self, get_response):
60+
# type: (Any, Callable[..., Any]) -> None
61+
self.get_response = get_response
62+
63+
def __call__(self, request):
64+
# type: (Any, Any) -> Any
65+
return self.get_response(request)
66+
67+
def process_exception(self, _request, exception):
68+
# type: (Any, Any, Exception) -> Optional[HttpResponseServerError]
69+
if not settings.DEBUG:
70+
return None
71+
72+
import sentry_sdk.api
73+
74+
spotlight_client = sentry_sdk.api.get_client().spotlight
75+
if spotlight_client is None:
76+
return None
77+
78+
# Spotlight URL has a trailing `/stream` part at the end so split it off
79+
spotlight_url = spotlight_client.url.rsplit("/", 1)[0]
80+
81+
try:
82+
spotlight = (
83+
urllib.request.urlopen(spotlight_url).read().decode("utf-8")
84+
).replace("<html>", f'<html><base href="{spotlight_url}">')
85+
except urllib.error.URLError:
86+
return None
87+
else:
88+
sentry_sdk.api.capture_exception(exception)
89+
return HttpResponseServerError(spotlight)
90+
91+
except ImportError:
92+
settings = None
93+
94+
4995
def setup_spotlight(options):
5096
# type: (Dict[str, Any]) -> Optional[SpotlightClient]
5197

@@ -58,4 +104,9 @@ def setup_spotlight(options):
58104
else:
59105
return None
60106

107+
if settings is not None and env_to_bool(
108+
os.environ.get("SENTRY_SPOTLIGHT_ON_ERROR", "1")
109+
):
110+
settings.MIDDLEWARE.append("sentry_sdk.spotlight.SpotlightMiddleware")
111+
61112
return SpotlightClient(url)

tests/integrations/django/test_basic.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,3 +1240,54 @@ def test_transaction_http_method_custom(sentry_init, client, capture_events):
12401240
(event1, event2) = events
12411241
assert event1["request"]["method"] == "OPTIONS"
12421242
assert event2["request"]["method"] == "HEAD"
1243+
1244+
1245+
def test_ensures_spotlight_middleware_when_spotlight_is_enabled(sentry_init, settings):
1246+
"""
1247+
Test that ensures if Spotlight is enabled, relevant SpotlightMiddleware
1248+
is added to middleware list in settings.
1249+
"""
1250+
original_middleware = frozenset(settings.MIDDLEWARE)
1251+
1252+
sentry_init(integrations=[DjangoIntegration()], spotlight=True)
1253+
1254+
added = frozenset(settings.MIDDLEWARE) ^ original_middleware
1255+
1256+
assert "sentry_sdk.spotlight.SpotlightMiddleware" in added
1257+
1258+
1259+
def test_ensures_no_spotlight_middleware_when_env_killswitch_is_false(
1260+
monkeypatch, sentry_init, settings
1261+
):
1262+
"""
1263+
Test that ensures if Spotlight is enabled, but is set to a falsy value
1264+
the relevant SpotlightMiddleware is NOT added to middleware list in settings.
1265+
"""
1266+
monkeypatch.setenv("SENTRY_SPOTLIGHT_ON_ERROR", "no")
1267+
1268+
original_middleware = frozenset(settings.MIDDLEWARE)
1269+
1270+
sentry_init(integrations=[DjangoIntegration()], spotlight=True)
1271+
1272+
added = frozenset(settings.MIDDLEWARE) ^ original_middleware
1273+
1274+
assert "sentry_sdk.spotlight.SpotlightMiddleware" not in added
1275+
1276+
1277+
def test_ensures_no_spotlight_middleware_when_no_spotlight(
1278+
monkeypatch, sentry_init, settings
1279+
):
1280+
"""
1281+
Test that ensures if Spotlight is not enabled
1282+
the relevant SpotlightMiddleware is NOT added to middleware list in settings.
1283+
"""
1284+
# We should NOT have the middleware even if the env var is truthy if Spotlight is off
1285+
monkeypatch.setenv("SENTRY_SPOTLIGHT_ON_ERROR", "1")
1286+
1287+
original_middleware = frozenset(settings.MIDDLEWARE)
1288+
1289+
sentry_init(integrations=[DjangoIntegration()], spotlight=False)
1290+
1291+
added = frozenset(settings.MIDDLEWARE) ^ original_middleware
1292+
1293+
assert "sentry_sdk.spotlight.SpotlightMiddleware" not in added

0 commit comments

Comments
 (0)