Repository: getsentry/sentry-python

Issue:
Tornado integration does not capture formdata
There is no obvious way to get just formdata from Tornado's request object (not mixed with other request params), but I haven't done a lot of research on that.


Discussion:
I think this is the function you want:

https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.get_body_arguments
@nickbaum amazing. I thought this would parse JSON as well.

Resolving patch:
diff --git a/sentry_sdk/integrations/tornado.py b/sentry_sdk/integrations/tornado.py
--- a/sentry_sdk/integrations/tornado.py
+++ b/sentry_sdk/integrations/tornado.py
@@ -173,8 +173,10 @@ def raw_data(self):
 
     def form(self):
         # type: () -> Optional[Any]
-        # TODO: Where to get formdata and nothing else?
-        return None
+        return {
+            k: [v.decode("latin1", "replace") for v in vs]
+            for k, vs in self.request.body_arguments.items()
+        }
 
     def is_json(self):
         # type: () -> bool


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -16,16 +16,20 @@
 
 @pytest.fixture(autouse=True)
 def reraise_internal_exceptions(request, monkeypatch):
+    errors = []
     if "tests_internal_exceptions" in request.keywords:
         return
 
-    def _capture_internal_exception(exc_info):
-        reraise(*exc_info)
+    def _capture_internal_exception(self, exc_info):
+        errors.append(exc_info)
+
+    @request.addfinalizer
+    def _():
+        for e in errors:
+            reraise(*e)
 
     monkeypatch.setattr(
-        sentry_sdk.Hub.current,
-        "_capture_internal_exception",
-        _capture_internal_exception,
+        sentry_sdk.Hub, "_capture_internal_exception", _capture_internal_exception
     )
 
 
diff --git a/tests/integrations/tornado/test_tornado.py b/tests/integrations/tornado/test_tornado.py
--- a/tests/integrations/tornado/test_tornado.py
+++ b/tests/integrations/tornado/test_tornado.py
@@ -1,7 +1,8 @@
+import json
+
 import pytest
 
 from sentry_sdk import configure_scope
-
 from sentry_sdk.integrations.tornado import TornadoIntegration
 
 from tornado.web import RequestHandler, Application, HTTPError
@@ -136,3 +137,56 @@ def get(self):
     assert exception["type"] == "ZeroDivisionError"
 
     assert "user" not in event
+
+
+def test_formdata(tornado_testcase, sentry_init, capture_events):
+    sentry_init(integrations=[TornadoIntegration()], send_default_pii=True)
+    events = capture_events()
+
+    class FormdataHandler(RequestHandler):
+        def post(self):
+            raise ValueError(json.dumps(sorted(self.request.body_arguments)))
+
+    client = tornado_testcase(Application([(r"/form", FormdataHandler)]))
+
+    response = client.fetch(
+        "/form?queryarg=1",
+        method="POST",
+        headers={"Content-Type": "application/x-www-form-urlencoded"},
+        body=b"field1=value1&field2=value2",
+    )
+
+    assert response.code == 500
+
+    event, = events
+    exception, = event["exception"]["values"]
+    assert exception["value"] == '["field1", "field2"]'
+    assert event["request"]["data"] == {"field1": ["value1"], "field2": ["value2"]}
+
+
+def test_json(tornado_testcase, sentry_init, capture_events):
+    sentry_init(integrations=[TornadoIntegration()], send_default_pii=True)
+    events = capture_events()
+
+    class FormdataHandler(RequestHandler):
+        def post(self):
+            raise ValueError(json.dumps(sorted(self.request.body_arguments)))
+
+    client = tornado_testcase(Application([(r"/form", FormdataHandler)]))
+
+    response = client.fetch(
+        "/form?queryarg=1",
+        method="POST",
+        headers={"Content-Type": "application/json"},
+        body=b"""
+        {"foo": {"bar": 42}}
+        """,
+    )
+
+    assert response.code == 500
+
+    event, = events
+    exception, = event["exception"]["values"]
+    assert exception["value"] == "[]"
+    assert event
+    assert event["request"]["data"] == {"foo": {"bar": 42}}