Repository: getsentry/sentry-python

Issue:
Falcon sentry integration catch all http errors
Hi,
seems that the falcon integration catches all the http errors and not just the 500 http error code as the documentation says, https://docs.sentry.io/platforms/python/falcon/#behavior

https://github.com/getsentry/sentry-python/blob/7d482b5bfa1d4f58eb090818496eba8fee8e63aa/sentry_sdk/integrations/falcon.py#L156


Discussion:

Resolving patch:
diff --git a/sentry_sdk/integrations/falcon.py b/sentry_sdk/integrations/falcon.py
--- a/sentry_sdk/integrations/falcon.py
+++ b/sentry_sdk/integrations/falcon.py
@@ -153,7 +153,7 @@ def sentry_patched_handle_exception(self, *args):
         hub = Hub.current
         integration = hub.get_integration(FalconIntegration)
 
-        if integration is not None and not _is_falcon_http_error(ex):
+        if integration is not None and _exception_leads_to_http_5xx(ex):
             # If an integration is there, a client has to be there.
             client = hub.client  # type: Any
 
@@ -186,9 +186,15 @@ def sentry_patched_prepare_middleware(
     falcon.api_helpers.prepare_middleware = sentry_patched_prepare_middleware
 
 
-def _is_falcon_http_error(ex):
-    # type: (BaseException) -> bool
-    return isinstance(ex, (falcon.HTTPError, falcon.http_status.HTTPStatus))
+def _exception_leads_to_http_5xx(ex):
+    # type: (Exception) -> bool
+    is_server_error = isinstance(ex, falcon.HTTPError) and (ex.status or "").startswith(
+        "5"
+    )
+    is_unhandled_error = not isinstance(
+        ex, (falcon.HTTPError, falcon.http_status.HTTPStatus)
+    )
+    return is_server_error or is_unhandled_error
 
 
 def _make_request_event_processor(req, integration):


Test patch:
diff --git a/tests/integrations/falcon/test_falcon.py b/tests/integrations/falcon/test_falcon.py
--- a/tests/integrations/falcon/test_falcon.py
+++ b/tests/integrations/falcon/test_falcon.py
@@ -71,15 +71,15 @@ def test_transaction_style(
     assert event["transaction"] == expected_transaction
 
 
-def test_errors(sentry_init, capture_exceptions, capture_events):
+def test_unhandled_errors(sentry_init, capture_exceptions, capture_events):
     sentry_init(integrations=[FalconIntegration()], debug=True)
 
-    class ZeroDivisionErrorResource:
+    class Resource:
         def on_get(self, req, resp):
             1 / 0
 
     app = falcon.API()
-    app.add_route("/", ZeroDivisionErrorResource())
+    app.add_route("/", Resource())
 
     exceptions = capture_exceptions()
     events = capture_events()
@@ -96,6 +96,75 @@ def on_get(self, req, resp):
 
     (event,) = events
     assert event["exception"]["values"][0]["mechanism"]["type"] == "falcon"
+    assert " by zero" in event["exception"]["values"][0]["value"]
+
+
+def test_raised_5xx_errors(sentry_init, capture_exceptions, capture_events):
+    sentry_init(integrations=[FalconIntegration()], debug=True)
+
+    class Resource:
+        def on_get(self, req, resp):
+            raise falcon.HTTPError(falcon.HTTP_502)
+
+    app = falcon.API()
+    app.add_route("/", Resource())
+
+    exceptions = capture_exceptions()
+    events = capture_events()
+
+    client = falcon.testing.TestClient(app)
+    client.simulate_get("/")
+
+    (exc,) = exceptions
+    assert isinstance(exc, falcon.HTTPError)
+
+    (event,) = events
+    assert event["exception"]["values"][0]["mechanism"]["type"] == "falcon"
+    assert event["exception"]["values"][0]["type"] == "HTTPError"
+
+
+def test_raised_4xx_errors(sentry_init, capture_exceptions, capture_events):
+    sentry_init(integrations=[FalconIntegration()], debug=True)
+
+    class Resource:
+        def on_get(self, req, resp):
+            raise falcon.HTTPError(falcon.HTTP_400)
+
+    app = falcon.API()
+    app.add_route("/", Resource())
+
+    exceptions = capture_exceptions()
+    events = capture_events()
+
+    client = falcon.testing.TestClient(app)
+    client.simulate_get("/")
+
+    assert len(exceptions) == 0
+    assert len(events) == 0
+
+
+def test_http_status(sentry_init, capture_exceptions, capture_events):
+    """
+    This just demonstrates, that if Falcon raises a HTTPStatus with code 500
+    (instead of a HTTPError with code 500) Sentry will not capture it.
+    """
+    sentry_init(integrations=[FalconIntegration()], debug=True)
+
+    class Resource:
+        def on_get(self, req, resp):
+            raise falcon.http_status.HTTPStatus(falcon.HTTP_508)
+
+    app = falcon.API()
+    app.add_route("/", Resource())
+
+    exceptions = capture_exceptions()
+    events = capture_events()
+
+    client = falcon.testing.TestClient(app)
+    client.simulate_get("/")
+
+    assert len(exceptions) == 0
+    assert len(events) == 0
 
 
 def test_falcon_large_json_request(sentry_init, capture_events):