1
1
import sys
2
- import itertools
3
2
4
3
from datetime import datetime
5
4
23
22
from typing import Any
24
23
from typing import Dict
25
24
from typing import List
26
- from typing import Tuple
27
25
from typing import Optional
28
26
from typing import Callable
29
27
from typing import Union
@@ -59,11 +57,11 @@ def add_global_repr_processor(processor):
59
57
60
58
61
59
class Memo (object ):
62
- __slots__ = ("_inner " , "_objs" )
60
+ __slots__ = ("_ids " , "_objs" )
63
61
64
62
def __init__ (self ):
65
63
# type: () -> None
66
- self ._inner = {} # type: Dict[int, Any]
64
+ self ._ids = {} # type: Dict[int, Any]
67
65
self ._objs = [] # type: List[Any]
68
66
69
67
def memoize (self , obj ):
@@ -74,10 +72,10 @@ def memoize(self, obj):
74
72
def __enter__ (self ):
75
73
# type: () -> bool
76
74
obj = self ._objs [- 1 ]
77
- if id (obj ) in self ._inner :
75
+ if id (obj ) in self ._ids :
78
76
return True
79
77
else :
80
- self ._inner [id (obj )] = obj
78
+ self ._ids [id (obj )] = obj
81
79
return False
82
80
83
81
def __exit__ (
@@ -87,7 +85,7 @@ def __exit__(
87
85
tb , # type: Optional[TracebackType]
88
86
):
89
87
# type: (...) -> None
90
- self ._inner .pop (id (self ._objs .pop ()), None )
88
+ self ._ids .pop (id (self ._objs .pop ()), None )
91
89
92
90
93
91
def serialize (event , ** kwargs ):
@@ -109,27 +107,80 @@ def _annotate(**meta):
109
107
110
108
meta_stack [- 1 ].setdefault ("" , {}).update (meta )
111
109
112
- def _startswith_path (prefix ):
113
- # type: (Tuple[Optional[Segment], ...]) -> bool
114
- if len (prefix ) > len (path ):
115
- return False
110
+ def _should_repr_strings ():
111
+ # type: () -> Optional[bool]
112
+ """
113
+ By default non-serializable objects are going through
114
+ safe_repr(). For certain places in the event (local vars) we
115
+ want to repr() even things that are JSON-serializable to
116
+ make their type more apparent. For example, it's useful to
117
+ see the difference between a unicode-string and a bytestring
118
+ when viewing a stacktrace.
119
+
120
+ For container-types we still don't do anything different.
121
+ Generally we just try to make the Sentry UI present exactly
122
+ what a pretty-printed repr would look like.
123
+
124
+ :returns: `True` if we are somewhere in frame variables, and `False` if
125
+ we are in a position where we will never encounter frame variables
126
+ when recursing (for example, we're in `event.extra`). `None` if we
127
+ are not (yet) in frame variables, but might encounter them when
128
+ recursing (e.g. we're in `event.exception`)
129
+ """
130
+ try :
131
+ p0 = path [0 ]
132
+ if p0 == "stacktrace" and path [1 ] == "frames" and path [3 ] == "vars" :
133
+ return True
134
+
135
+ if (
136
+ p0 in ("threads" , "exception" )
137
+ and path [1 ] == "values"
138
+ and path [3 ] == "stacktrace"
139
+ and path [4 ] == "frames"
140
+ and path [6 ] == "vars"
141
+ ):
142
+ return True
143
+ except IndexError :
144
+ return None
116
145
117
- for i , segment in enumerate (prefix ):
118
- if segment is None :
119
- continue
146
+ return False
147
+
148
+ def _is_databag ():
149
+ # type: () -> Optional[bool]
150
+ """
151
+ A databag is any value that we need to trim.
152
+
153
+ :returns: Works like `_should_repr_strings()`. `True` for "yes",
154
+ `False` for :"no", `None` for "maybe soon".
155
+ """
156
+ try :
157
+ rv = _should_repr_strings ()
158
+ if rv in (True , None ):
159
+ return rv
120
160
121
- if path [i ] != segment :
122
- return False
161
+ p0 = path [0 ]
162
+ if p0 == "request" and path [1 ] == "data" :
163
+ return True
123
164
124
- return True
165
+ if p0 == "breadcrumbs" :
166
+ path [1 ]
167
+ return True
168
+
169
+ if p0 == "extra" :
170
+ return True
171
+
172
+ except IndexError :
173
+ return None
174
+
175
+ return False
125
176
126
177
def _serialize_node (
127
178
obj , # type: Any
128
- max_depth = None , # type: Optional[int]
129
- max_breadth = None , # type: Optional[int]
130
179
is_databag = None , # type: Optional[bool]
131
180
should_repr_strings = None , # type: Optional[bool]
132
181
segment = None , # type: Optional[Segment]
182
+ remaining_breadth = None , # type: Optional[int]
183
+ remaining_depth = None , # type: Optional[int]
133
184
):
134
185
# type: (...) -> Any
135
186
if segment is not None :
@@ -142,10 +193,10 @@ def _serialize_node(
142
193
143
194
return _serialize_node_impl (
144
195
obj ,
145
- max_depth = max_depth ,
146
- max_breadth = max_breadth ,
147
196
is_databag = is_databag ,
148
197
should_repr_strings = should_repr_strings ,
198
+ remaining_depth = remaining_depth ,
199
+ remaining_breadth = remaining_breadth ,
149
200
)
150
201
except BaseException :
151
202
capture_internal_exception (sys .exc_info ())
@@ -167,47 +218,19 @@ def _flatten_annotated(obj):
167
218
return obj
168
219
169
220
def _serialize_node_impl (
170
- obj , max_depth , max_breadth , is_databag , should_repr_strings
221
+ obj , is_databag , should_repr_strings , remaining_depth , remaining_breadth
171
222
):
172
- # type: (Any, Optional[int], Optional[int], Optional[bool], Optional[bool]) -> Any
173
- if not should_repr_strings :
174
- should_repr_strings = (
175
- _startswith_path (
176
- ("exception" , "values" , None , "stacktrace" , "frames" , None , "vars" )
177
- )
178
- or _startswith_path (
179
- ("threads" , "values" , None , "stacktrace" , "frames" , None , "vars" )
180
- )
181
- or _startswith_path (("stacktrace" , "frames" , None , "vars" ))
182
- )
223
+ # type: (Any, Optional[bool], Optional[bool], Optional[int], Optional[int]) -> Any
224
+ if should_repr_strings is None :
225
+ should_repr_strings = _should_repr_strings ()
183
226
184
- if obj is None or isinstance ( obj , ( bool , number_types )) :
185
- return obj if not should_repr_strings else safe_repr ( obj )
227
+ if is_databag is None :
228
+ is_databag = _is_databag ( )
186
229
187
- if isinstance (obj , datetime ):
188
- return (
189
- text_type (obj .strftime ("%Y-%m-%dT%H:%M:%S.%fZ" ))
190
- if not should_repr_strings
191
- else safe_repr (obj )
192
- )
193
-
194
- if not is_databag :
195
- is_databag = (
196
- should_repr_strings
197
- or _startswith_path (("request" , "data" ))
198
- or _startswith_path (("breadcrumbs" , None ))
199
- or _startswith_path (("extra" ,))
200
- )
201
-
202
- cur_depth = len (path )
203
- if max_depth is None and max_breadth is None and is_databag :
204
- max_depth = cur_depth + MAX_DATABAG_DEPTH
205
- max_breadth = cur_depth + MAX_DATABAG_BREADTH
206
-
207
- if max_depth is None :
208
- remaining_depth = None
209
- else :
210
- remaining_depth = max_depth - cur_depth
230
+ if is_databag and remaining_depth is None :
231
+ remaining_depth = MAX_DATABAG_DEPTH
232
+ if is_databag and remaining_breadth is None :
233
+ remaining_breadth = MAX_DATABAG_BREADTH
211
234
212
235
obj = _flatten_annotated (obj )
213
236
@@ -217,54 +240,72 @@ def _serialize_node_impl(
217
240
return _flatten_annotated (strip_string (safe_repr (obj )))
218
241
return None
219
242
220
- if global_repr_processors and is_databag :
243
+ if is_databag and global_repr_processors :
221
244
hints = {"memo" : memo , "remaining_depth" : remaining_depth }
222
245
for processor in global_repr_processors :
223
246
result = processor (obj , hints )
224
247
if result is not NotImplemented :
225
248
return _flatten_annotated (result )
226
249
227
- if isinstance (obj , Mapping ):
250
+ if obj is None or isinstance (obj , (bool , number_types )):
251
+ return obj if not should_repr_strings else safe_repr (obj )
252
+
253
+ elif isinstance (obj , datetime ):
254
+ return (
255
+ text_type (obj .strftime ("%Y-%m-%dT%H:%M:%S.%fZ" ))
256
+ if not should_repr_strings
257
+ else safe_repr (obj )
258
+ )
259
+
260
+ elif isinstance (obj , Mapping ):
228
261
# Create temporary copy here to avoid calling too much code that
229
262
# might mutate our dictionary while we're still iterating over it.
230
- if max_breadth is not None and len ( obj ) >= max_breadth :
231
- rv_dict = dict ( itertools . islice ( iteritems ( obj ), None , max_breadth ))
232
- _annotate ( len = len ( obj ))
233
- else :
234
- if type ( obj ) is dict :
235
- rv_dict = dict (obj )
236
- else :
237
- rv_dict = dict ( iteritems (obj ))
238
-
239
- for k in list ( rv_dict ):
263
+ obj = dict ( iteritems ( obj ))
264
+
265
+ rv_dict = {}
266
+ i = 0
267
+
268
+ for k , v in iteritems (obj ):
269
+ if remaining_breadth is not None and i >= remaining_breadth :
270
+ _annotate ( len = len (obj ))
271
+ break
272
+
240
273
str_k = text_type (k )
241
274
v = _serialize_node (
242
- rv_dict .pop (k ),
243
- max_depth = max_depth ,
244
- max_breadth = max_breadth ,
275
+ v ,
245
276
segment = str_k ,
246
277
should_repr_strings = should_repr_strings ,
247
278
is_databag = is_databag ,
279
+ remaining_depth = remaining_depth - 1
280
+ if remaining_depth is not None
281
+ else None ,
282
+ remaining_breadth = remaining_breadth ,
248
283
)
249
284
if v is not None :
250
285
rv_dict [str_k ] = v
286
+ i += 1
251
287
252
288
return rv_dict
289
+
253
290
elif not isinstance (obj , string_types ) and isinstance (obj , Sequence ):
254
- if max_breadth is not None and len (obj ) >= max_breadth :
255
- rv_list = list (obj )[:max_breadth ]
256
- _annotate (len = len (obj ))
257
- else :
258
- rv_list = list (obj )
259
-
260
- for i in range (len (rv_list )):
261
- rv_list [i ] = _serialize_node (
262
- rv_list [i ],
263
- max_depth = max_depth ,
264
- max_breadth = max_breadth ,
265
- segment = i ,
266
- should_repr_strings = should_repr_strings ,
267
- is_databag = is_databag ,
291
+ rv_list = []
292
+
293
+ for i , v in enumerate (obj ):
294
+ if remaining_breadth is not None and i >= remaining_breadth :
295
+ _annotate (len = len (obj ))
296
+ break
297
+
298
+ rv_list .append (
299
+ _serialize_node (
300
+ v ,
301
+ segment = i ,
302
+ should_repr_strings = should_repr_strings ,
303
+ is_databag = is_databag ,
304
+ remaining_depth = remaining_depth - 1
305
+ if remaining_depth is not None
306
+ else None ,
307
+ remaining_breadth = remaining_breadth ,
308
+ )
268
309
)
269
310
270
311
return rv_list
@@ -285,6 +326,7 @@ def _serialize_node_impl(
285
326
rv = _serialize_node (event , ** kwargs )
286
327
if meta_stack and isinstance (rv , dict ):
287
328
rv ["_meta" ] = meta_stack [0 ]
329
+
288
330
return rv
289
331
finally :
290
332
disable_capture_event .set (False )
0 commit comments