1
1
from __future__ import annotations
2
2
import io
3
3
import logging
4
- import os
5
- import urllib .parse
6
- import urllib .request
7
- import urllib .error
8
4
import urllib3
9
5
import sys
10
6
11
- from itertools import chain , product
12
-
13
7
from typing import TYPE_CHECKING
14
8
15
9
if TYPE_CHECKING :
16
- from typing import Any , Callable , Dict , Optional
10
+ from typing import Any , Dict , Optional
17
11
18
12
from sentry_sdk .utils import (
19
13
logger as sentry_logger ,
20
- env_to_bool ,
21
- capture_internal_exceptions ,
22
14
)
23
15
from sentry_sdk .envelope import Envelope
24
16
@@ -63,145 +55,6 @@ def capture_envelope(self, envelope: Envelope) -> None:
63
55
# to avoid overflowing the variable if Spotlight never becomes reachable
64
56
65
57
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
-
205
58
def setup_spotlight (options : Dict [str , Any ]) -> Optional [SpotlightClient ]:
206
59
_handler = logging .StreamHandler (sys .stderr )
207
60
_handler .setFormatter (logging .Formatter (" [spotlight] %(levelname)s: %(message)s" ))
@@ -216,20 +69,6 @@ def setup_spotlight(options: Dict[str, Any]) -> Optional[SpotlightClient]:
216
69
if not isinstance (url , str ):
217
70
return None
218
71
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
-
233
72
client = SpotlightClient (url )
234
73
logger .info ("Enabled Spotlight using sidecar at %s" , url )
235
74
0 commit comments