11from __future__ import annotations
22import io
33import logging
4- import os
5- import urllib .parse
6- import urllib .request
7- import urllib .error
84import urllib3
95import sys
106
11- from itertools import chain , product
12-
137from typing import TYPE_CHECKING
148
159if TYPE_CHECKING :
16- from typing import Any , Callable , Dict , Optional
10+ from typing import Any , Dict , Optional
1711
1812from sentry_sdk .utils import (
1913 logger as sentry_logger ,
20- env_to_bool ,
21- capture_internal_exceptions ,
2214)
2315from 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-
20558def 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
0 commit comments