11import logging
2+ from http import HTTPStatus
23
34import falcon
45import falcon .testing
89from sentry_sdk .consts import SPANDATA
910from sentry_sdk .integrations .falcon import FalconIntegration
1011from sentry_sdk .integrations .logging import LoggingIntegration
12+ from sentry_sdk .utils import parse_version
1113
1214try :
1315 import falcon .asgi
1719 import falcon .inspect # We only need this module for the ASGI test
1820
1921
22+ FALCON_VERSION = parse_version (falcon .__version__ )
23+
24+
2025@pytest .fixture
2126def make_app (sentry_init ):
2227 def inner ():
@@ -178,18 +183,41 @@ def on_get(self, req, resp):
178183 assert " by zero" in event ["exception" ]["values" ][0 ]["value" ]
179184
180185
186+ @pytest .mark .parametrize (
187+ "status" ,
188+ [
189+ falcon .HTTP_502 ,
190+ pytest .param (
191+ 502 ,
192+ marks = pytest .mark .skipif (
193+ (FALCON_VERSION or ()) < (4 ,),
194+ reason = "Integer HTTPError statuses require Falcon 4+" ,
195+ ),
196+ ),
197+ pytest .param (
198+ HTTPStatus .BAD_GATEWAY ,
199+ id = "HTTPStatus.BAD_GATEWAY" ,
200+ marks = pytest .mark .skipif (
201+ (FALCON_VERSION or ()) < (4 ,),
202+ reason = "Integer HTTPError statuses require Falcon 4+" ,
203+ ),
204+ ),
205+ ],
206+ )
181207def test_raised_5xx_errors (
182208 sentry_init ,
183209 capture_exceptions ,
184210 capture_items ,
211+ status ,
185212):
213+ """String, integer, and http.HTTPStatus 5xx HTTPError statuses should be captured."""
186214 sentry_init (
187215 integrations = [FalconIntegration ()],
188216 )
189217
190218 class Resource :
191219 def on_get (self , req , resp ):
192- raise falcon .HTTPError (falcon . HTTP_502 )
220+ raise falcon .HTTPError (status )
193221
194222 app = falcon .API ()
195223 app .add_route ("/" , Resource ())
@@ -210,18 +238,41 @@ def on_get(self, req, resp):
210238 assert event ["exception" ]["values" ][0 ]["type" ] == "HTTPError"
211239
212240
241+ @pytest .mark .parametrize (
242+ "status" ,
243+ [
244+ falcon .HTTP_400 ,
245+ pytest .param (
246+ 400 ,
247+ marks = pytest .mark .skipif (
248+ (FALCON_VERSION or ()) < (4 ,),
249+ reason = "Integer HTTPError statuses require Falcon 4+" ,
250+ ),
251+ ),
252+ pytest .param (
253+ HTTPStatus .BAD_REQUEST ,
254+ id = "HTTPStatus.BAD_REQUEST" ,
255+ marks = pytest .mark .skipif (
256+ (FALCON_VERSION or ()) < (4 ,),
257+ reason = "Integer HTTPError statuses require Falcon 4+" ,
258+ ),
259+ ),
260+ ],
261+ )
213262def test_raised_4xx_errors (
214263 sentry_init ,
215264 capture_exceptions ,
216265 capture_items ,
266+ status ,
217267):
268+ """String, integer, and http.HTTPStatus 4xx HTTPError statuses should not be captured."""
218269 sentry_init (
219270 integrations = [FalconIntegration ()],
220271 )
221272
222273 class Resource :
223274 def on_get (self , req , resp ):
224- raise falcon .HTTPError (falcon . HTTP_400 )
275+ raise falcon .HTTPError (status )
225276
226277 app = falcon .API ()
227278 app .add_route ("/" , Resource ())
@@ -236,22 +287,41 @@ def on_get(self, req, resp):
236287 assert len (events ) == 0
237288
238289
290+ @pytest .mark .parametrize (
291+ "status" ,
292+ [
293+ falcon .HTTP_508 ,
294+ pytest .param (
295+ 508 ,
296+ marks = pytest .mark .skipif (
297+ (FALCON_VERSION or ()) < (4 ,),
298+ reason = "Integer HTTPStatus statuses require Falcon 4+" ,
299+ ),
300+ ),
301+ pytest .param (
302+ HTTPStatus .LOOP_DETECTED ,
303+ id = "HTTPStatus.LOOP_DETECTED" ,
304+ marks = pytest .mark .skipif (
305+ (FALCON_VERSION or ()) < (4 ,),
306+ reason = "Integer HTTPStatus statuses require Falcon 4+" ,
307+ ),
308+ ),
309+ ],
310+ )
239311def test_http_status (
240312 sentry_init ,
241313 capture_exceptions ,
242314 capture_items ,
315+ status ,
243316):
244- """
245- This just demonstrates, that if Falcon raises a HTTPStatus with code 500
246- (instead of a HTTPError with code 500) Sentry will not capture it.
247- """
317+ """String, integer, and http.HTTPStatus 5xx falcon.HTTPStatus exceptions are not captured."""
248318 sentry_init (
249319 integrations = [FalconIntegration ()],
250320 )
251321
252322 class Resource :
253323 def on_get (self , req , resp ):
254- raise falcon .http_status .HTTPStatus (falcon . HTTP_508 )
324+ raise falcon .http_status .HTTPStatus (status )
255325
256326 app = falcon .API ()
257327 app .add_route ("/" , Resource ())
@@ -267,6 +337,53 @@ def on_get(self, req, resp):
267337 assert len (events ) == 0
268338
269339
340+ @pytest .mark .parametrize (
341+ "status" ,
342+ [
343+ 400 ,
344+ pytest .param (HTTPStatus .BAD_REQUEST , id = "HTTPStatus.BAD_REQUEST" ),
345+ 499 ,
346+ 500 ,
347+ pytest .param (
348+ HTTPStatus .INTERNAL_SERVER_ERROR , id = "HTTPStatus.INTERNAL_SERVER_ERROR"
349+ ),
350+ 599 ,
351+ 600 ,
352+ ],
353+ )
354+ def test_integer_response_status (sentry_init , capture_items , status ):
355+ """A handler's integer or HTTPStatus response status should capture only 5xx errors."""
356+ sentry_init (
357+ integrations = [FalconIntegration ()],
358+ )
359+
360+ class Resource :
361+ def on_get (self , req , resp ):
362+ raise ValueError ("handled error" )
363+
364+ def error_handler (req , resp , ex , params ):
365+ resp .status = status
366+
367+ app = falcon .API ()
368+ app .add_route ("/" , Resource ())
369+ app .add_error_handler (ValueError , error_handler )
370+ items = capture_items ("event" )
371+
372+ response = falcon .testing .TestClient (app ).simulate_get ("/" )
373+
374+ # this exists in falcon >= 3
375+ assert response .status_code == status
376+ if 500 <= status < 600 :
377+ (event ,) = (item .payload for item in items )
378+ exception = event ["exception" ]["values" ][- 1 ]
379+ assert exception ["type" ] == "ValueError"
380+ assert exception ["value" ] == "handled error"
381+ assert exception ["mechanism" ]["type" ] == "falcon"
382+ assert exception ["mechanism" ]["handled" ] is False
383+ else :
384+ assert not items
385+
386+
270387@pytest .mark .parametrize ("max_value_length" , [1024 , None ])
271388def test_falcon_large_json_request (
272389 sentry_init ,
0 commit comments