Skip to content

Commit 9d08577

Browse files
authored
Merge pull request #461 from rollbar/changed/shortner-to-breadth-first-traverse
Changed the `ShortenerTransform` to use breadth first traversal
2 parents 3a2330f + 9200f86 commit 9d08577

File tree

7 files changed

+422
-147
lines changed

7 files changed

+422
-147
lines changed

rollbar/lib/transform.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
class Transform(object):
2+
depth_first = True
3+
24
def default(self, o, key=None):
35
return o
46

rollbar/lib/transforms/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def default_handler(o, key=None):
8989
"allowed_circular_reference_types": _ALLOWED_CIRCULAR_REFERENCE_TYPES,
9090
}
9191

92-
return traverse.traverse(obj, key=key, **handlers)
92+
return traverse.traverse(obj, key=key, depth_first=transform.depth_first, **handlers)
9393

9494

9595
__all__ = ["transform", "Transform"]

rollbar/lib/transforms/shortener.py

Lines changed: 115 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
import reprlib
55

66
from collections.abc import Mapping
7+
from typing import Union, Tuple
78

89
from rollbar.lib import (
9-
integer_types, key_in, key_depth, number_types, sequence_types,
10+
integer_types, key_in, key_depth, sequence_types,
1011
string_types)
1112
from rollbar.lib.transform import Transform
1213

@@ -25,7 +26,89 @@
2526
}
2627

2728

29+
def _max_left_right(max_len: int, seperator_len: int) -> Tuple[int, int]:
30+
left = max(0, (max_len-seperator_len)//2)
31+
right = max(0, max_len-seperator_len-left)
32+
return left, right
33+
34+
35+
def shorten_array(obj: array, max_len: int) -> array:
36+
if len(obj) <= max_len:
37+
return obj
38+
39+
return obj[:max_len]
40+
41+
42+
def shorten_bytes(obj: bytes, max_len: int) -> bytes:
43+
if len(obj) <= max_len:
44+
return obj
45+
46+
return obj[:max_len]
47+
48+
49+
def shorten_deque(obj: collections.deque, max_len: int) -> collections.deque:
50+
if len(obj) <= max_len:
51+
return obj
52+
53+
return collections.deque(itertools.islice(obj, max_len))
54+
55+
56+
def shorten_frozenset(obj: frozenset, max_len: int) -> frozenset:
57+
if len(obj) <= max_len:
58+
return obj
59+
60+
return frozenset([elem for i, elem in enumerate(obj) if i < max_len] + ['...'])
61+
62+
63+
def shorten_int(obj: int, max_len: int) -> Union[int, str]:
64+
s = repr(obj)
65+
if len(s) <= max_len:
66+
return obj
67+
68+
left, right = _max_left_right(max_len, 3)
69+
return s[:left] + '...' + s[len(s)-right:]
70+
71+
72+
def shorten_list(obj: list, max_len: int) -> list:
73+
if len(obj) <= max_len:
74+
return obj
75+
76+
return obj[:max_len] + ['...']
77+
78+
79+
def shorten_mapping(obj: Union[dict, Mapping], max_keys: int) -> dict:
80+
if len(obj) <= max_keys:
81+
return obj
82+
83+
return {k: obj[k] for k in itertools.islice(obj.keys(), max_keys)}
84+
85+
86+
def shorten_set(obj: set, max_len: int) -> set:
87+
if len(obj) <= max_len:
88+
return obj
89+
90+
return set([elem for i, elem in enumerate(obj) if i < max_len] + ['...'])
91+
92+
93+
def shorten_string(obj: str, max_len: int) -> str:
94+
if len(obj) <= max_len:
95+
return obj
96+
97+
left, right = _max_left_right(max_len, 3)
98+
return obj[:left] + '...' + obj[len(obj)-right:]
99+
100+
101+
def shorten_tuple(obj: tuple, max_len: int) -> tuple:
102+
if len(obj) <= max_len:
103+
return obj
104+
105+
return obj[:max_len] + ('...',)
106+
107+
108+
28109
class ShortenerTransform(Transform):
110+
depth_first = False
111+
29112
def __init__(self, safe_repr=True, keys=None, **sizes):
30113
super(ShortenerTransform, self).__init__()
31114
self.safe_repr = safe_repr
@@ -47,26 +130,33 @@ def _get_max_size(self, obj):
47130

48131
return self._repr.maxother
49132

50-
def _shorten_sequence(self, obj, max_keys):
51-
_len = len(obj)
52-
if _len <= max_keys:
53-
return obj
54-
55-
return self._repr.repr(obj)
56-
57-
def _shorten_mapping(self, obj, max_keys):
58-
_len = len(obj)
59-
if _len <= max_keys:
60-
return obj
61-
62-
return {k: obj[k] for k in itertools.islice(obj.keys(), max_keys)}
133+
def _shorten(self, val):
134+
max_size = self._get_max_size(val)
63135

64-
def _shorten_basic(self, obj, max_len):
65-
val = str(obj)
66-
if len(val) <= max_len:
67-
return obj
136+
if isinstance(val, array):
137+
return shorten_array(val, max_size)
138+
if isinstance(val, bytes):
139+
return shorten_bytes(val, max_size)
140+
if isinstance(val, collections.deque):
141+
return shorten_deque(val, max_size)
142+
if isinstance(val, (dict, Mapping)):
143+
return shorten_mapping(val, max_size)
144+
if isinstance(val, float):
145+
return val
146+
if isinstance(val, frozenset):
147+
return shorten_frozenset(val, max_size)
148+
if isinstance(val, int):
149+
return shorten_int(val, max_size)
150+
if isinstance(val, list):
151+
return shorten_list(val, max_size)
152+
if isinstance(val, set):
153+
return shorten_set(val, max_size)
154+
if isinstance(val, str):
155+
return shorten_string(val, max_size)
156+
if isinstance(val, tuple):
157+
return shorten_tuple(val, max_size)
68158

69-
return self._repr.repr(obj)
159+
return self._shorten_other(val)
70160

71161
def _shorten_other(self, obj):
72162
if obj is None:
@@ -77,19 +167,6 @@ def _shorten_other(self, obj):
77167

78168
return self._repr.repr(obj)
79169

80-
def _shorten(self, val):
81-
max_size = self._get_max_size(val)
82-
83-
if isinstance(val, dict):
84-
return self._shorten_mapping(val, max_size)
85-
if isinstance(val, (string_types, sequence_types)):
86-
return self._shorten_sequence(val, max_size)
87-
88-
if isinstance(val, number_types):
89-
return self._shorten_basic(val, self._repr.maxlong)
90-
91-
return self._shorten_other(val)
92-
93170
def _should_shorten(self, val, key):
94171
if not key:
95172
return False
@@ -100,18 +177,18 @@ def _should_drop(self, val, key) -> bool:
100177
if not key:
101178
return False
102179

103-
maxdepth = key_depth(key, self.keys)
104-
if maxdepth == 0:
180+
max_depth = key_depth(key, self.keys)
181+
if max_depth == 0:
105182
return False
106183

107-
return (maxdepth + self._repr.maxlevel) <= len(key)
184+
return (max_depth + self._repr.maxlevel) <= len(key)
108185

109186
def default(self, o, key=None):
110187
if self._should_drop(o, key):
111-
if isinstance(o, dict):
112-
return '{...}'
188+
if isinstance(o, (dict, Mapping)):
189+
return {'...': '...'}
113190
if isinstance(o, sequence_types):
114-
return '[...]'
191+
return ['...']
115192

116193
if self._should_shorten(o, key):
117194
return self._shorten(o)

rollbar/lib/traverse.py

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def traverse(
8181
circular_reference_handler=_default_handlers[CIRCULAR],
8282
allowed_circular_reference_types=None,
8383
memo=None,
84+
depth_first=True,
8485
**custom_handlers
8586
):
8687
memo = memo or {}
@@ -108,42 +109,58 @@ def traverse(
108109
"circular_reference_handler": circular_reference_handler,
109110
"allowed_circular_reference_types": allowed_circular_reference_types,
110111
"memo": memo,
112+
"depth_first": depth_first,
111113
}
112114
kw.update(custom_handlers)
113115

114116
try:
115117
if obj_type is STRING:
116118
return string_handler(obj, key=key)
117119
elif obj_type is TUPLE:
118-
return tuple_handler(
119-
tuple(
120-
traverse(elem, key=key + (i,), **kw) for i, elem in enumerate(obj)
121-
),
122-
key=key,
123-
)
120+
if depth_first:
121+
return tuple_handler(
122+
tuple(
123+
traverse(elem, key=key + (i,), **kw) for i, elem in enumerate(obj)
124+
),
125+
key=key,
126+
)
127+
# Breadth first
128+
return tuple(traverse(elem, key=key + (i,), **kw) for i, elem in enumerate(tuple_handler(obj, key=key)))
124129
elif obj_type is NAMEDTUPLE:
125-
return namedtuple_handler(
126-
obj._make(
127-
traverse(v, key=key + (k,), **kw)
128-
for k, v in obj._asdict().items()
129-
),
130-
key=key,
131-
)
130+
if depth_first:
131+
return namedtuple_handler(
132+
obj._make(
133+
traverse(v, key=key + (k,), **kw)
134+
for k, v in obj._asdict().items()
135+
),
136+
key=key,
137+
)
138+
# Breadth first
139+
return obj._make(traverse(v, key=key + (k,), **kw) for k, v in namedtuple_handler(obj, key=key)._asdict().items())
132140
elif obj_type is LIST:
133-
return list_handler(
134-
[traverse(elem, key=key + (i,), **kw) for i, elem in enumerate(obj)],
135-
key=key,
136-
)
141+
if depth_first:
142+
return list_handler(
143+
[traverse(elem, key=key + (i,), **kw) for i, elem in enumerate(obj)],
144+
key=key,
145+
)
146+
# Breadth first
147+
return [traverse(elem, key=key + (i,), **kw) for i, elem in enumerate(list_handler(obj, key=key))]
137148
elif obj_type is SET:
138-
return set_handler(
139-
{traverse(elem, key=key + (i,), **kw) for i, elem in enumerate(obj)},
140-
key=key,
141-
)
149+
if depth_first:
150+
return set_handler(
151+
{traverse(elem, key=key + (i,), **kw) for i, elem in enumerate(obj)},
152+
key=key,
153+
)
154+
# Breadth first
155+
return {traverse(elem, key=key + (i,), **kw) for i, elem in enumerate(set_handler(obj, key=key))}
142156
elif obj_type is MAPPING:
143-
return mapping_handler(
144-
{k: traverse(v, key=key + (k,), **kw) for k, v in obj.items()},
145-
key=key,
146-
)
157+
if depth_first:
158+
return mapping_handler(
159+
{k: traverse(v, key=key + (k,), **kw) for k, v in obj.items()},
160+
key=key,
161+
)
162+
# Breadth first
163+
return {k: traverse(v, key=key + (k,), **kw) for k, v in mapping_handler(obj, key=key).items()}
147164
elif obj_type is PATH:
148165
return path_handler(obj, key=key)
149166
elif obj_type is DEFAULT:

rollbar/test/test_rollbar.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,7 +1555,7 @@ def _raise(large):
15551555

15561556
self.assertEqual(1, len(payload['data']['body']['trace']['frames'][-1]['argspec']))
15571557
self.assertEqual('large', payload['data']['body']['trace']['frames'][-1]['argspec'][0])
1558-
self.assertEqual("'###############################################...################################################'",
1558+
self.assertEqual("################################################...#################################################",
15591559
payload['data']['body']['trace']['frames'][-1]['locals']['large'])
15601560

15611561
@mock.patch('rollbar.send_payload')
@@ -1586,12 +1586,12 @@ def _raise(large):
15861586

15871587
self.assertEqual('large', payload['data']['body']['trace']['frames'][-1]['argspec'][0])
15881588
self.assertTrue(
1589-
("['hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', ...]" ==
1589+
(['hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', '...'] ==
15901590
payload['data']['body']['trace']['frames'][-1]['argspec'][0])
15911591

15921592
or
15931593

1594-
("['hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', ...]" ==
1594+
(['hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', '...'] ==
15951595
payload['data']['body']['trace']['frames'][0]['locals']['xlarge']))
15961596

15971597

0 commit comments

Comments
 (0)