We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Falcon integration does not respect custom exception handlers
### How do you use Sentry?

Sentry Saas (sentry.io)

### Version

1.5.6

### Steps to Reproduce

1. Attach a custom exception handler:
```
import marshmallow

def marshmallow_validation_error_handler(req, res, exc, params):
    """Transforms a marshmallow validation error into the appropriate HTTP response."""
    raise falcon.HTTPError(400)

api.add_error_handler(marshmallow.ValidationError, marshmallow_validation_error_handler)
```

2. Do an API request that leads to a validation error

### Expected Result

The `marshmallow` exception should not have been reported to Sentry.

### Actual Result

The custom exception leads to a 4xx, not a 5xx, yet it is still reported as an unhandled error on Sentry.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `sentry_sdk/integrations/falcon.py`
Content:
```
1 from __future__ import absolute_import
2 
3 from sentry_sdk.hub import Hub
4 from sentry_sdk.integrations import Integration, DidNotEnable
5 from sentry_sdk.integrations._wsgi_common import RequestExtractor
6 from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
7 from sentry_sdk.tracing import SOURCE_FOR_STYLE
8 from sentry_sdk.utils import (
9     capture_internal_exceptions,
10     event_from_exception,
11     parse_version,
12 )
13 
14 from sentry_sdk._types import TYPE_CHECKING
15 
16 if TYPE_CHECKING:
17     from typing import Any
18     from typing import Dict
19     from typing import Optional
20 
21     from sentry_sdk._types import EventProcessor
22 
23 # In Falcon 3.0 `falcon.api_helpers` is renamed to `falcon.app_helpers`
24 # and `falcon.API` to `falcon.App`
25 
26 try:
27     import falcon  # type: ignore
28 
29     from falcon import __version__ as FALCON_VERSION
30 except ImportError:
31     raise DidNotEnable("Falcon not installed")
32 
33 try:
34     import falcon.app_helpers  # type: ignore
35 
36     falcon_helpers = falcon.app_helpers
37     falcon_app_class = falcon.App
38     FALCON3 = True
39 except ImportError:
40     import falcon.api_helpers  # type: ignore
41 
42     falcon_helpers = falcon.api_helpers
43     falcon_app_class = falcon.API
44     FALCON3 = False
45 
46 
47 class FalconRequestExtractor(RequestExtractor):
48     def env(self):
49         # type: () -> Dict[str, Any]
50         return self.request.env
51 
52     def cookies(self):
53         # type: () -> Dict[str, Any]
54         return self.request.cookies
55 
56     def form(self):
57         # type: () -> None
58         return None  # No such concept in Falcon
59 
60     def files(self):
61         # type: () -> None
62         return None  # No such concept in Falcon
63 
64     def raw_data(self):
65         # type: () -> Optional[str]
66 
67         # As request data can only be read once we won't make this available
68         # to Sentry. Just send back a dummy string in case there was a
69         # content length.
70         # TODO(jmagnusson): Figure out if there's a way to support this
71         content_length = self.content_length()
72         if content_length > 0:
73             return "[REQUEST_CONTAINING_RAW_DATA]"
74         else:
75             return None
76 
77     if FALCON3:
78 
79         def json(self):
80             # type: () -> Optional[Dict[str, Any]]
81             try:
82                 return self.request.media
83             except falcon.errors.HTTPBadRequest:
84                 return None
85 
86     else:
87 
88         def json(self):
89             # type: () -> Optional[Dict[str, Any]]
90             try:
91                 return self.request.media
92             except falcon.errors.HTTPBadRequest:
93                 # NOTE(jmagnusson): We return `falcon.Request._media` here because
94                 # falcon 1.4 doesn't do proper type checking in
95                 # `falcon.Request.media`. This has been fixed in 2.0.
96                 # Relevant code: https://github.com/falconry/falcon/blob/1.4.1/falcon/request.py#L953
97                 return self.request._media
98 
99 
100 class SentryFalconMiddleware(object):
101     """Captures exceptions in Falcon requests and send to Sentry"""
102 
103     def process_request(self, req, resp, *args, **kwargs):
104         # type: (Any, Any, *Any, **Any) -> None
105         hub = Hub.current
106         integration = hub.get_integration(FalconIntegration)
107         if integration is None:
108             return
109 
110         with hub.configure_scope() as scope:
111             scope._name = "falcon"
112             scope.add_event_processor(_make_request_event_processor(req, integration))
113 
114 
115 TRANSACTION_STYLE_VALUES = ("uri_template", "path")
116 
117 
118 class FalconIntegration(Integration):
119     identifier = "falcon"
120 
121     transaction_style = ""
122 
123     def __init__(self, transaction_style="uri_template"):
124         # type: (str) -> None
125         if transaction_style not in TRANSACTION_STYLE_VALUES:
126             raise ValueError(
127                 "Invalid value for transaction_style: %s (must be in %s)"
128                 % (transaction_style, TRANSACTION_STYLE_VALUES)
129             )
130         self.transaction_style = transaction_style
131 
132     @staticmethod
133     def setup_once():
134         # type: () -> None
135 
136         version = parse_version(FALCON_VERSION)
137 
138         if version is None:
139             raise DidNotEnable("Unparsable Falcon version: {}".format(FALCON_VERSION))
140 
141         if version < (1, 4):
142             raise DidNotEnable("Falcon 1.4 or newer required.")
143 
144         _patch_wsgi_app()
145         _patch_handle_exception()
146         _patch_prepare_middleware()
147 
148 
149 def _patch_wsgi_app():
150     # type: () -> None
151     original_wsgi_app = falcon_app_class.__call__
152 
153     def sentry_patched_wsgi_app(self, env, start_response):
154         # type: (falcon.API, Any, Any) -> Any
155         hub = Hub.current
156         integration = hub.get_integration(FalconIntegration)
157         if integration is None:
158             return original_wsgi_app(self, env, start_response)
159 
160         sentry_wrapped = SentryWsgiMiddleware(
161             lambda envi, start_resp: original_wsgi_app(self, envi, start_resp)
162         )
163 
164         return sentry_wrapped(env, start_response)
165 
166     falcon_app_class.__call__ = sentry_patched_wsgi_app
167 
168 
169 def _patch_handle_exception():
170     # type: () -> None
171     original_handle_exception = falcon_app_class._handle_exception
172 
173     def sentry_patched_handle_exception(self, *args):
174         # type: (falcon.API, *Any) -> Any
175         # NOTE(jmagnusson): falcon 2.0 changed falcon.API._handle_exception
176         # method signature from `(ex, req, resp, params)` to
177         # `(req, resp, ex, params)`
178         if isinstance(args[0], Exception):
179             ex = args[0]
180         else:
181             ex = args[2]
182 
183         was_handled = original_handle_exception(self, *args)
184 
185         hub = Hub.current
186         integration = hub.get_integration(FalconIntegration)
187 
188         if integration is not None and _exception_leads_to_http_5xx(ex):
189             # If an integration is there, a client has to be there.
190             client = hub.client  # type: Any
191 
192             event, hint = event_from_exception(
193                 ex,
194                 client_options=client.options,
195                 mechanism={"type": "falcon", "handled": False},
196             )
197             hub.capture_event(event, hint=hint)
198 
199         return was_handled
200 
201     falcon_app_class._handle_exception = sentry_patched_handle_exception
202 
203 
204 def _patch_prepare_middleware():
205     # type: () -> None
206     original_prepare_middleware = falcon_helpers.prepare_middleware
207 
208     def sentry_patched_prepare_middleware(
209         middleware=None, independent_middleware=False, asgi=False
210     ):
211         # type: (Any, Any, bool) -> Any
212         if asgi:
213             # We don't support ASGI Falcon apps, so we don't patch anything here
214             return original_prepare_middleware(middleware, independent_middleware, asgi)
215 
216         hub = Hub.current
217         integration = hub.get_integration(FalconIntegration)
218         if integration is not None:
219             middleware = [SentryFalconMiddleware()] + (middleware or [])
220 
221         # We intentionally omit the asgi argument here, since the default is False anyways,
222         # and this way, we remain backwards-compatible with pre-3.0.0 Falcon versions.
223         return original_prepare_middleware(middleware, independent_middleware)
224 
225     falcon_helpers.prepare_middleware = sentry_patched_prepare_middleware
226 
227 
228 def _exception_leads_to_http_5xx(ex):
229     # type: (Exception) -> bool
230     is_server_error = isinstance(ex, falcon.HTTPError) and (ex.status or "").startswith(
231         "5"
232     )
233     is_unhandled_error = not isinstance(
234         ex, (falcon.HTTPError, falcon.http_status.HTTPStatus)
235     )
236     return is_server_error or is_unhandled_error
237 
238 
239 def _set_transaction_name_and_source(event, transaction_style, request):
240     # type: (Dict[str, Any], str, falcon.Request) -> None
241     name_for_style = {
242         "uri_template": request.uri_template,
243         "path": request.path,
244     }
245     event["transaction"] = name_for_style[transaction_style]
246     event["transaction_info"] = {"source": SOURCE_FOR_STYLE[transaction_style]}
247 
248 
249 def _make_request_event_processor(req, integration):
250     # type: (falcon.Request, FalconIntegration) -> EventProcessor
251 
252     def event_processor(event, hint):
253         # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
254         _set_transaction_name_and_source(event, integration.transaction_style, req)
255 
256         with capture_internal_exceptions():
257             FalconRequestExtractor(req).extract_into_event(event)
258 
259         return event
260 
261     return event_processor
262
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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
@@ -175,17 +175,25 @@
         # NOTE(jmagnusson): falcon 2.0 changed falcon.API._handle_exception
         # method signature from `(ex, req, resp, params)` to
         # `(req, resp, ex, params)`
-        if isinstance(args[0], Exception):
-            ex = args[0]
-        else:
-            ex = args[2]
+        ex = response = None
+        with capture_internal_exceptions():
+            ex = next(argument for argument in args if isinstance(argument, Exception))
+            response = next(
+                argument for argument in args if isinstance(argument, falcon.Response)
+            )
 
         was_handled = original_handle_exception(self, *args)
 
+        if ex is None or response is None:
+            # Both ex and response should have a non-None value at this point; otherwise,
+            # there is an error with the SDK that will have been captured in the
+            # capture_internal_exceptions block above.
+            return was_handled
+
         hub = Hub.current
         integration = hub.get_integration(FalconIntegration)
 
-        if integration is not None and _exception_leads_to_http_5xx(ex):
+        if integration is not None and _exception_leads_to_http_5xx(ex, response):
             # If an integration is there, a client has to be there.
             client = hub.client  # type: Any
 
@@ -225,15 +233,28 @@
     falcon_helpers.prepare_middleware = sentry_patched_prepare_middleware
 
 
-def _exception_leads_to_http_5xx(ex):
-    # type: (Exception) -> bool
+def _exception_leads_to_http_5xx(ex, response):
+    # type: (Exception, falcon.Response) -> 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
+
+    # We only check the HTTP status on Falcon 3 because in Falcon 2, the status on the response
+    # at the stage where we capture it is listed as 200, even though we would expect to see a 500
+    # status. Since at the time of this change, Falcon 2 is ca. 4 years old, we have decided to
+    # only perform this check on Falcon 3+, despite the risk that some handled errors might be
+    # reported to Sentry as unhandled on Falcon 2.
+    return (is_server_error or is_unhandled_error) and (
+        not FALCON3 or _has_http_5xx_status(response)
+    )
+
+
+def _has_http_5xx_status(response):
+    # type: (falcon.Response) -> bool
+    return response.status.startswith("5")
 
 
 def _set_transaction_name_and_source(event, transaction_style, request):

{"golden_diff": "diff --git a/sentry_sdk/integrations/falcon.py b/sentry_sdk/integrations/falcon.py\n--- a/sentry_sdk/integrations/falcon.py\n+++ b/sentry_sdk/integrations/falcon.py\n@@ -175,17 +175,25 @@\n         # NOTE(jmagnusson): falcon 2.0 changed falcon.API._handle_exception\n         # method signature from `(ex, req, resp, params)` to\n         # `(req, resp, ex, params)`\n-        if isinstance(args[0], Exception):\n-            ex = args[0]\n-        else:\n-            ex = args[2]\n+        ex = response = None\n+        with capture_internal_exceptions():\n+            ex = next(argument for argument in args if isinstance(argument, Exception))\n+            response = next(\n+                argument for argument in args if isinstance(argument, falcon.Response)\n+            )\n \n         was_handled = original_handle_exception(self, *args)\n \n+        if ex is None or response is None:\n+            # Both ex and response should have a non-None value at this point; otherwise,\n+            # there is an error with the SDK that will have been captured in the\n+            # capture_internal_exceptions block above.\n+            return was_handled\n+\n         hub = Hub.current\n         integration = hub.get_integration(FalconIntegration)\n \n-        if integration is not None and _exception_leads_to_http_5xx(ex):\n+        if integration is not None and _exception_leads_to_http_5xx(ex, response):\n             # If an integration is there, a client has to be there.\n             client = hub.client  # type: Any\n \n@@ -225,15 +233,28 @@\n     falcon_helpers.prepare_middleware = sentry_patched_prepare_middleware\n \n \n-def _exception_leads_to_http_5xx(ex):\n-    # type: (Exception) -> bool\n+def _exception_leads_to_http_5xx(ex, response):\n+    # type: (Exception, falcon.Response) -> bool\n     is_server_error = isinstance(ex, falcon.HTTPError) and (ex.status or \"\").startswith(\n         \"5\"\n     )\n     is_unhandled_error = not isinstance(\n         ex, (falcon.HTTPError, falcon.http_status.HTTPStatus)\n     )\n-    return is_server_error or is_unhandled_error\n+\n+    # We only check the HTTP status on Falcon 3 because in Falcon 2, the status on the response\n+    # at the stage where we capture it is listed as 200, even though we would expect to see a 500\n+    # status. Since at the time of this change, Falcon 2 is ca. 4 years old, we have decided to\n+    # only perform this check on Falcon 3+, despite the risk that some handled errors might be\n+    # reported to Sentry as unhandled on Falcon 2.\n+    return (is_server_error or is_unhandled_error) and (\n+        not FALCON3 or _has_http_5xx_status(response)\n+    )\n+\n+\n+def _has_http_5xx_status(response):\n+    # type: (falcon.Response) -> bool\n+    return response.status.startswith(\"5\")\n \n \n def _set_transaction_name_and_source(event, transaction_style, request):\n", "issue": "Falcon integration does not respect custom exception handlers\n### How do you use Sentry?\n\nSentry Saas (sentry.io)\n\n### Version\n\n1.5.6\n\n### Steps to Reproduce\n\n1. Attach a custom exception handler:\r\n```\r\nimport marshmallow\r\n\r\ndef marshmallow_validation_error_handler(req, res, exc, params):\r\n    \"\"\"Transforms a marshmallow validation error into the appropriate HTTP response.\"\"\"\r\n    raise falcon.HTTPError(400)\r\n\r\napi.add_error_handler(marshmallow.ValidationError, marshmallow_validation_error_handler)\r\n```\r\n\r\n2. Do an API request that leads to a validation error\n\n### Expected Result\n\nThe `marshmallow` exception should not have been reported to Sentry.\n\n### Actual Result\n\nThe custom exception leads to a 4xx, not a 5xx, yet it is still reported as an unhandled error on Sentry.\n", "before_files": [{"content": "from __future__ import absolute_import\n\nfrom sentry_sdk.hub import Hub\nfrom sentry_sdk.integrations import Integration, DidNotEnable\nfrom sentry_sdk.integrations._wsgi_common import RequestExtractor\nfrom sentry_sdk.integrations.wsgi import SentryWsgiMiddleware\nfrom sentry_sdk.tracing import SOURCE_FOR_STYLE\nfrom sentry_sdk.utils import (\n    capture_internal_exceptions,\n    event_from_exception,\n    parse_version,\n)\n\nfrom sentry_sdk._types import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Any\n    from typing import Dict\n    from typing import Optional\n\n    from sentry_sdk._types import EventProcessor\n\n# In Falcon 3.0 `falcon.api_helpers` is renamed to `falcon.app_helpers`\n# and `falcon.API` to `falcon.App`\n\ntry:\n    import falcon  # type: ignore\n\n    from falcon import __version__ as FALCON_VERSION\nexcept ImportError:\n    raise DidNotEnable(\"Falcon not installed\")\n\ntry:\n    import falcon.app_helpers  # type: ignore\n\n    falcon_helpers = falcon.app_helpers\n    falcon_app_class = falcon.App\n    FALCON3 = True\nexcept ImportError:\n    import falcon.api_helpers  # type: ignore\n\n    falcon_helpers = falcon.api_helpers\n    falcon_app_class = falcon.API\n    FALCON3 = False\n\n\nclass FalconRequestExtractor(RequestExtractor):\n    def env(self):\n        # type: () -> Dict[str, Any]\n        return self.request.env\n\n    def cookies(self):\n        # type: () -> Dict[str, Any]\n        return self.request.cookies\n\n    def form(self):\n        # type: () -> None\n        return None  # No such concept in Falcon\n\n    def files(self):\n        # type: () -> None\n        return None  # No such concept in Falcon\n\n    def raw_data(self):\n        # type: () -> Optional[str]\n\n        # As request data can only be read once we won't make this available\n        # to Sentry. Just send back a dummy string in case there was a\n        # content length.\n        # TODO(jmagnusson): Figure out if there's a way to support this\n        content_length = self.content_length()\n        if content_length > 0:\n            return \"[REQUEST_CONTAINING_RAW_DATA]\"\n        else:\n            return None\n\n    if FALCON3:\n\n        def json(self):\n            # type: () -> Optional[Dict[str, Any]]\n            try:\n                return self.request.media\n            except falcon.errors.HTTPBadRequest:\n                return None\n\n    else:\n\n        def json(self):\n            # type: () -> Optional[Dict[str, Any]]\n            try:\n                return self.request.media\n            except falcon.errors.HTTPBadRequest:\n                # NOTE(jmagnusson): We return `falcon.Request._media` here because\n                # falcon 1.4 doesn't do proper type checking in\n                # `falcon.Request.media`. This has been fixed in 2.0.\n                # Relevant code: https://github.com/falconry/falcon/blob/1.4.1/falcon/request.py#L953\n                return self.request._media\n\n\nclass SentryFalconMiddleware(object):\n    \"\"\"Captures exceptions in Falcon requests and send to Sentry\"\"\"\n\n    def process_request(self, req, resp, *args, **kwargs):\n        # type: (Any, Any, *Any, **Any) -> None\n        hub = Hub.current\n        integration = hub.get_integration(FalconIntegration)\n        if integration is None:\n            return\n\n        with hub.configure_scope() as scope:\n            scope._name = \"falcon\"\n            scope.add_event_processor(_make_request_event_processor(req, integration))\n\n\nTRANSACTION_STYLE_VALUES = (\"uri_template\", \"path\")\n\n\nclass FalconIntegration(Integration):\n    identifier = \"falcon\"\n\n    transaction_style = \"\"\n\n    def __init__(self, transaction_style=\"uri_template\"):\n        # type: (str) -> None\n        if transaction_style not in TRANSACTION_STYLE_VALUES:\n            raise ValueError(\n                \"Invalid value for transaction_style: %s (must be in %s)\"\n                % (transaction_style, TRANSACTION_STYLE_VALUES)\n            )\n        self.transaction_style = transaction_style\n\n    @staticmethod\n    def setup_once():\n        # type: () -> None\n\n        version = parse_version(FALCON_VERSION)\n\n        if version is None:\n            raise DidNotEnable(\"Unparsable Falcon version: {}\".format(FALCON_VERSION))\n\n        if version < (1, 4):\n            raise DidNotEnable(\"Falcon 1.4 or newer required.\")\n\n        _patch_wsgi_app()\n        _patch_handle_exception()\n        _patch_prepare_middleware()\n\n\ndef _patch_wsgi_app():\n    # type: () -> None\n    original_wsgi_app = falcon_app_class.__call__\n\n    def sentry_patched_wsgi_app(self, env, start_response):\n        # type: (falcon.API, Any, Any) -> Any\n        hub = Hub.current\n        integration = hub.get_integration(FalconIntegration)\n        if integration is None:\n            return original_wsgi_app(self, env, start_response)\n\n        sentry_wrapped = SentryWsgiMiddleware(\n            lambda envi, start_resp: original_wsgi_app(self, envi, start_resp)\n        )\n\n        return sentry_wrapped(env, start_response)\n\n    falcon_app_class.__call__ = sentry_patched_wsgi_app\n\n\ndef _patch_handle_exception():\n    # type: () -> None\n    original_handle_exception = falcon_app_class._handle_exception\n\n    def sentry_patched_handle_exception(self, *args):\n        # type: (falcon.API, *Any) -> Any\n        # NOTE(jmagnusson): falcon 2.0 changed falcon.API._handle_exception\n        # method signature from `(ex, req, resp, params)` to\n        # `(req, resp, ex, params)`\n        if isinstance(args[0], Exception):\n            ex = args[0]\n        else:\n            ex = args[2]\n\n        was_handled = original_handle_exception(self, *args)\n\n        hub = Hub.current\n        integration = hub.get_integration(FalconIntegration)\n\n        if integration is not None and _exception_leads_to_http_5xx(ex):\n            # If an integration is there, a client has to be there.\n            client = hub.client  # type: Any\n\n            event, hint = event_from_exception(\n                ex,\n                client_options=client.options,\n                mechanism={\"type\": \"falcon\", \"handled\": False},\n            )\n            hub.capture_event(event, hint=hint)\n\n        return was_handled\n\n    falcon_app_class._handle_exception = sentry_patched_handle_exception\n\n\ndef _patch_prepare_middleware():\n    # type: () -> None\n    original_prepare_middleware = falcon_helpers.prepare_middleware\n\n    def sentry_patched_prepare_middleware(\n        middleware=None, independent_middleware=False, asgi=False\n    ):\n        # type: (Any, Any, bool) -> Any\n        if asgi:\n            # We don't support ASGI Falcon apps, so we don't patch anything here\n            return original_prepare_middleware(middleware, independent_middleware, asgi)\n\n        hub = Hub.current\n        integration = hub.get_integration(FalconIntegration)\n        if integration is not None:\n            middleware = [SentryFalconMiddleware()] + (middleware or [])\n\n        # We intentionally omit the asgi argument here, since the default is False anyways,\n        # and this way, we remain backwards-compatible with pre-3.0.0 Falcon versions.\n        return original_prepare_middleware(middleware, independent_middleware)\n\n    falcon_helpers.prepare_middleware = sentry_patched_prepare_middleware\n\n\ndef _exception_leads_to_http_5xx(ex):\n    # type: (Exception) -> bool\n    is_server_error = isinstance(ex, falcon.HTTPError) and (ex.status or \"\").startswith(\n        \"5\"\n    )\n    is_unhandled_error = not isinstance(\n        ex, (falcon.HTTPError, falcon.http_status.HTTPStatus)\n    )\n    return is_server_error or is_unhandled_error\n\n\ndef _set_transaction_name_and_source(event, transaction_style, request):\n    # type: (Dict[str, Any], str, falcon.Request) -> None\n    name_for_style = {\n        \"uri_template\": request.uri_template,\n        \"path\": request.path,\n    }\n    event[\"transaction\"] = name_for_style[transaction_style]\n    event[\"transaction_info\"] = {\"source\": SOURCE_FOR_STYLE[transaction_style]}\n\n\ndef _make_request_event_processor(req, integration):\n    # type: (falcon.Request, FalconIntegration) -> EventProcessor\n\n    def event_processor(event, hint):\n        # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]\n        _set_transaction_name_and_source(event, integration.transaction_style, req)\n\n        with capture_internal_exceptions():\n            FalconRequestExtractor(req).extract_into_event(event)\n\n        return event\n\n    return event_processor\n", "path": "sentry_sdk/integrations/falcon.py"}], "after_files": [{"content": "from __future__ import absolute_import\n\nfrom sentry_sdk.hub import Hub\nfrom sentry_sdk.integrations import Integration, DidNotEnable\nfrom sentry_sdk.integrations._wsgi_common import RequestExtractor\nfrom sentry_sdk.integrations.wsgi import SentryWsgiMiddleware\nfrom sentry_sdk.tracing import SOURCE_FOR_STYLE\nfrom sentry_sdk.utils import (\n    capture_internal_exceptions,\n    event_from_exception,\n    parse_version,\n)\n\nfrom sentry_sdk._types import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Any\n    from typing import Dict\n    from typing import Optional\n\n    from sentry_sdk._types import EventProcessor\n\n# In Falcon 3.0 `falcon.api_helpers` is renamed to `falcon.app_helpers`\n# and `falcon.API` to `falcon.App`\n\ntry:\n    import falcon  # type: ignore\n\n    from falcon import __version__ as FALCON_VERSION\nexcept ImportError:\n    raise DidNotEnable(\"Falcon not installed\")\n\ntry:\n    import falcon.app_helpers  # type: ignore\n\n    falcon_helpers = falcon.app_helpers\n    falcon_app_class = falcon.App\n    FALCON3 = True\nexcept ImportError:\n    import falcon.api_helpers  # type: ignore\n\n    falcon_helpers = falcon.api_helpers\n    falcon_app_class = falcon.API\n    FALCON3 = False\n\n\nclass FalconRequestExtractor(RequestExtractor):\n    def env(self):\n        # type: () -> Dict[str, Any]\n        return self.request.env\n\n    def cookies(self):\n        # type: () -> Dict[str, Any]\n        return self.request.cookies\n\n    def form(self):\n        # type: () -> None\n        return None  # No such concept in Falcon\n\n    def files(self):\n        # type: () -> None\n        return None  # No such concept in Falcon\n\n    def raw_data(self):\n        # type: () -> Optional[str]\n\n        # As request data can only be read once we won't make this available\n        # to Sentry. Just send back a dummy string in case there was a\n        # content length.\n        # TODO(jmagnusson): Figure out if there's a way to support this\n        content_length = self.content_length()\n        if content_length > 0:\n            return \"[REQUEST_CONTAINING_RAW_DATA]\"\n        else:\n            return None\n\n    if FALCON3:\n\n        def json(self):\n            # type: () -> Optional[Dict[str, Any]]\n            try:\n                return self.request.media\n            except falcon.errors.HTTPBadRequest:\n                return None\n\n    else:\n\n        def json(self):\n            # type: () -> Optional[Dict[str, Any]]\n            try:\n                return self.request.media\n            except falcon.errors.HTTPBadRequest:\n                # NOTE(jmagnusson): We return `falcon.Request._media` here because\n                # falcon 1.4 doesn't do proper type checking in\n                # `falcon.Request.media`. This has been fixed in 2.0.\n                # Relevant code: https://github.com/falconry/falcon/blob/1.4.1/falcon/request.py#L953\n                return self.request._media\n\n\nclass SentryFalconMiddleware(object):\n    \"\"\"Captures exceptions in Falcon requests and send to Sentry\"\"\"\n\n    def process_request(self, req, resp, *args, **kwargs):\n        # type: (Any, Any, *Any, **Any) -> None\n        hub = Hub.current\n        integration = hub.get_integration(FalconIntegration)\n        if integration is None:\n            return\n\n        with hub.configure_scope() as scope:\n            scope._name = \"falcon\"\n            scope.add_event_processor(_make_request_event_processor(req, integration))\n\n\nTRANSACTION_STYLE_VALUES = (\"uri_template\", \"path\")\n\n\nclass FalconIntegration(Integration):\n    identifier = \"falcon\"\n\n    transaction_style = \"\"\n\n    def __init__(self, transaction_style=\"uri_template\"):\n        # type: (str) -> None\n        if transaction_style not in TRANSACTION_STYLE_VALUES:\n            raise ValueError(\n                \"Invalid value for transaction_style: %s (must be in %s)\"\n                % (transaction_style, TRANSACTION_STYLE_VALUES)\n            )\n        self.transaction_style = transaction_style\n\n    @staticmethod\n    def setup_once():\n        # type: () -> None\n\n        version = parse_version(FALCON_VERSION)\n\n        if version is None:\n            raise DidNotEnable(\"Unparsable Falcon version: {}\".format(FALCON_VERSION))\n\n        if version < (1, 4):\n            raise DidNotEnable(\"Falcon 1.4 or newer required.\")\n\n        _patch_wsgi_app()\n        _patch_handle_exception()\n        _patch_prepare_middleware()\n\n\ndef _patch_wsgi_app():\n    # type: () -> None\n    original_wsgi_app = falcon_app_class.__call__\n\n    def sentry_patched_wsgi_app(self, env, start_response):\n        # type: (falcon.API, Any, Any) -> Any\n        hub = Hub.current\n        integration = hub.get_integration(FalconIntegration)\n        if integration is None:\n            return original_wsgi_app(self, env, start_response)\n\n        sentry_wrapped = SentryWsgiMiddleware(\n            lambda envi, start_resp: original_wsgi_app(self, envi, start_resp)\n        )\n\n        return sentry_wrapped(env, start_response)\n\n    falcon_app_class.__call__ = sentry_patched_wsgi_app\n\n\ndef _patch_handle_exception():\n    # type: () -> None\n    original_handle_exception = falcon_app_class._handle_exception\n\n    def sentry_patched_handle_exception(self, *args):\n        # type: (falcon.API, *Any) -> Any\n        # NOTE(jmagnusson): falcon 2.0 changed falcon.API._handle_exception\n        # method signature from `(ex, req, resp, params)` to\n        # `(req, resp, ex, params)`\n        ex = response = None\n        with capture_internal_exceptions():\n            ex = next(argument for argument in args if isinstance(argument, Exception))\n            response = next(\n                argument for argument in args if isinstance(argument, falcon.Response)\n            )\n\n        was_handled = original_handle_exception(self, *args)\n\n        if ex is None or response is None:\n            # Both ex and response should have a non-None value at this point; otherwise,\n            # there is an error with the SDK that will have been captured in the\n            # capture_internal_exceptions block above.\n            return was_handled\n\n        hub = Hub.current\n        integration = hub.get_integration(FalconIntegration)\n\n        if integration is not None and _exception_leads_to_http_5xx(ex, response):\n            # If an integration is there, a client has to be there.\n            client = hub.client  # type: Any\n\n            event, hint = event_from_exception(\n                ex,\n                client_options=client.options,\n                mechanism={\"type\": \"falcon\", \"handled\": False},\n            )\n            hub.capture_event(event, hint=hint)\n\n        return was_handled\n\n    falcon_app_class._handle_exception = sentry_patched_handle_exception\n\n\ndef _patch_prepare_middleware():\n    # type: () -> None\n    original_prepare_middleware = falcon_helpers.prepare_middleware\n\n    def sentry_patched_prepare_middleware(\n        middleware=None, independent_middleware=False, asgi=False\n    ):\n        # type: (Any, Any, bool) -> Any\n        if asgi:\n            # We don't support ASGI Falcon apps, so we don't patch anything here\n            return original_prepare_middleware(middleware, independent_middleware, asgi)\n\n        hub = Hub.current\n        integration = hub.get_integration(FalconIntegration)\n        if integration is not None:\n            middleware = [SentryFalconMiddleware()] + (middleware or [])\n\n        # We intentionally omit the asgi argument here, since the default is False anyways,\n        # and this way, we remain backwards-compatible with pre-3.0.0 Falcon versions.\n        return original_prepare_middleware(middleware, independent_middleware)\n\n    falcon_helpers.prepare_middleware = sentry_patched_prepare_middleware\n\n\ndef _exception_leads_to_http_5xx(ex, response):\n    # type: (Exception, falcon.Response) -> bool\n    is_server_error = isinstance(ex, falcon.HTTPError) and (ex.status or \"\").startswith(\n        \"5\"\n    )\n    is_unhandled_error = not isinstance(\n        ex, (falcon.HTTPError, falcon.http_status.HTTPStatus)\n    )\n\n    # We only check the HTTP status on Falcon 3 because in Falcon 2, the status on the response\n    # at the stage where we capture it is listed as 200, even though we would expect to see a 500\n    # status. Since at the time of this change, Falcon 2 is ca. 4 years old, we have decided to\n    # only perform this check on Falcon 3+, despite the risk that some handled errors might be\n    # reported to Sentry as unhandled on Falcon 2.\n    return (is_server_error or is_unhandled_error) and (\n        not FALCON3 or _has_http_5xx_status(response)\n    )\n\n\ndef _has_http_5xx_status(response):\n    # type: (falcon.Response) -> bool\n    return response.status.startswith(\"5\")\n\n\ndef _set_transaction_name_and_source(event, transaction_style, request):\n    # type: (Dict[str, Any], str, falcon.Request) -> None\n    name_for_style = {\n        \"uri_template\": request.uri_template,\n        \"path\": request.path,\n    }\n    event[\"transaction\"] = name_for_style[transaction_style]\n    event[\"transaction_info\"] = {\"source\": SOURCE_FOR_STYLE[transaction_style]}\n\n\ndef _make_request_event_processor(req, integration):\n    # type: (falcon.Request, FalconIntegration) -> EventProcessor\n\n    def event_processor(event, hint):\n        # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]\n        _set_transaction_name_and_source(event, integration.transaction_style, req)\n\n        with capture_internal_exceptions():\n            FalconRequestExtractor(req).extract_into_event(event)\n\n        return event\n\n    return event_processor\n", "path": "sentry_sdk/integrations/falcon.py"}]}