2
2
from __future__ import absolute_import
3
3
4
4
import sys
5
+ import threading
5
6
import weakref
6
7
7
8
from django import VERSION as DJANGO_VERSION # type: ignore
8
- from django .db .models .query import QuerySet # type: ignore
9
9
from django .core import signals # type: ignore
10
10
11
11
if False :
@@ -116,6 +116,8 @@ def sentry_patched_get_response(self, request):
116
116
hub = Hub .current
117
117
integration = hub .get_integration (DjangoIntegration )
118
118
if integration is not None :
119
+ _patch_drf ()
120
+
119
121
with hub .configure_scope () as scope :
120
122
scope .add_event_processor (
121
123
_make_event_processor (weakref .ref (request ), integration )
@@ -124,25 +126,6 @@ def sentry_patched_get_response(self, request):
124
126
125
127
BaseHandler .get_response = sentry_patched_get_response
126
128
127
- try :
128
- from rest_framework .views import APIView # type: ignore
129
- except ImportError :
130
- pass
131
- else :
132
- # DRF's request type (which wraps the Django request and proxies
133
- # all attrs) has some attributes such as `data` which buffer
134
- # request data. We want to use those in the RequestExtractor to
135
- # get body data more reliably.
136
- old_drf_initial = APIView .initial
137
-
138
- def sentry_patched_drf_initial (self , request , * args , ** kwargs ):
139
- with capture_internal_exceptions ():
140
- request ._request ._sentry_drf_request_backref = weakref .ref (request )
141
- pass
142
- return old_drf_initial (self , request , * args , ** kwargs )
143
-
144
- APIView .initial = sentry_patched_drf_initial
145
-
146
129
signals .got_request_exception .connect (_got_request_exception )
147
130
148
131
@add_global_event_processor
@@ -190,6 +173,17 @@ def process_django_templates(event, hint):
190
173
191
174
@add_global_repr_processor
192
175
def _django_queryset_repr (value , hint ):
176
+ try :
177
+ # Django 1.6 can fail to import `QuerySet` when Django settings
178
+ # have not yet been initialized.
179
+ #
180
+ # If we fail to import, return `NotImplemented`. It's at least
181
+ # unlikely that we have a query set in `value` when importing
182
+ # `QuerySet` fails.
183
+ from django .db .models .query import QuerySet # type: ignore
184
+ except Exception :
185
+ return NotImplemented
186
+
193
187
if not isinstance (value , QuerySet ) or value ._result_cache :
194
188
return NotImplemented
195
189
@@ -205,6 +199,63 @@ def _django_queryset_repr(value, hint):
205
199
)
206
200
207
201
202
+ _DRF_PATCHED = False
203
+ _DRF_PATCH_LOCK = threading .Lock ()
204
+
205
+
206
+ def _patch_drf ():
207
+ """
208
+ Patch Django Rest Framework for more/better request data. DRF's request
209
+ type is a wrapper around Django's request type. The attribute we're
210
+ interested in is `request.data`, which is a cached property containing a
211
+ parsed request body. Reading a request body from that property is more
212
+ reliable than reading from any of Django's own properties, as those don't
213
+ hold payloads in memory and therefore can only be accessed once.
214
+
215
+ We patch the Django request object to include a weak backreference to the
216
+ DRF request object, such that we can later use either in
217
+ `DjangoRequestExtractor`.
218
+
219
+ This function is not called directly on SDK setup, because importing almost
220
+ any part of Django Rest Framework will try to access Django settings (where
221
+ `sentry_sdk.init()` might be called from in the first place). Instead we
222
+ run this function on every request and do the patching on the first
223
+ request.
224
+ """
225
+
226
+ global _DRF_PATCHED
227
+
228
+ if _DRF_PATCHED :
229
+ # Double-checked locking
230
+ return
231
+
232
+ with _DRF_PATCH_LOCK :
233
+ if _DRF_PATCHED :
234
+ return
235
+
236
+ # We set this regardless of whether the code below succeeds or fails.
237
+ # There is no point in trying to patch again on the next request.
238
+ _DRF_PATCHED = True
239
+
240
+ with capture_internal_exceptions ():
241
+ try :
242
+ from rest_framework .views import APIView # type: ignore
243
+ except ImportError :
244
+ pass
245
+ else :
246
+ old_drf_initial = APIView .initial
247
+
248
+ def sentry_patched_drf_initial (self , request , * args , ** kwargs ):
249
+ with capture_internal_exceptions ():
250
+ request ._request ._sentry_drf_request_backref = weakref .ref (
251
+ request
252
+ )
253
+ pass
254
+ return old_drf_initial (self , request , * args , ** kwargs )
255
+
256
+ APIView .initial = sentry_patched_drf_initial
257
+
258
+
208
259
def _make_event_processor (weak_request , integration ):
209
260
# type: (Callable[[], WSGIRequest], DjangoIntegration) -> Callable
210
261
def event_processor (event , hint ):
0 commit comments