Skip to content

Commit f2783c5

Browse files
committed
Do not truncate body if request_bodies is always
1 parent 8a2b74f commit f2783c5

File tree

6 files changed

+166
-13
lines changed

6 files changed

+166
-13
lines changed

sentry_sdk/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ def _prepare_event(
320320
# Postprocess the event here so that annotated types do
321321
# generally not surface in before_send
322322
if event is not None:
323-
event = serialize(event)
323+
event = serialize(event, request_bodies=self.options.get("request_bodies"))
324324

325325
before_send = self.options["before_send"]
326326
if (

sentry_sdk/serializer.py

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@
6767
# this value due to attached metadata, so keep the number conservative.
6868
MAX_EVENT_BYTES = 10**6
6969

70+
# Maximum depth and breadth of databags. Excess data will be trimmed. If
71+
# request_bodies is "always", request bodies won't be trimmed.
7072
MAX_DATABAG_DEPTH = 5
7173
MAX_DATABAG_BREADTH = 10
7274
CYCLE_MARKER = "<cyclic>"
@@ -118,6 +120,10 @@ def serialize(event, **kwargs):
118120
path = [] # type: List[Segment]
119121
meta_stack = [] # type: List[Dict[str, Any]]
120122

123+
do_not_trim_request_bodies = (
124+
kwargs.pop("request_bodies", None) == "always"
125+
) # type: bool
126+
121127
def _annotate(**meta):
122128
# type: (**Any) -> None
123129
while len(meta_stack) <= len(path):
@@ -182,10 +188,11 @@ def _is_databag():
182188
if rv in (True, None):
183189
return rv
184190

185-
p0 = path[0]
186-
if p0 == "request" and path[1] == "data":
187-
return True
191+
is_request_body = _is_request_body()
192+
if is_request_body in (True, None):
193+
return is_request_body
188194

195+
p0 = path[0]
189196
if p0 == "breadcrumbs" and path[1] == "values":
190197
path[2]
191198
return True
@@ -198,9 +205,20 @@ def _is_databag():
198205

199206
return False
200207

208+
def _is_request_body():
209+
# type: () -> Optional[bool]
210+
try:
211+
if path[0] == "request" and path[1] == "data":
212+
return True
213+
except IndexError:
214+
return None
215+
216+
return False
217+
201218
def _serialize_node(
202219
obj, # type: Any
203220
is_databag=None, # type: Optional[bool]
221+
is_request_body=None, # type: Optional[bool]
204222
should_repr_strings=None, # type: Optional[bool]
205223
segment=None, # type: Optional[Segment]
206224
remaining_breadth=None, # type: Optional[int]
@@ -218,6 +236,7 @@ def _serialize_node(
218236
return _serialize_node_impl(
219237
obj,
220238
is_databag=is_databag,
239+
is_request_body=is_request_body,
221240
should_repr_strings=should_repr_strings,
222241
remaining_depth=remaining_depth,
223242
remaining_breadth=remaining_breadth,
@@ -242,9 +261,14 @@ def _flatten_annotated(obj):
242261
return obj
243262

244263
def _serialize_node_impl(
245-
obj, is_databag, should_repr_strings, remaining_depth, remaining_breadth
264+
obj,
265+
is_databag,
266+
is_request_body,
267+
should_repr_strings,
268+
remaining_depth,
269+
remaining_breadth,
246270
):
247-
# type: (Any, Optional[bool], Optional[bool], Optional[int], Optional[int]) -> Any
271+
# type: (Any, Optional[bool], Optional[bool], Optional[bool], Optional[int], Optional[int]) -> Any
248272
if isinstance(obj, AnnotatedValue):
249273
should_repr_strings = False
250274
if should_repr_strings is None:
@@ -253,10 +277,18 @@ def _serialize_node_impl(
253277
if is_databag is None:
254278
is_databag = _is_databag()
255279

256-
if is_databag and remaining_depth is None:
257-
remaining_depth = MAX_DATABAG_DEPTH
258-
if is_databag and remaining_breadth is None:
259-
remaining_breadth = MAX_DATABAG_BREADTH
280+
if is_request_body is None:
281+
is_request_body = _is_request_body()
282+
283+
if is_databag:
284+
if is_request_body and do_not_trim_request_bodies:
285+
remaining_depth = float("inf")
286+
remaining_breadth = float("inf")
287+
else:
288+
if remaining_depth is None:
289+
remaining_depth = MAX_DATABAG_DEPTH
290+
if remaining_breadth is None:
291+
remaining_breadth = MAX_DATABAG_BREADTH
260292

261293
obj = _flatten_annotated(obj)
262294

tests/integrations/bottle/test_bottle.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,38 @@ def index():
275275
assert not event["request"]["data"]["file"]
276276

277277

278+
def test_json_not_truncated_if_request_bodies_is_always(
279+
sentry_init, capture_events, app, get_client
280+
):
281+
sentry_init(
282+
integrations=[bottle_sentry.BottleIntegration()], request_bodies="always"
283+
)
284+
285+
data = {"key{}".format(i): "value{}".format(i) for i in range(100)}
286+
data["nested"] = {
287+
"nested_key{}".format(i): "nested_value{}".format(i) for i in range(100)
288+
}
289+
290+
@app.route("/", method="POST")
291+
def index():
292+
import bottle
293+
294+
assert bottle.request.json == data
295+
assert bottle.request.body.read() == json.dumps(data).encode("ascii")
296+
capture_message("hi")
297+
return "ok"
298+
299+
events = capture_events()
300+
301+
client = get_client()
302+
303+
response = client.post("/", content_type="application/json", data=json.dumps(data))
304+
assert response[1] == "200 OK"
305+
306+
(event,) = events
307+
assert event["request"]["data"] == data
308+
309+
278310
@pytest.mark.parametrize(
279311
"integrations",
280312
[

tests/integrations/flask/test_flask.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,33 @@ def index():
447447
assert not event["request"]["data"]["file"]
448448

449449

450+
def test_json_not_truncated_if_request_bodies_is_always(
451+
sentry_init, capture_events, app
452+
):
453+
sentry_init(integrations=[flask_sentry.FlaskIntegration()], request_bodies="always")
454+
455+
data = {"key{}".format(i): "value{}".format(i) for i in range(100)}
456+
data["nested"] = {
457+
"nested_key{}".format(i): "nested_value{}".format(i) for i in range(100)
458+
}
459+
460+
@app.route("/", methods=["POST"])
461+
def index():
462+
assert request.get_json() == data
463+
assert request.get_data() == json.dumps(data).encode("ascii")
464+
capture_message("hi")
465+
return "ok"
466+
467+
events = capture_events()
468+
469+
client = app.test_client()
470+
response = client.post("/", content_type="application/json", data=json.dumps(data))
471+
assert response.status_code == 200
472+
473+
(event,) = events
474+
assert event["request"]["data"] == data
475+
476+
450477
@pytest.mark.parametrize(
451478
"integrations",
452479
[

tests/integrations/pyramid/test_pyramid.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,32 @@ def index(request):
192192
assert event["request"]["data"] == data
193193

194194

195+
def test_json_not_truncated_if_request_bodies_is_always(
196+
sentry_init, capture_events, route, get_client
197+
):
198+
sentry_init(integrations=[PyramidIntegration()], request_bodies="always")
199+
200+
data = {"key{}".format(i): "value{}".format(i) for i in range(100)}
201+
data["nested"] = {
202+
"nested_key{}".format(i): "nested_value{}".format(i) for i in range(100)
203+
}
204+
205+
@route("/")
206+
def index(request):
207+
assert request.json == data
208+
assert request.text == json.dumps(data)
209+
capture_message("hi")
210+
return Response("ok")
211+
212+
events = capture_events()
213+
214+
client = get_client()
215+
client.post("/", content_type="application/json", data=json.dumps(data))
216+
217+
(event,) = events
218+
assert event["request"]["data"] == data
219+
220+
195221
def test_files_and_form(sentry_init, capture_events, route, get_client):
196222
sentry_init(integrations=[PyramidIntegration()], request_bodies="always")
197223

tests/test_serializer.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import sys
33
import pytest
44

5-
from sentry_sdk.serializer import serialize
5+
from sentry_sdk.serializer import MAX_DATABAG_BREADTH, MAX_DATABAG_DEPTH, serialize
66

77
try:
88
from hypothesis import given
@@ -40,14 +40,24 @@ def inner(message, **kwargs):
4040

4141
@pytest.fixture
4242
def extra_normalizer(validate_event_schema):
43-
def inner(message, **kwargs):
44-
event = serialize({"extra": {"foo": message}}, **kwargs)
43+
def inner(extra, **kwargs):
44+
event = serialize({"extra": {"foo": extra}}, **kwargs)
4545
validate_event_schema(event)
4646
return event["extra"]["foo"]
4747

4848
return inner
4949

5050

51+
@pytest.fixture
52+
def body_normalizer(validate_event_schema):
53+
def inner(body, **kwargs):
54+
event = serialize({"request": {"data": body}}, **kwargs)
55+
validate_event_schema(event)
56+
return event["request"]["data"]
57+
58+
return inner
59+
60+
5161
def test_bytes_serialization_decode(message_normalizer):
5262
binary = b"abc123\x80\xf0\x9f\x8d\x95"
5363
result = message_normalizer(binary, should_repr_strings=False)
@@ -106,3 +116,29 @@ def test_custom_mapping_doesnt_mess_with_mock(extra_normalizer):
106116
m = mock.Mock()
107117
extra_normalizer(m)
108118
assert len(m.mock_calls) == 0
119+
120+
121+
def test_trim_databag_breadth(body_normalizer):
122+
data = {
123+
"key{}".format(i): "value{}".format(i) for i in range(MAX_DATABAG_BREADTH + 10)
124+
}
125+
126+
result = body_normalizer(data)
127+
128+
assert len(result) == MAX_DATABAG_BREADTH
129+
for key, value in result.items():
130+
assert data.get(key) == value
131+
132+
133+
def test_no_trimming_if_request_bodies_is_always(body_normalizer):
134+
data = {
135+
"key{}".format(i): "value{}".format(i) for i in range(MAX_DATABAG_BREADTH + 10)
136+
}
137+
curr = data
138+
for _ in range(MAX_DATABAG_DEPTH + 5):
139+
curr["nested"] = {}
140+
curr = data["nested"]
141+
142+
result = body_normalizer(data, request_bodies="always")
143+
144+
assert result == data

0 commit comments

Comments
 (0)