Skip to content

Commit c1d157d

Browse files
fix(integrations): Falcon integration checks response status before reporting error (#2465)
* Falcon checks actual HTTP status before reporting error * Only support custom error handlers on Falcon 3+ * Add Falcon 3.1 to tox.ini This change fixes an issue where the Falcon integration would report an error occurring in a Falcon request handler to Sentry, even though a Falcon custom event handler was handling the exception, causing an HTTP status other than 5xx to be returned. From now on, Falcon will inspect the HTTP status on the response before sending the associated error event to Sentry, and the error will only be reported if the response status is a 5xx status. Fixes GH-#1362
1 parent 39e3556 commit c1d157d

File tree

3 files changed

+68
-8
lines changed

3 files changed

+68
-8
lines changed

sentry_sdk/integrations/falcon.py

+29-8
Original file line numberDiff line numberDiff line change
@@ -175,17 +175,25 @@ def sentry_patched_handle_exception(self, *args):
175175
# NOTE(jmagnusson): falcon 2.0 changed falcon.API._handle_exception
176176
# method signature from `(ex, req, resp, params)` to
177177
# `(req, resp, ex, params)`
178-
if isinstance(args[0], Exception):
179-
ex = args[0]
180-
else:
181-
ex = args[2]
178+
ex = response = None
179+
with capture_internal_exceptions():
180+
ex = next(argument for argument in args if isinstance(argument, Exception))
181+
response = next(
182+
argument for argument in args if isinstance(argument, falcon.Response)
183+
)
182184

183185
was_handled = original_handle_exception(self, *args)
184186

187+
if ex is None or response is None:
188+
# Both ex and response should have a non-None value at this point; otherwise,
189+
# there is an error with the SDK that will have been captured in the
190+
# capture_internal_exceptions block above.
191+
return was_handled
192+
185193
hub = Hub.current
186194
integration = hub.get_integration(FalconIntegration)
187195

188-
if integration is not None and _exception_leads_to_http_5xx(ex):
196+
if integration is not None and _exception_leads_to_http_5xx(ex, response):
189197
# If an integration is there, a client has to be there.
190198
client = hub.client # type: Any
191199

@@ -225,15 +233,28 @@ def sentry_patched_prepare_middleware(
225233
falcon_helpers.prepare_middleware = sentry_patched_prepare_middleware
226234

227235

228-
def _exception_leads_to_http_5xx(ex):
229-
# type: (Exception) -> bool
236+
def _exception_leads_to_http_5xx(ex, response):
237+
# type: (Exception, falcon.Response) -> bool
230238
is_server_error = isinstance(ex, falcon.HTTPError) and (ex.status or "").startswith(
231239
"5"
232240
)
233241
is_unhandled_error = not isinstance(
234242
ex, (falcon.HTTPError, falcon.http_status.HTTPStatus)
235243
)
236-
return is_server_error or is_unhandled_error
244+
245+
# We only check the HTTP status on Falcon 3 because in Falcon 2, the status on the response
246+
# at the stage where we capture it is listed as 200, even though we would expect to see a 500
247+
# status. Since at the time of this change, Falcon 2 is ca. 4 years old, we have decided to
248+
# only perform this check on Falcon 3+, despite the risk that some handled errors might be
249+
# reported to Sentry as unhandled on Falcon 2.
250+
return (is_server_error or is_unhandled_error) and (
251+
not FALCON3 or _has_http_5xx_status(response)
252+
)
253+
254+
255+
def _has_http_5xx_status(response):
256+
# type: (falcon.Response) -> bool
257+
return response.status.startswith("5")
237258

238259

239260
def _set_transaction_name_and_source(event, transaction_style, request):

tests/integrations/falcon/test_falcon.py

+37
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import sentry_sdk
1010
from sentry_sdk.integrations.falcon import FalconIntegration
1111
from sentry_sdk.integrations.logging import LoggingIntegration
12+
from sentry_sdk.utils import parse_version
1213

1314

1415
try:
@@ -19,6 +20,9 @@
1920
import falcon.inspect # We only need this module for the ASGI test
2021

2122

23+
FALCON_VERSION = parse_version(falcon.__version__)
24+
25+
2226
@pytest.fixture
2327
def make_app(sentry_init):
2428
def inner():
@@ -32,9 +36,22 @@ def on_get(self, req, resp, message_id):
3236
sentry_sdk.capture_message("hi")
3337
resp.media = "hi"
3438

39+
class CustomError(Exception):
40+
pass
41+
42+
class CustomErrorResource:
43+
def on_get(self, req, resp):
44+
raise CustomError()
45+
46+
def custom_error_handler(*args, **kwargs):
47+
raise falcon.HTTPError(status=falcon.HTTP_400)
48+
3549
app = falcon.API()
3650
app.add_route("/message", MessageResource())
3751
app.add_route("/message/{message_id:int}", MessageByIdResource())
52+
app.add_route("/custom-error", CustomErrorResource())
53+
54+
app.add_error_handler(CustomError, custom_error_handler)
3855

3956
return app
4057

@@ -418,3 +435,23 @@ def test_falcon_not_breaking_asgi(sentry_init):
418435
falcon.inspect.inspect_app(asgi_app)
419436
except TypeError:
420437
pytest.fail("Falcon integration causing errors in ASGI apps.")
438+
439+
440+
@pytest.mark.skipif(
441+
(FALCON_VERSION or ()) < (3,),
442+
reason="The Sentry Falcon integration only supports custom error handlers on Falcon 3+",
443+
)
444+
def test_falcon_custom_error_handler(sentry_init, make_app, capture_events):
445+
"""
446+
When a custom error handler handles what otherwise would have resulted in a 5xx error,
447+
changing the HTTP status to a non-5xx status, no error event should be sent to Sentry.
448+
"""
449+
sentry_init(integrations=[FalconIntegration()])
450+
events = capture_events()
451+
452+
app = make_app()
453+
client = falcon.testing.TestClient(app)
454+
455+
client.simulate_get("/custom-error")
456+
457+
assert len(events) == 0

tox.ini

+2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ envlist =
8181
{py2.7,py3.5,py3.6,py3.7}-falcon-v{1.4}
8282
{py2.7,py3.5,py3.6,py3.7}-falcon-v{2.0}
8383
{py3.5,py3.6,py3.7,py3.8,py3.9,py3.10,py3.11}-falcon-v{3.0}
84+
{py3.7,py3.8,py3.9,py3.10,py3.11}-falcon-v{3.1}
8485

8586
# FastAPI
8687
{py3.7,py3.8,py3.9,py3.10,py3.11}-fastapi
@@ -312,6 +313,7 @@ deps =
312313
falcon-v1.4: falcon>=1.4,<1.5
313314
falcon-v2.0: falcon>=2.0.0rc3,<3.0
314315
falcon-v3.0: falcon>=3.0.0,<3.1.0
316+
falcon-v3.1: falcon>=3.1.0,<3.2
315317

316318
# FastAPI
317319
fastapi: fastapi

0 commit comments

Comments
 (0)