We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Performance instrumentation for Sanic Integration
### Problem Statement

Currently the `SanicIntegration` does not start transactions for a request-response-cycle.

### Solution Brainstorm

If Sanic is a WSGI based framework we maybe can use `SentryWsgiMiddleware` like bottle does: 
https://github.com/getsentry/sentry-python/blob/master/sentry_sdk/integrations/bottle.py

If not we can have a look at how the low level WSGI integration creates transactions (and continues traces from incoming requests): https://github.com/getsentry/sentry-python/blob/master/sentry_sdk/integrations/wsgi.py#L97-L115

--- 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/sanic.py`
Content:
```
1 import sys
2 import weakref
3 from inspect import isawaitable
4 
5 from sentry_sdk._compat import urlparse, reraise
6 from sentry_sdk.hub import Hub
7 from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT
8 from sentry_sdk.utils import (
9     capture_internal_exceptions,
10     event_from_exception,
11     HAS_REAL_CONTEXTVARS,
12     CONTEXTVARS_ERROR_MESSAGE,
13     parse_version,
14 )
15 from sentry_sdk.integrations import Integration, DidNotEnable
16 from sentry_sdk.integrations._wsgi_common import RequestExtractor, _filter_headers
17 from sentry_sdk.integrations.logging import ignore_logger
18 
19 from sentry_sdk._types import TYPE_CHECKING
20 
21 if TYPE_CHECKING:
22     from typing import Any
23     from typing import Callable
24     from typing import Optional
25     from typing import Union
26     from typing import Tuple
27     from typing import Dict
28 
29     from sanic.request import Request, RequestParameters
30 
31     from sentry_sdk._types import Event, EventProcessor, Hint
32     from sanic.router import Route
33 
34 try:
35     from sanic import Sanic, __version__ as SANIC_VERSION
36     from sanic.exceptions import SanicException
37     from sanic.router import Router
38     from sanic.handlers import ErrorHandler
39 except ImportError:
40     raise DidNotEnable("Sanic not installed")
41 
42 old_error_handler_lookup = ErrorHandler.lookup
43 old_handle_request = Sanic.handle_request
44 old_router_get = Router.get
45 
46 try:
47     # This method was introduced in Sanic v21.9
48     old_startup = Sanic._startup
49 except AttributeError:
50     pass
51 
52 
53 class SanicIntegration(Integration):
54     identifier = "sanic"
55     version = None
56 
57     @staticmethod
58     def setup_once():
59         # type: () -> None
60 
61         SanicIntegration.version = parse_version(SANIC_VERSION)
62 
63         if SanicIntegration.version is None:
64             raise DidNotEnable("Unparsable Sanic version: {}".format(SANIC_VERSION))
65 
66         if SanicIntegration.version < (0, 8):
67             raise DidNotEnable("Sanic 0.8 or newer required.")
68 
69         if not HAS_REAL_CONTEXTVARS:
70             # We better have contextvars or we're going to leak state between
71             # requests.
72             raise DidNotEnable(
73                 "The sanic integration for Sentry requires Python 3.7+ "
74                 " or the aiocontextvars package." + CONTEXTVARS_ERROR_MESSAGE
75             )
76 
77         if SANIC_VERSION.startswith("0.8."):
78             # Sanic 0.8 and older creates a logger named "root" and puts a
79             # stringified version of every exception in there (without exc_info),
80             # which our error deduplication can't detect.
81             #
82             # We explicitly check the version here because it is a very
83             # invasive step to ignore this logger and not necessary in newer
84             # versions at all.
85             #
86             # https://github.com/huge-success/sanic/issues/1332
87             ignore_logger("root")
88 
89         if SanicIntegration.version < (21, 9):
90             _setup_legacy_sanic()
91             return
92 
93         _setup_sanic()
94 
95 
96 class SanicRequestExtractor(RequestExtractor):
97     def content_length(self):
98         # type: () -> int
99         if self.request.body is None:
100             return 0
101         return len(self.request.body)
102 
103     def cookies(self):
104         # type: () -> Dict[str, str]
105         return dict(self.request.cookies)
106 
107     def raw_data(self):
108         # type: () -> bytes
109         return self.request.body
110 
111     def form(self):
112         # type: () -> RequestParameters
113         return self.request.form
114 
115     def is_json(self):
116         # type: () -> bool
117         raise NotImplementedError()
118 
119     def json(self):
120         # type: () -> Optional[Any]
121         return self.request.json
122 
123     def files(self):
124         # type: () -> RequestParameters
125         return self.request.files
126 
127     def size_of_file(self, file):
128         # type: (Any) -> int
129         return len(file.body or ())
130 
131 
132 def _setup_sanic():
133     # type: () -> None
134     Sanic._startup = _startup
135     ErrorHandler.lookup = _sentry_error_handler_lookup
136 
137 
138 def _setup_legacy_sanic():
139     # type: () -> None
140     Sanic.handle_request = _legacy_handle_request
141     Router.get = _legacy_router_get
142     ErrorHandler.lookup = _sentry_error_handler_lookup
143 
144 
145 async def _startup(self):
146     # type: (Sanic) -> None
147     # This happens about as early in the lifecycle as possible, just after the
148     # Request object is created. The body has not yet been consumed.
149     self.signal("http.lifecycle.request")(_hub_enter)
150 
151     # This happens after the handler is complete. In v21.9 this signal is not
152     # dispatched when there is an exception. Therefore we need to close out
153     # and call _hub_exit from the custom exception handler as well.
154     # See https://github.com/sanic-org/sanic/issues/2297
155     self.signal("http.lifecycle.response")(_hub_exit)
156 
157     # This happens inside of request handling immediately after the route
158     # has been identified by the router.
159     self.signal("http.routing.after")(_set_transaction)
160 
161     # The above signals need to be declared before this can be called.
162     await old_startup(self)
163 
164 
165 async def _hub_enter(request):
166     # type: (Request) -> None
167     hub = Hub.current
168     request.ctx._sentry_do_integration = (
169         hub.get_integration(SanicIntegration) is not None
170     )
171 
172     if not request.ctx._sentry_do_integration:
173         return
174 
175     weak_request = weakref.ref(request)
176     request.ctx._sentry_hub = Hub(hub)
177     request.ctx._sentry_hub.__enter__()
178 
179     with request.ctx._sentry_hub.configure_scope() as scope:
180         scope.clear_breadcrumbs()
181         scope.add_event_processor(_make_request_processor(weak_request))
182 
183 
184 async def _hub_exit(request, **_):
185     # type: (Request, **Any) -> None
186     request.ctx._sentry_hub.__exit__(None, None, None)
187 
188 
189 async def _set_transaction(request, route, **kwargs):
190     # type: (Request, Route, **Any) -> None
191     hub = Hub.current
192     if hub.get_integration(SanicIntegration) is not None:
193         with capture_internal_exceptions():
194             with hub.configure_scope() as scope:
195                 route_name = route.name.replace(request.app.name, "").strip(".")
196                 scope.set_transaction_name(
197                     route_name, source=TRANSACTION_SOURCE_COMPONENT
198                 )
199 
200 
201 def _sentry_error_handler_lookup(self, exception, *args, **kwargs):
202     # type: (Any, Exception, *Any, **Any) -> Optional[object]
203     _capture_exception(exception)
204     old_error_handler = old_error_handler_lookup(self, exception, *args, **kwargs)
205 
206     if old_error_handler is None:
207         return None
208 
209     if Hub.current.get_integration(SanicIntegration) is None:
210         return old_error_handler
211 
212     async def sentry_wrapped_error_handler(request, exception):
213         # type: (Request, Exception) -> Any
214         try:
215             response = old_error_handler(request, exception)
216             if isawaitable(response):
217                 response = await response
218             return response
219         except Exception:
220             # Report errors that occur in Sanic error handler. These
221             # exceptions will not even show up in Sanic's
222             # `sanic.exceptions` logger.
223             exc_info = sys.exc_info()
224             _capture_exception(exc_info)
225             reraise(*exc_info)
226         finally:
227             # As mentioned in previous comment in _startup, this can be removed
228             # after https://github.com/sanic-org/sanic/issues/2297 is resolved
229             if SanicIntegration.version and SanicIntegration.version == (21, 9):
230                 await _hub_exit(request)
231 
232     return sentry_wrapped_error_handler
233 
234 
235 async def _legacy_handle_request(self, request, *args, **kwargs):
236     # type: (Any, Request, *Any, **Any) -> Any
237     hub = Hub.current
238     if hub.get_integration(SanicIntegration) is None:
239         return old_handle_request(self, request, *args, **kwargs)
240 
241     weak_request = weakref.ref(request)
242 
243     with Hub(hub) as hub:
244         with hub.configure_scope() as scope:
245             scope.clear_breadcrumbs()
246             scope.add_event_processor(_make_request_processor(weak_request))
247 
248         response = old_handle_request(self, request, *args, **kwargs)
249         if isawaitable(response):
250             response = await response
251 
252         return response
253 
254 
255 def _legacy_router_get(self, *args):
256     # type: (Any, Union[Any, Request]) -> Any
257     rv = old_router_get(self, *args)
258     hub = Hub.current
259     if hub.get_integration(SanicIntegration) is not None:
260         with capture_internal_exceptions():
261             with hub.configure_scope() as scope:
262                 if SanicIntegration.version and SanicIntegration.version >= (21, 3):
263                     # Sanic versions above and including 21.3 append the app name to the
264                     # route name, and so we need to remove it from Route name so the
265                     # transaction name is consistent across all versions
266                     sanic_app_name = self.ctx.app.name
267                     sanic_route = rv[0].name
268 
269                     if sanic_route.startswith("%s." % sanic_app_name):
270                         # We add a 1 to the len of the sanic_app_name because there is a dot
271                         # that joins app name and the route name
272                         # Format: app_name.route_name
273                         sanic_route = sanic_route[len(sanic_app_name) + 1 :]
274 
275                     scope.set_transaction_name(
276                         sanic_route, source=TRANSACTION_SOURCE_COMPONENT
277                     )
278                 else:
279                     scope.set_transaction_name(
280                         rv[0].__name__, source=TRANSACTION_SOURCE_COMPONENT
281                     )
282 
283     return rv
284 
285 
286 def _capture_exception(exception):
287     # type: (Union[Tuple[Optional[type], Optional[BaseException], Any], BaseException]) -> None
288     hub = Hub.current
289     integration = hub.get_integration(SanicIntegration)
290     if integration is None:
291         return
292 
293     # If an integration is there, a client has to be there.
294     client = hub.client  # type: Any
295 
296     with capture_internal_exceptions():
297         event, hint = event_from_exception(
298             exception,
299             client_options=client.options,
300             mechanism={"type": "sanic", "handled": False},
301         )
302         hub.capture_event(event, hint=hint)
303 
304 
305 def _make_request_processor(weak_request):
306     # type: (Callable[[], Request]) -> EventProcessor
307     def sanic_processor(event, hint):
308         # type: (Event, Optional[Hint]) -> Optional[Event]
309 
310         try:
311             if hint and issubclass(hint["exc_info"][0], SanicException):
312                 return None
313         except KeyError:
314             pass
315 
316         request = weak_request()
317         if request is None:
318             return event
319 
320         with capture_internal_exceptions():
321             extractor = SanicRequestExtractor(request)
322             extractor.extract_into_event(event)
323 
324             request_info = event["request"]
325             urlparts = urlparse.urlsplit(request.url)
326 
327             request_info["url"] = "%s://%s%s" % (
328                 urlparts.scheme,
329                 urlparts.netloc,
330                 urlparts.path,
331             )
332 
333             request_info["query_string"] = urlparts.query
334             request_info["method"] = request.method
335             request_info["env"] = {"REMOTE_ADDR": request.remote_addr}
336             request_info["headers"] = _filter_headers(dict(request.headers))
337 
338         return event
339 
340     return sanic_processor
341
```
--- 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/sanic.py b/sentry_sdk/integrations/sanic.py
--- a/sentry_sdk/integrations/sanic.py
+++ b/sentry_sdk/integrations/sanic.py
@@ -2,9 +2,11 @@
 import weakref
 from inspect import isawaitable
 
+from sentry_sdk import continue_trace
 from sentry_sdk._compat import urlparse, reraise
+from sentry_sdk.consts import OP
 from sentry_sdk.hub import Hub
-from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT
+from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT, TRANSACTION_SOURCE_URL
 from sentry_sdk.utils import (
     capture_internal_exceptions,
     event_from_exception,
@@ -19,6 +21,7 @@
 from sentry_sdk._types import TYPE_CHECKING
 
 if TYPE_CHECKING:
+    from collections.abc import Container
     from typing import Any
     from typing import Callable
     from typing import Optional
@@ -27,6 +30,7 @@
     from typing import Dict
 
     from sanic.request import Request, RequestParameters
+    from sanic.response import BaseHTTPResponse
 
     from sentry_sdk._types import Event, EventProcessor, Hint
     from sanic.router import Route
@@ -54,6 +58,16 @@
     identifier = "sanic"
     version = None
 
+    def __init__(self, unsampled_statuses=frozenset({404})):
+        # type: (Optional[Container[int]]) -> None
+        """
+        The unsampled_statuses parameter can be used to specify for which HTTP statuses the
+        transactions should not be sent to Sentry. By default, transactions are sent for all
+        HTTP statuses, except 404. Set unsampled_statuses to None to send transactions for all
+        HTTP statuses, including 404.
+        """
+        self._unsampled_statuses = unsampled_statuses or set()
+
     @staticmethod
     def setup_once():
         # type: () -> None
@@ -180,16 +194,45 @@
         scope.clear_breadcrumbs()
         scope.add_event_processor(_make_request_processor(weak_request))
 
+    transaction = continue_trace(
+        dict(request.headers),
+        op=OP.HTTP_SERVER,
+        # Unless the request results in a 404 error, the name and source will get overwritten in _set_transaction
+        name=request.path,
+        source=TRANSACTION_SOURCE_URL,
+    )
+    request.ctx._sentry_transaction = request.ctx._sentry_hub.start_transaction(
+        transaction
+    ).__enter__()
+
+
+async def _hub_exit(request, response=None):
+    # type: (Request, Optional[BaseHTTPResponse]) -> None
+    with capture_internal_exceptions():
+        if not request.ctx._sentry_do_integration:
+            return
+
+        integration = Hub.current.get_integration(SanicIntegration)  # type: Integration
+
+        response_status = None if response is None else response.status
+
+        # This capture_internal_exceptions block has been intentionally nested here, so that in case an exception
+        # happens while trying to end the transaction, we still attempt to exit the hub.
+        with capture_internal_exceptions():
+            request.ctx._sentry_transaction.set_http_status(response_status)
+            request.ctx._sentry_transaction.sampled &= (
+                isinstance(integration, SanicIntegration)
+                and response_status not in integration._unsampled_statuses
+            )
+            request.ctx._sentry_transaction.__exit__(None, None, None)
 
-async def _hub_exit(request, **_):
-    # type: (Request, **Any) -> None
-    request.ctx._sentry_hub.__exit__(None, None, None)
+        request.ctx._sentry_hub.__exit__(None, None, None)
 
 
-async def _set_transaction(request, route, **kwargs):
+async def _set_transaction(request, route, **_):
     # type: (Request, Route, **Any) -> None
     hub = Hub.current
-    if hub.get_integration(SanicIntegration) is not None:
+    if request.ctx._sentry_do_integration:
         with capture_internal_exceptions():
             with hub.configure_scope() as scope:
                 route_name = route.name.replace(request.app.name, "").strip(".")

{"golden_diff": "diff --git a/sentry_sdk/integrations/sanic.py b/sentry_sdk/integrations/sanic.py\n--- a/sentry_sdk/integrations/sanic.py\n+++ b/sentry_sdk/integrations/sanic.py\n@@ -2,9 +2,11 @@\n import weakref\n from inspect import isawaitable\n \n+from sentry_sdk import continue_trace\n from sentry_sdk._compat import urlparse, reraise\n+from sentry_sdk.consts import OP\n from sentry_sdk.hub import Hub\n-from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT\n+from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT, TRANSACTION_SOURCE_URL\n from sentry_sdk.utils import (\n     capture_internal_exceptions,\n     event_from_exception,\n@@ -19,6 +21,7 @@\n from sentry_sdk._types import TYPE_CHECKING\n \n if TYPE_CHECKING:\n+    from collections.abc import Container\n     from typing import Any\n     from typing import Callable\n     from typing import Optional\n@@ -27,6 +30,7 @@\n     from typing import Dict\n \n     from sanic.request import Request, RequestParameters\n+    from sanic.response import BaseHTTPResponse\n \n     from sentry_sdk._types import Event, EventProcessor, Hint\n     from sanic.router import Route\n@@ -54,6 +58,16 @@\n     identifier = \"sanic\"\n     version = None\n \n+    def __init__(self, unsampled_statuses=frozenset({404})):\n+        # type: (Optional[Container[int]]) -> None\n+        \"\"\"\n+        The unsampled_statuses parameter can be used to specify for which HTTP statuses the\n+        transactions should not be sent to Sentry. By default, transactions are sent for all\n+        HTTP statuses, except 404. Set unsampled_statuses to None to send transactions for all\n+        HTTP statuses, including 404.\n+        \"\"\"\n+        self._unsampled_statuses = unsampled_statuses or set()\n+\n     @staticmethod\n     def setup_once():\n         # type: () -> None\n@@ -180,16 +194,45 @@\n         scope.clear_breadcrumbs()\n         scope.add_event_processor(_make_request_processor(weak_request))\n \n+    transaction = continue_trace(\n+        dict(request.headers),\n+        op=OP.HTTP_SERVER,\n+        # Unless the request results in a 404 error, the name and source will get overwritten in _set_transaction\n+        name=request.path,\n+        source=TRANSACTION_SOURCE_URL,\n+    )\n+    request.ctx._sentry_transaction = request.ctx._sentry_hub.start_transaction(\n+        transaction\n+    ).__enter__()\n+\n+\n+async def _hub_exit(request, response=None):\n+    # type: (Request, Optional[BaseHTTPResponse]) -> None\n+    with capture_internal_exceptions():\n+        if not request.ctx._sentry_do_integration:\n+            return\n+\n+        integration = Hub.current.get_integration(SanicIntegration)  # type: Integration\n+\n+        response_status = None if response is None else response.status\n+\n+        # This capture_internal_exceptions block has been intentionally nested here, so that in case an exception\n+        # happens while trying to end the transaction, we still attempt to exit the hub.\n+        with capture_internal_exceptions():\n+            request.ctx._sentry_transaction.set_http_status(response_status)\n+            request.ctx._sentry_transaction.sampled &= (\n+                isinstance(integration, SanicIntegration)\n+                and response_status not in integration._unsampled_statuses\n+            )\n+            request.ctx._sentry_transaction.__exit__(None, None, None)\n \n-async def _hub_exit(request, **_):\n-    # type: (Request, **Any) -> None\n-    request.ctx._sentry_hub.__exit__(None, None, None)\n+        request.ctx._sentry_hub.__exit__(None, None, None)\n \n \n-async def _set_transaction(request, route, **kwargs):\n+async def _set_transaction(request, route, **_):\n     # type: (Request, Route, **Any) -> None\n     hub = Hub.current\n-    if hub.get_integration(SanicIntegration) is not None:\n+    if request.ctx._sentry_do_integration:\n         with capture_internal_exceptions():\n             with hub.configure_scope() as scope:\n                 route_name = route.name.replace(request.app.name, \"\").strip(\".\")\n", "issue": "Performance instrumentation for Sanic Integration\n### Problem Statement\n\nCurrently the `SanicIntegration` does not start transactions for a request-response-cycle.\n\n### Solution Brainstorm\n\nIf Sanic is a WSGI based framework we maybe can use `SentryWsgiMiddleware` like bottle does: \r\nhttps://github.com/getsentry/sentry-python/blob/master/sentry_sdk/integrations/bottle.py\r\n\r\nIf not we can have a look at how the low level WSGI integration creates transactions (and continues traces from incoming requests): https://github.com/getsentry/sentry-python/blob/master/sentry_sdk/integrations/wsgi.py#L97-L115\n", "before_files": [{"content": "import sys\nimport weakref\nfrom inspect import isawaitable\n\nfrom sentry_sdk._compat import urlparse, reraise\nfrom sentry_sdk.hub import Hub\nfrom sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT\nfrom sentry_sdk.utils import (\n    capture_internal_exceptions,\n    event_from_exception,\n    HAS_REAL_CONTEXTVARS,\n    CONTEXTVARS_ERROR_MESSAGE,\n    parse_version,\n)\nfrom sentry_sdk.integrations import Integration, DidNotEnable\nfrom sentry_sdk.integrations._wsgi_common import RequestExtractor, _filter_headers\nfrom sentry_sdk.integrations.logging import ignore_logger\n\nfrom sentry_sdk._types import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Any\n    from typing import Callable\n    from typing import Optional\n    from typing import Union\n    from typing import Tuple\n    from typing import Dict\n\n    from sanic.request import Request, RequestParameters\n\n    from sentry_sdk._types import Event, EventProcessor, Hint\n    from sanic.router import Route\n\ntry:\n    from sanic import Sanic, __version__ as SANIC_VERSION\n    from sanic.exceptions import SanicException\n    from sanic.router import Router\n    from sanic.handlers import ErrorHandler\nexcept ImportError:\n    raise DidNotEnable(\"Sanic not installed\")\n\nold_error_handler_lookup = ErrorHandler.lookup\nold_handle_request = Sanic.handle_request\nold_router_get = Router.get\n\ntry:\n    # This method was introduced in Sanic v21.9\n    old_startup = Sanic._startup\nexcept AttributeError:\n    pass\n\n\nclass SanicIntegration(Integration):\n    identifier = \"sanic\"\n    version = None\n\n    @staticmethod\n    def setup_once():\n        # type: () -> None\n\n        SanicIntegration.version = parse_version(SANIC_VERSION)\n\n        if SanicIntegration.version is None:\n            raise DidNotEnable(\"Unparsable Sanic version: {}\".format(SANIC_VERSION))\n\n        if SanicIntegration.version < (0, 8):\n            raise DidNotEnable(\"Sanic 0.8 or newer required.\")\n\n        if not HAS_REAL_CONTEXTVARS:\n            # We better have contextvars or we're going to leak state between\n            # requests.\n            raise DidNotEnable(\n                \"The sanic integration for Sentry requires Python 3.7+ \"\n                \" or the aiocontextvars package.\" + CONTEXTVARS_ERROR_MESSAGE\n            )\n\n        if SANIC_VERSION.startswith(\"0.8.\"):\n            # Sanic 0.8 and older creates a logger named \"root\" and puts a\n            # stringified version of every exception in there (without exc_info),\n            # which our error deduplication can't detect.\n            #\n            # We explicitly check the version here because it is a very\n            # invasive step to ignore this logger and not necessary in newer\n            # versions at all.\n            #\n            # https://github.com/huge-success/sanic/issues/1332\n            ignore_logger(\"root\")\n\n        if SanicIntegration.version < (21, 9):\n            _setup_legacy_sanic()\n            return\n\n        _setup_sanic()\n\n\nclass SanicRequestExtractor(RequestExtractor):\n    def content_length(self):\n        # type: () -> int\n        if self.request.body is None:\n            return 0\n        return len(self.request.body)\n\n    def cookies(self):\n        # type: () -> Dict[str, str]\n        return dict(self.request.cookies)\n\n    def raw_data(self):\n        # type: () -> bytes\n        return self.request.body\n\n    def form(self):\n        # type: () -> RequestParameters\n        return self.request.form\n\n    def is_json(self):\n        # type: () -> bool\n        raise NotImplementedError()\n\n    def json(self):\n        # type: () -> Optional[Any]\n        return self.request.json\n\n    def files(self):\n        # type: () -> RequestParameters\n        return self.request.files\n\n    def size_of_file(self, file):\n        # type: (Any) -> int\n        return len(file.body or ())\n\n\ndef _setup_sanic():\n    # type: () -> None\n    Sanic._startup = _startup\n    ErrorHandler.lookup = _sentry_error_handler_lookup\n\n\ndef _setup_legacy_sanic():\n    # type: () -> None\n    Sanic.handle_request = _legacy_handle_request\n    Router.get = _legacy_router_get\n    ErrorHandler.lookup = _sentry_error_handler_lookup\n\n\nasync def _startup(self):\n    # type: (Sanic) -> None\n    # This happens about as early in the lifecycle as possible, just after the\n    # Request object is created. The body has not yet been consumed.\n    self.signal(\"http.lifecycle.request\")(_hub_enter)\n\n    # This happens after the handler is complete. In v21.9 this signal is not\n    # dispatched when there is an exception. Therefore we need to close out\n    # and call _hub_exit from the custom exception handler as well.\n    # See https://github.com/sanic-org/sanic/issues/2297\n    self.signal(\"http.lifecycle.response\")(_hub_exit)\n\n    # This happens inside of request handling immediately after the route\n    # has been identified by the router.\n    self.signal(\"http.routing.after\")(_set_transaction)\n\n    # The above signals need to be declared before this can be called.\n    await old_startup(self)\n\n\nasync def _hub_enter(request):\n    # type: (Request) -> None\n    hub = Hub.current\n    request.ctx._sentry_do_integration = (\n        hub.get_integration(SanicIntegration) is not None\n    )\n\n    if not request.ctx._sentry_do_integration:\n        return\n\n    weak_request = weakref.ref(request)\n    request.ctx._sentry_hub = Hub(hub)\n    request.ctx._sentry_hub.__enter__()\n\n    with request.ctx._sentry_hub.configure_scope() as scope:\n        scope.clear_breadcrumbs()\n        scope.add_event_processor(_make_request_processor(weak_request))\n\n\nasync def _hub_exit(request, **_):\n    # type: (Request, **Any) -> None\n    request.ctx._sentry_hub.__exit__(None, None, None)\n\n\nasync def _set_transaction(request, route, **kwargs):\n    # type: (Request, Route, **Any) -> None\n    hub = Hub.current\n    if hub.get_integration(SanicIntegration) is not None:\n        with capture_internal_exceptions():\n            with hub.configure_scope() as scope:\n                route_name = route.name.replace(request.app.name, \"\").strip(\".\")\n                scope.set_transaction_name(\n                    route_name, source=TRANSACTION_SOURCE_COMPONENT\n                )\n\n\ndef _sentry_error_handler_lookup(self, exception, *args, **kwargs):\n    # type: (Any, Exception, *Any, **Any) -> Optional[object]\n    _capture_exception(exception)\n    old_error_handler = old_error_handler_lookup(self, exception, *args, **kwargs)\n\n    if old_error_handler is None:\n        return None\n\n    if Hub.current.get_integration(SanicIntegration) is None:\n        return old_error_handler\n\n    async def sentry_wrapped_error_handler(request, exception):\n        # type: (Request, Exception) -> Any\n        try:\n            response = old_error_handler(request, exception)\n            if isawaitable(response):\n                response = await response\n            return response\n        except Exception:\n            # Report errors that occur in Sanic error handler. These\n            # exceptions will not even show up in Sanic's\n            # `sanic.exceptions` logger.\n            exc_info = sys.exc_info()\n            _capture_exception(exc_info)\n            reraise(*exc_info)\n        finally:\n            # As mentioned in previous comment in _startup, this can be removed\n            # after https://github.com/sanic-org/sanic/issues/2297 is resolved\n            if SanicIntegration.version and SanicIntegration.version == (21, 9):\n                await _hub_exit(request)\n\n    return sentry_wrapped_error_handler\n\n\nasync def _legacy_handle_request(self, request, *args, **kwargs):\n    # type: (Any, Request, *Any, **Any) -> Any\n    hub = Hub.current\n    if hub.get_integration(SanicIntegration) is None:\n        return old_handle_request(self, request, *args, **kwargs)\n\n    weak_request = weakref.ref(request)\n\n    with Hub(hub) as hub:\n        with hub.configure_scope() as scope:\n            scope.clear_breadcrumbs()\n            scope.add_event_processor(_make_request_processor(weak_request))\n\n        response = old_handle_request(self, request, *args, **kwargs)\n        if isawaitable(response):\n            response = await response\n\n        return response\n\n\ndef _legacy_router_get(self, *args):\n    # type: (Any, Union[Any, Request]) -> Any\n    rv = old_router_get(self, *args)\n    hub = Hub.current\n    if hub.get_integration(SanicIntegration) is not None:\n        with capture_internal_exceptions():\n            with hub.configure_scope() as scope:\n                if SanicIntegration.version and SanicIntegration.version >= (21, 3):\n                    # Sanic versions above and including 21.3 append the app name to the\n                    # route name, and so we need to remove it from Route name so the\n                    # transaction name is consistent across all versions\n                    sanic_app_name = self.ctx.app.name\n                    sanic_route = rv[0].name\n\n                    if sanic_route.startswith(\"%s.\" % sanic_app_name):\n                        # We add a 1 to the len of the sanic_app_name because there is a dot\n                        # that joins app name and the route name\n                        # Format: app_name.route_name\n                        sanic_route = sanic_route[len(sanic_app_name) + 1 :]\n\n                    scope.set_transaction_name(\n                        sanic_route, source=TRANSACTION_SOURCE_COMPONENT\n                    )\n                else:\n                    scope.set_transaction_name(\n                        rv[0].__name__, source=TRANSACTION_SOURCE_COMPONENT\n                    )\n\n    return rv\n\n\ndef _capture_exception(exception):\n    # type: (Union[Tuple[Optional[type], Optional[BaseException], Any], BaseException]) -> None\n    hub = Hub.current\n    integration = hub.get_integration(SanicIntegration)\n    if integration is None:\n        return\n\n    # If an integration is there, a client has to be there.\n    client = hub.client  # type: Any\n\n    with capture_internal_exceptions():\n        event, hint = event_from_exception(\n            exception,\n            client_options=client.options,\n            mechanism={\"type\": \"sanic\", \"handled\": False},\n        )\n        hub.capture_event(event, hint=hint)\n\n\ndef _make_request_processor(weak_request):\n    # type: (Callable[[], Request]) -> EventProcessor\n    def sanic_processor(event, hint):\n        # type: (Event, Optional[Hint]) -> Optional[Event]\n\n        try:\n            if hint and issubclass(hint[\"exc_info\"][0], SanicException):\n                return None\n        except KeyError:\n            pass\n\n        request = weak_request()\n        if request is None:\n            return event\n\n        with capture_internal_exceptions():\n            extractor = SanicRequestExtractor(request)\n            extractor.extract_into_event(event)\n\n            request_info = event[\"request\"]\n            urlparts = urlparse.urlsplit(request.url)\n\n            request_info[\"url\"] = \"%s://%s%s\" % (\n                urlparts.scheme,\n                urlparts.netloc,\n                urlparts.path,\n            )\n\n            request_info[\"query_string\"] = urlparts.query\n            request_info[\"method\"] = request.method\n            request_info[\"env\"] = {\"REMOTE_ADDR\": request.remote_addr}\n            request_info[\"headers\"] = _filter_headers(dict(request.headers))\n\n        return event\n\n    return sanic_processor\n", "path": "sentry_sdk/integrations/sanic.py"}], "after_files": [{"content": "import sys\nimport weakref\nfrom inspect import isawaitable\n\nfrom sentry_sdk import continue_trace\nfrom sentry_sdk._compat import urlparse, reraise\nfrom sentry_sdk.consts import OP\nfrom sentry_sdk.hub import Hub\nfrom sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT, TRANSACTION_SOURCE_URL\nfrom sentry_sdk.utils import (\n    capture_internal_exceptions,\n    event_from_exception,\n    HAS_REAL_CONTEXTVARS,\n    CONTEXTVARS_ERROR_MESSAGE,\n    parse_version,\n)\nfrom sentry_sdk.integrations import Integration, DidNotEnable\nfrom sentry_sdk.integrations._wsgi_common import RequestExtractor, _filter_headers\nfrom sentry_sdk.integrations.logging import ignore_logger\n\nfrom sentry_sdk._types import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from collections.abc import Container\n    from typing import Any\n    from typing import Callable\n    from typing import Optional\n    from typing import Union\n    from typing import Tuple\n    from typing import Dict\n\n    from sanic.request import Request, RequestParameters\n    from sanic.response import BaseHTTPResponse\n\n    from sentry_sdk._types import Event, EventProcessor, Hint\n    from sanic.router import Route\n\ntry:\n    from sanic import Sanic, __version__ as SANIC_VERSION\n    from sanic.exceptions import SanicException\n    from sanic.router import Router\n    from sanic.handlers import ErrorHandler\nexcept ImportError:\n    raise DidNotEnable(\"Sanic not installed\")\n\nold_error_handler_lookup = ErrorHandler.lookup\nold_handle_request = Sanic.handle_request\nold_router_get = Router.get\n\ntry:\n    # This method was introduced in Sanic v21.9\n    old_startup = Sanic._startup\nexcept AttributeError:\n    pass\n\n\nclass SanicIntegration(Integration):\n    identifier = \"sanic\"\n    version = None\n\n    def __init__(self, unsampled_statuses=frozenset({404})):\n        # type: (Optional[Container[int]]) -> None\n        \"\"\"\n        The unsampled_statuses parameter can be used to specify for which HTTP statuses the\n        transactions should not be sent to Sentry. By default, transactions are sent for all\n        HTTP statuses, except 404. Set unsampled_statuses to None to send transactions for all\n        HTTP statuses, including 404.\n        \"\"\"\n        self._unsampled_statuses = unsampled_statuses or set()\n\n    @staticmethod\n    def setup_once():\n        # type: () -> None\n\n        SanicIntegration.version = parse_version(SANIC_VERSION)\n\n        if SanicIntegration.version is None:\n            raise DidNotEnable(\"Unparsable Sanic version: {}\".format(SANIC_VERSION))\n\n        if SanicIntegration.version < (0, 8):\n            raise DidNotEnable(\"Sanic 0.8 or newer required.\")\n\n        if not HAS_REAL_CONTEXTVARS:\n            # We better have contextvars or we're going to leak state between\n            # requests.\n            raise DidNotEnable(\n                \"The sanic integration for Sentry requires Python 3.7+ \"\n                \" or the aiocontextvars package.\" + CONTEXTVARS_ERROR_MESSAGE\n            )\n\n        if SANIC_VERSION.startswith(\"0.8.\"):\n            # Sanic 0.8 and older creates a logger named \"root\" and puts a\n            # stringified version of every exception in there (without exc_info),\n            # which our error deduplication can't detect.\n            #\n            # We explicitly check the version here because it is a very\n            # invasive step to ignore this logger and not necessary in newer\n            # versions at all.\n            #\n            # https://github.com/huge-success/sanic/issues/1332\n            ignore_logger(\"root\")\n\n        if SanicIntegration.version < (21, 9):\n            _setup_legacy_sanic()\n            return\n\n        _setup_sanic()\n\n\nclass SanicRequestExtractor(RequestExtractor):\n    def content_length(self):\n        # type: () -> int\n        if self.request.body is None:\n            return 0\n        return len(self.request.body)\n\n    def cookies(self):\n        # type: () -> Dict[str, str]\n        return dict(self.request.cookies)\n\n    def raw_data(self):\n        # type: () -> bytes\n        return self.request.body\n\n    def form(self):\n        # type: () -> RequestParameters\n        return self.request.form\n\n    def is_json(self):\n        # type: () -> bool\n        raise NotImplementedError()\n\n    def json(self):\n        # type: () -> Optional[Any]\n        return self.request.json\n\n    def files(self):\n        # type: () -> RequestParameters\n        return self.request.files\n\n    def size_of_file(self, file):\n        # type: (Any) -> int\n        return len(file.body or ())\n\n\ndef _setup_sanic():\n    # type: () -> None\n    Sanic._startup = _startup\n    ErrorHandler.lookup = _sentry_error_handler_lookup\n\n\ndef _setup_legacy_sanic():\n    # type: () -> None\n    Sanic.handle_request = _legacy_handle_request\n    Router.get = _legacy_router_get\n    ErrorHandler.lookup = _sentry_error_handler_lookup\n\n\nasync def _startup(self):\n    # type: (Sanic) -> None\n    # This happens about as early in the lifecycle as possible, just after the\n    # Request object is created. The body has not yet been consumed.\n    self.signal(\"http.lifecycle.request\")(_hub_enter)\n\n    # This happens after the handler is complete. In v21.9 this signal is not\n    # dispatched when there is an exception. Therefore we need to close out\n    # and call _hub_exit from the custom exception handler as well.\n    # See https://github.com/sanic-org/sanic/issues/2297\n    self.signal(\"http.lifecycle.response\")(_hub_exit)\n\n    # This happens inside of request handling immediately after the route\n    # has been identified by the router.\n    self.signal(\"http.routing.after\")(_set_transaction)\n\n    # The above signals need to be declared before this can be called.\n    await old_startup(self)\n\n\nasync def _hub_enter(request):\n    # type: (Request) -> None\n    hub = Hub.current\n    request.ctx._sentry_do_integration = (\n        hub.get_integration(SanicIntegration) is not None\n    )\n\n    if not request.ctx._sentry_do_integration:\n        return\n\n    weak_request = weakref.ref(request)\n    request.ctx._sentry_hub = Hub(hub)\n    request.ctx._sentry_hub.__enter__()\n\n    with request.ctx._sentry_hub.configure_scope() as scope:\n        scope.clear_breadcrumbs()\n        scope.add_event_processor(_make_request_processor(weak_request))\n\n    transaction = continue_trace(\n        dict(request.headers),\n        op=OP.HTTP_SERVER,\n        # Unless the request results in a 404 error, the name and source will get overwritten in _set_transaction\n        name=request.path,\n        source=TRANSACTION_SOURCE_URL,\n    )\n    request.ctx._sentry_transaction = request.ctx._sentry_hub.start_transaction(\n        transaction\n    ).__enter__()\n\n\nasync def _hub_exit(request, response=None):\n    # type: (Request, Optional[BaseHTTPResponse]) -> None\n    with capture_internal_exceptions():\n        if not request.ctx._sentry_do_integration:\n            return\n\n        integration = Hub.current.get_integration(SanicIntegration)  # type: Integration\n\n        response_status = None if response is None else response.status\n\n        # This capture_internal_exceptions block has been intentionally nested here, so that in case an exception\n        # happens while trying to end the transaction, we still attempt to exit the hub.\n        with capture_internal_exceptions():\n            request.ctx._sentry_transaction.set_http_status(response_status)\n            request.ctx._sentry_transaction.sampled &= (\n                isinstance(integration, SanicIntegration)\n                and response_status not in integration._unsampled_statuses\n            )\n            request.ctx._sentry_transaction.__exit__(None, None, None)\n\n        request.ctx._sentry_hub.__exit__(None, None, None)\n\n\nasync def _set_transaction(request, route, **_):\n    # type: (Request, Route, **Any) -> None\n    hub = Hub.current\n    if request.ctx._sentry_do_integration:\n        with capture_internal_exceptions():\n            with hub.configure_scope() as scope:\n                route_name = route.name.replace(request.app.name, \"\").strip(\".\")\n                scope.set_transaction_name(\n                    route_name, source=TRANSACTION_SOURCE_COMPONENT\n                )\n\n\ndef _sentry_error_handler_lookup(self, exception, *args, **kwargs):\n    # type: (Any, Exception, *Any, **Any) -> Optional[object]\n    _capture_exception(exception)\n    old_error_handler = old_error_handler_lookup(self, exception, *args, **kwargs)\n\n    if old_error_handler is None:\n        return None\n\n    if Hub.current.get_integration(SanicIntegration) is None:\n        return old_error_handler\n\n    async def sentry_wrapped_error_handler(request, exception):\n        # type: (Request, Exception) -> Any\n        try:\n            response = old_error_handler(request, exception)\n            if isawaitable(response):\n                response = await response\n            return response\n        except Exception:\n            # Report errors that occur in Sanic error handler. These\n            # exceptions will not even show up in Sanic's\n            # `sanic.exceptions` logger.\n            exc_info = sys.exc_info()\n            _capture_exception(exc_info)\n            reraise(*exc_info)\n        finally:\n            # As mentioned in previous comment in _startup, this can be removed\n            # after https://github.com/sanic-org/sanic/issues/2297 is resolved\n            if SanicIntegration.version and SanicIntegration.version == (21, 9):\n                await _hub_exit(request)\n\n    return sentry_wrapped_error_handler\n\n\nasync def _legacy_handle_request(self, request, *args, **kwargs):\n    # type: (Any, Request, *Any, **Any) -> Any\n    hub = Hub.current\n    if hub.get_integration(SanicIntegration) is None:\n        return old_handle_request(self, request, *args, **kwargs)\n\n    weak_request = weakref.ref(request)\n\n    with Hub(hub) as hub:\n        with hub.configure_scope() as scope:\n            scope.clear_breadcrumbs()\n            scope.add_event_processor(_make_request_processor(weak_request))\n\n        response = old_handle_request(self, request, *args, **kwargs)\n        if isawaitable(response):\n            response = await response\n\n        return response\n\n\ndef _legacy_router_get(self, *args):\n    # type: (Any, Union[Any, Request]) -> Any\n    rv = old_router_get(self, *args)\n    hub = Hub.current\n    if hub.get_integration(SanicIntegration) is not None:\n        with capture_internal_exceptions():\n            with hub.configure_scope() as scope:\n                if SanicIntegration.version and SanicIntegration.version >= (21, 3):\n                    # Sanic versions above and including 21.3 append the app name to the\n                    # route name, and so we need to remove it from Route name so the\n                    # transaction name is consistent across all versions\n                    sanic_app_name = self.ctx.app.name\n                    sanic_route = rv[0].name\n\n                    if sanic_route.startswith(\"%s.\" % sanic_app_name):\n                        # We add a 1 to the len of the sanic_app_name because there is a dot\n                        # that joins app name and the route name\n                        # Format: app_name.route_name\n                        sanic_route = sanic_route[len(sanic_app_name) + 1 :]\n\n                    scope.set_transaction_name(\n                        sanic_route, source=TRANSACTION_SOURCE_COMPONENT\n                    )\n                else:\n                    scope.set_transaction_name(\n                        rv[0].__name__, source=TRANSACTION_SOURCE_COMPONENT\n                    )\n\n    return rv\n\n\ndef _capture_exception(exception):\n    # type: (Union[Tuple[Optional[type], Optional[BaseException], Any], BaseException]) -> None\n    hub = Hub.current\n    integration = hub.get_integration(SanicIntegration)\n    if integration is None:\n        return\n\n    # If an integration is there, a client has to be there.\n    client = hub.client  # type: Any\n\n    with capture_internal_exceptions():\n        event, hint = event_from_exception(\n            exception,\n            client_options=client.options,\n            mechanism={\"type\": \"sanic\", \"handled\": False},\n        )\n        hub.capture_event(event, hint=hint)\n\n\ndef _make_request_processor(weak_request):\n    # type: (Callable[[], Request]) -> EventProcessor\n    def sanic_processor(event, hint):\n        # type: (Event, Optional[Hint]) -> Optional[Event]\n\n        try:\n            if hint and issubclass(hint[\"exc_info\"][0], SanicException):\n                return None\n        except KeyError:\n            pass\n\n        request = weak_request()\n        if request is None:\n            return event\n\n        with capture_internal_exceptions():\n            extractor = SanicRequestExtractor(request)\n            extractor.extract_into_event(event)\n\n            request_info = event[\"request\"]\n            urlparts = urlparse.urlsplit(request.url)\n\n            request_info[\"url\"] = \"%s://%s%s\" % (\n                urlparts.scheme,\n                urlparts.netloc,\n                urlparts.path,\n            )\n\n            request_info[\"query_string\"] = urlparts.query\n            request_info[\"method\"] = request.method\n            request_info[\"env\"] = {\"REMOTE_ADDR\": request.remote_addr}\n            request_info[\"headers\"] = _filter_headers(dict(request.headers))\n\n        return event\n\n    return sanic_processor\n", "path": "sentry_sdk/integrations/sanic.py"}]}