Skip to content

Commit 356e0c6

Browse files
committed
feat: change how request bodies are dealt with
1 parent 5980908 commit 356e0c6

File tree

7 files changed

+61
-62
lines changed

7 files changed

+61
-62
lines changed

docs/config.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,11 @@
4949
The provided list must contain actual classes, "string identifiers" are not
5050
supported. Subclass hierarchies are respected, for example, setting
5151
``ignore_errors=[BaseException]`` will ignore all exceptions.
52+
- ``request_bodies`` (default ``"medium"``): One of
53+
``always|medium|small|never``. Whether to send the request body with each event.
54+
55+
- ``always``: Always send the request body. It is still omitted if it is not
56+
JSON or formdata.
57+
- ``medium``: Send "medium-large" request bodies but omit bigger ones.
58+
- ``small``: Send "small" request bodies but omit bigger ones.
59+
- ``never``: Never touch the request body.

sentry_sdk/client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ def __init__(self, dsn=None, *args, **kwargs):
5858
for integration in integrations:
5959
integration(self)
6060

61+
request_bodies = ("always", "never", "small", "medium")
62+
if options["request_bodies"] not in request_bodies:
63+
raise ValueError(
64+
"Invalid value for request_bodies. Must be one of {}".format(
65+
request_bodies
66+
)
67+
)
68+
6169
atexit.register(self.close)
6270

6371
@property

sentry_sdk/consts.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"http_proxy": None,
2020
"https_proxy": None,
2121
"ignore_errors": (),
22+
"request_bodies": "medium",
2223
}
2324

2425
SDK_INFO = {"name": "sentry-python", "version": VERSION}

sentry_sdk/integrations/_wsgi.py

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import json
2-
import base64
32

43
from sentry_sdk.hub import _should_send_default_pii
54
from sentry_sdk.stripping import AnnotatedValue
@@ -38,7 +37,7 @@ class RequestExtractor(object):
3837
def __init__(self, request):
3938
self.request = request
4039

41-
def extract_into_event(self, event):
40+
def extract_into_event(self, event, client_options):
4241
if "request" in event:
4342
return
4443

@@ -62,40 +61,48 @@ def extract_into_event(self, event):
6261
not in ("set-cookie", "cookie", "authentication")
6362
}
6463

65-
if self.form or self.files:
64+
bodies = client_options.get("request_bodies")
65+
if (
66+
bodies == "never"
67+
or (bodies == "small" and self.content_length > 10 ** 3)
68+
or (bodies == "medium" and self.content_length > 10 ** 6)
69+
):
70+
data = AnnotatedValue(
71+
"",
72+
{
73+
"rem": [["!config", "x", 0, self.content_length]],
74+
"len": self.content_length,
75+
},
76+
)
77+
elif self.form or self.files:
6678
data = dict(self.form.items())
6779
for k, v in self.files.items():
80+
size = self.size_of_file(v)
6881
data[k] = AnnotatedValue(
69-
"",
70-
{"len": self.size_of_file(v), "rem": [["!filecontent", "x", 0, 0]]},
82+
"", {"len": size, "rem": [["!filecontent", "x", 0, size]]}
7183
)
7284

73-
if self.files or self.form_is_multipart:
74-
ct = "multipart"
75-
else:
76-
ct = "urlencoded"
77-
repr = "structured"
7885
elif self.json is not None:
7986
data = self.json
80-
ct = "json"
81-
repr = "structured"
8287
elif self.raw_data:
83-
data = self.raw_data
84-
85-
try:
86-
if isinstance(data, bytes):
87-
data = data.decode("utf-8")
88-
ct = "plain"
89-
repr = "other"
90-
except UnicodeDecodeError:
91-
ct = "bytes"
92-
repr = "base64"
93-
data = base64.b64encode(data).decode("ascii")
88+
data = AnnotatedValue(
89+
"",
90+
{
91+
"rem": [["!rawbody", "x", 0, self.content_length]],
92+
"len": self.content_length,
93+
},
94+
)
9495
else:
9596
return
9697

9798
request_info["data"] = data
98-
request_info["data_info"] = {"ct": ct, "repr": repr}
99+
100+
@property
101+
def content_length(self):
102+
try:
103+
return int(self.env.get("CONTENT_LENGTH", 0))
104+
except ValueError:
105+
return 0
99106

100107
@property
101108
def url(self):

sentry_sdk/integrations/django.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,13 @@ def sentry_patched_get_response(self, request):
5555
signals.got_request_exception.connect(_got_request_exception)
5656

5757
def _make_event_processor(self, request):
58+
client_options = get_current_hub().client.options
59+
5860
def processor(event):
5961
with _internal_exceptions():
60-
DjangoRequestExtractor(request).extract_into_event(event)
62+
DjangoRequestExtractor(request).extract_into_event(
63+
event, client_options
64+
)
6165

6266
if _should_send_default_pii():
6367
with _internal_exceptions():

sentry_sdk/integrations/flask.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def _capture_exception(sender, exception, **kwargs):
4646
def _make_event_processor():
4747
request = getattr(_request_ctx_stack.top, "request", None)
4848
app = getattr(_app_ctx_stack.top, "app", None)
49+
client_options = get_current_hub().client.options
4950

5051
def event_processor(event):
5152
if request:
@@ -54,7 +55,7 @@ def event_processor(event):
5455
event["transaction"] = request.url_rule.endpoint
5556

5657
with _internal_exceptions():
57-
FlaskRequestExtractor(request).extract_into_event(event)
58+
FlaskRequestExtractor(request).extract_into_event(event, client_options)
5859

5960
if _should_send_default_pii():
6061
with _internal_exceptions():

tests/integrations/flask/test_flask.py

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,9 @@ def index():
182182
"": {"len": 2000, "rem": [["!len", "x", 509, 512]]}
183183
}
184184
assert len(event["request"]["data"]["foo"]["bar"]) == 512
185-
assert event["request"]["data_info"] == {"ct": "json", "repr": "structured"}
186185

187186

188-
def test_flask_large_formdata_request(sentry_init, capture_events, app):
187+
def test_flask_medium_formdata_request(sentry_init, capture_events, app):
189188
sentry_init(integrations=[flask_sentry.FlaskIntegration()])
190189

191190
data = {"foo": "a" * 2000}
@@ -209,12 +208,11 @@ def index():
209208
"": {"len": 2000, "rem": [["!len", "x", 509, 512]]}
210209
}
211210
assert len(event["request"]["data"]["foo"]) == 512
212-
assert event["request"]["data_info"] == {"ct": "urlencoded", "repr": "structured"}
213211

214212

215213
@pytest.mark.parametrize("input_char", [u"a", b"a"])
216-
def test_flask_large_text_request(sentry_init, input_char, capture_events, app):
217-
sentry_init(integrations=[flask_sentry.FlaskIntegration()])
214+
def test_flask_too_large_raw_request(sentry_init, input_char, capture_events, app):
215+
sentry_init(integrations=[flask_sentry.FlaskIntegration()], request_bodies="small")
218216

219217
data = input_char * 2000
220218

@@ -237,41 +235,13 @@ def index():
237235

238236
event, = events
239237
assert event[""]["request"]["data"] == {
240-
"": {"len": 2000, "rem": [["!len", "x", 509, 512]]}
241-
}
242-
assert len(event["request"]["data"]) == 512
243-
assert event["request"]["data_info"] == {"ct": "plain", "repr": "other"}
244-
245-
246-
def test_flask_large_bytes_request(sentry_init, capture_events, app):
247-
sentry_init(integrations=[flask_sentry.FlaskIntegration()])
248-
249-
data = b"\xc3" * 2000
250-
251-
@app.route("/", methods=["POST"])
252-
def index():
253-
assert not request.form
254-
assert request.data == data
255-
assert not request.json
256-
capture_message("hi")
257-
return "ok"
258-
259-
events = capture_events()
260-
261-
client = app.test_client()
262-
response = client.post("/", data=data)
263-
assert response.status_code == 200
264-
265-
event, = events
266-
assert event[""]["request"]["data"] == {
267-
"": {"len": 2668, "rem": [["!len", "x", 509, 512]]}
238+
"": {"len": 2000, "rem": [["!config", "x", 0, 2000]]}
268239
}
269-
assert len(event["request"]["data"]) == 512
270-
assert event["request"]["data_info"] == {"ct": "bytes", "repr": "base64"}
240+
assert not event["request"]["data"]
271241

272242

273243
def test_flask_files_and_form(sentry_init, capture_events, app):
274-
sentry_init(integrations=[flask_sentry.FlaskIntegration()])
244+
sentry_init(integrations=[flask_sentry.FlaskIntegration()], request_bodies="always")
275245

276246
data = {"foo": "a" * 2000, "file": (BytesIO(b"hello"), "hello.txt")}
277247

0 commit comments

Comments
 (0)