We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
last_event_id() returns None after SentryAsgiMiddleware.__call__() finished
I'm unable to access `last_event_id` of `SentryAsgiMiddleware` on exception handler in `Starlette` framework.
```python
from sentry_sdk import last_event_id
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route


async def test_endpoint(request):
    raise RuntimeError("test")


def exception_handler(*args, **kwargs):
    return JSONResponse({"last_event_id": last_event_id()})


app = Starlette(
    routes=[Route('/', test_endpoint)],
    exception_handlers={
        Exception: exception_handler,
    }
)
app.add_middleware(SentryAsgiMiddleware)
```  

the problem is probably with usage of Hub's context manager in `SentryAsgiMiddleware._run_app()` - after throwing exception you are clearing local `ContextVar` so `last_event_id` function tries to access wrong Hub instance.

--- 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/asgi.py`
Content:
```
1 """
2 An ASGI middleware.
3 
4 Based on Tom Christie's `sentry-asgi <https://github.com/encode/sentry-asgi>`_.
5 """
6 
7 import asyncio
8 import inspect
9 import urllib
10 
11 from sentry_sdk._functools import partial
12 from sentry_sdk._types import MYPY
13 from sentry_sdk.hub import Hub, _should_send_default_pii
14 from sentry_sdk.integrations._wsgi_common import _filter_headers
15 from sentry_sdk.utils import (
16     ContextVar,
17     event_from_exception,
18     transaction_from_function,
19     HAS_REAL_CONTEXTVARS,
20     CONTEXTVARS_ERROR_MESSAGE,
21 )
22 from sentry_sdk.tracing import Transaction
23 
24 if MYPY:
25     from typing import Dict
26     from typing import Any
27     from typing import Optional
28     from typing import Callable
29 
30     from typing_extensions import Literal
31 
32     from sentry_sdk._types import Event, Hint
33 
34 
35 _asgi_middleware_applied = ContextVar("sentry_asgi_middleware_applied")
36 
37 _DEFAULT_TRANSACTION_NAME = "generic ASGI request"
38 
39 
40 def _capture_exception(hub, exc):
41     # type: (Hub, Any) -> None
42 
43     # Check client here as it might have been unset while streaming response
44     if hub.client is not None:
45         event, hint = event_from_exception(
46             exc,
47             client_options=hub.client.options,
48             mechanism={"type": "asgi", "handled": False},
49         )
50         hub.capture_event(event, hint=hint)
51 
52 
53 def _looks_like_asgi3(app):
54     # type: (Any) -> bool
55     """
56     Try to figure out if an application object supports ASGI3.
57 
58     This is how uvicorn figures out the application version as well.
59     """
60     if inspect.isclass(app):
61         return hasattr(app, "__await__")
62     elif inspect.isfunction(app):
63         return asyncio.iscoroutinefunction(app)
64     else:
65         call = getattr(app, "__call__", None)  # noqa
66         return asyncio.iscoroutinefunction(call)
67 
68 
69 class SentryAsgiMiddleware:
70     __slots__ = ("app", "__call__")
71 
72     def __init__(self, app, unsafe_context_data=False):
73         # type: (Any, bool) -> None
74         """
75         Instrument an ASGI application with Sentry. Provides HTTP/websocket
76         data to sent events and basic handling for exceptions bubbling up
77         through the middleware.
78 
79         :param unsafe_context_data: Disable errors when a proper contextvars installation could not be found. We do not recommend changing this from the default.
80         """
81 
82         if not unsafe_context_data and not HAS_REAL_CONTEXTVARS:
83             # We better have contextvars or we're going to leak state between
84             # requests.
85             raise RuntimeError(
86                 "The ASGI middleware for Sentry requires Python 3.7+ "
87                 "or the aiocontextvars package." + CONTEXTVARS_ERROR_MESSAGE
88             )
89         self.app = app
90 
91         if _looks_like_asgi3(app):
92             self.__call__ = self._run_asgi3  # type: Callable[..., Any]
93         else:
94             self.__call__ = self._run_asgi2
95 
96     def _run_asgi2(self, scope):
97         # type: (Any) -> Any
98         async def inner(receive, send):
99             # type: (Any, Any) -> Any
100             return await self._run_app(scope, lambda: self.app(scope)(receive, send))
101 
102         return inner
103 
104     async def _run_asgi3(self, scope, receive, send):
105         # type: (Any, Any, Any) -> Any
106         return await self._run_app(scope, lambda: self.app(scope, receive, send))
107 
108     async def _run_app(self, scope, callback):
109         # type: (Any, Any) -> Any
110         if _asgi_middleware_applied.get(False):
111             return await callback()
112 
113         _asgi_middleware_applied.set(True)
114         try:
115             hub = Hub(Hub.current)
116             with hub:
117                 with hub.configure_scope() as sentry_scope:
118                     sentry_scope.clear_breadcrumbs()
119                     sentry_scope._name = "asgi"
120                     processor = partial(self.event_processor, asgi_scope=scope)
121                     sentry_scope.add_event_processor(processor)
122 
123                 ty = scope["type"]
124 
125                 if ty in ("http", "websocket"):
126                     transaction = Transaction.continue_from_headers(
127                         dict(scope["headers"]),
128                         op="{}.server".format(ty),
129                     )
130                 else:
131                     transaction = Transaction(op="asgi.server")
132 
133                 transaction.name = _DEFAULT_TRANSACTION_NAME
134                 transaction.set_tag("asgi.type", ty)
135 
136                 with hub.start_transaction(transaction):
137                     # XXX: Would be cool to have correct span status, but we
138                     # would have to wrap send(). That is a bit hard to do with
139                     # the current abstraction over ASGI 2/3.
140                     try:
141                         return await callback()
142                     except Exception as exc:
143                         _capture_exception(hub, exc)
144                         raise exc from None
145         finally:
146             _asgi_middleware_applied.set(False)
147 
148     def event_processor(self, event, hint, asgi_scope):
149         # type: (Event, Hint, Any) -> Optional[Event]
150         request_info = event.get("request", {})
151 
152         ty = asgi_scope["type"]
153         if ty in ("http", "websocket"):
154             request_info["method"] = asgi_scope.get("method")
155             request_info["headers"] = headers = _filter_headers(
156                 self._get_headers(asgi_scope)
157             )
158             request_info["query_string"] = self._get_query(asgi_scope)
159 
160             request_info["url"] = self._get_url(
161                 asgi_scope, "http" if ty == "http" else "ws", headers.get("host")
162             )
163 
164         client = asgi_scope.get("client")
165         if client and _should_send_default_pii():
166             request_info["env"] = {"REMOTE_ADDR": client[0]}
167 
168         if (
169             event.get("transaction", _DEFAULT_TRANSACTION_NAME)
170             == _DEFAULT_TRANSACTION_NAME
171         ):
172             endpoint = asgi_scope.get("endpoint")
173             # Webframeworks like Starlette mutate the ASGI env once routing is
174             # done, which is sometime after the request has started. If we have
175             # an endpoint, overwrite our generic transaction name.
176             if endpoint:
177                 event["transaction"] = transaction_from_function(endpoint)
178 
179         event["request"] = request_info
180 
181         return event
182 
183     # Helper functions for extracting request data.
184     #
185     # Note: Those functions are not public API. If you want to mutate request
186     # data to your liking it's recommended to use the `before_send` callback
187     # for that.
188 
189     def _get_url(self, scope, default_scheme, host):
190         # type: (Dict[str, Any], Literal["ws", "http"], Optional[str]) -> str
191         """
192         Extract URL from the ASGI scope, without also including the querystring.
193         """
194         scheme = scope.get("scheme", default_scheme)
195 
196         server = scope.get("server", None)
197         path = scope.get("root_path", "") + scope.get("path", "")
198 
199         if host:
200             return "%s://%s%s" % (scheme, host, path)
201 
202         if server is not None:
203             host, port = server
204             default_port = {"http": 80, "https": 443, "ws": 80, "wss": 443}[scheme]
205             if port != default_port:
206                 return "%s://%s:%s%s" % (scheme, host, port, path)
207             return "%s://%s%s" % (scheme, host, path)
208         return path
209 
210     def _get_query(self, scope):
211         # type: (Any) -> Any
212         """
213         Extract querystring from the ASGI scope, in the format that the Sentry protocol expects.
214         """
215         qs = scope.get("query_string")
216         if not qs:
217             return None
218         return urllib.parse.unquote(qs.decode("latin-1"))
219 
220     def _get_headers(self, scope):
221         # type: (Any) -> Dict[str, str]
222         """
223         Extract headers from the ASGI scope, in the format that the Sentry protocol expects.
224         """
225         headers = {}  # type: Dict[str, str]
226         for raw_key, raw_value in scope["headers"]:
227             key = raw_key.decode("latin-1")
228             value = raw_value.decode("latin-1")
229             if key in headers:
230                 headers[key] = headers[key] + ", " + value
231             else:
232                 headers[key] = value
233         return headers
234
```
--- 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/asgi.py b/sentry_sdk/integrations/asgi.py
--- a/sentry_sdk/integrations/asgi.py
+++ b/sentry_sdk/integrations/asgi.py
@@ -107,8 +107,14 @@
 
     async def _run_app(self, scope, callback):
         # type: (Any, Any) -> Any
-        if _asgi_middleware_applied.get(False):
-            return await callback()
+        is_recursive_asgi_middleware = _asgi_middleware_applied.get(False)
+
+        if is_recursive_asgi_middleware:
+            try:
+                return await callback()
+            except Exception as exc:
+                _capture_exception(Hub.current, exc)
+                raise exc from None
 
         _asgi_middleware_applied.set(True)
         try:

{"golden_diff": "diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py\n--- a/sentry_sdk/integrations/asgi.py\n+++ b/sentry_sdk/integrations/asgi.py\n@@ -107,8 +107,14 @@\n \n     async def _run_app(self, scope, callback):\n         # type: (Any, Any) -> Any\n-        if _asgi_middleware_applied.get(False):\n-            return await callback()\n+        is_recursive_asgi_middleware = _asgi_middleware_applied.get(False)\n+\n+        if is_recursive_asgi_middleware:\n+            try:\n+                return await callback()\n+            except Exception as exc:\n+                _capture_exception(Hub.current, exc)\n+                raise exc from None\n \n         _asgi_middleware_applied.set(True)\n         try:\n", "issue": "last_event_id() returns None after SentryAsgiMiddleware.__call__() finished\nI'm unable to access `last_event_id` of `SentryAsgiMiddleware` on exception handler in `Starlette` framework.\r\n```python\r\nfrom sentry_sdk import last_event_id\r\nfrom sentry_sdk.integrations.asgi import SentryAsgiMiddleware\r\nfrom starlette.applications import Starlette\r\nfrom starlette.responses import JSONResponse\r\nfrom starlette.routing import Route\r\n\r\n\r\nasync def test_endpoint(request):\r\n    raise RuntimeError(\"test\")\r\n\r\n\r\ndef exception_handler(*args, **kwargs):\r\n    return JSONResponse({\"last_event_id\": last_event_id()})\r\n\r\n\r\napp = Starlette(\r\n    routes=[Route('/', test_endpoint)],\r\n    exception_handlers={\r\n        Exception: exception_handler,\r\n    }\r\n)\r\napp.add_middleware(SentryAsgiMiddleware)\r\n```  \r\n\r\nthe problem is probably with usage of Hub's context manager in `SentryAsgiMiddleware._run_app()` - after throwing exception you are clearing local `ContextVar` so `last_event_id` function tries to access wrong Hub instance.\n", "before_files": [{"content": "\"\"\"\nAn ASGI middleware.\n\nBased on Tom Christie's `sentry-asgi <https://github.com/encode/sentry-asgi>`_.\n\"\"\"\n\nimport asyncio\nimport inspect\nimport urllib\n\nfrom sentry_sdk._functools import partial\nfrom sentry_sdk._types import MYPY\nfrom sentry_sdk.hub import Hub, _should_send_default_pii\nfrom sentry_sdk.integrations._wsgi_common import _filter_headers\nfrom sentry_sdk.utils import (\n    ContextVar,\n    event_from_exception,\n    transaction_from_function,\n    HAS_REAL_CONTEXTVARS,\n    CONTEXTVARS_ERROR_MESSAGE,\n)\nfrom sentry_sdk.tracing import Transaction\n\nif MYPY:\n    from typing import Dict\n    from typing import Any\n    from typing import Optional\n    from typing import Callable\n\n    from typing_extensions import Literal\n\n    from sentry_sdk._types import Event, Hint\n\n\n_asgi_middleware_applied = ContextVar(\"sentry_asgi_middleware_applied\")\n\n_DEFAULT_TRANSACTION_NAME = \"generic ASGI request\"\n\n\ndef _capture_exception(hub, exc):\n    # type: (Hub, Any) -> None\n\n    # Check client here as it might have been unset while streaming response\n    if hub.client is not None:\n        event, hint = event_from_exception(\n            exc,\n            client_options=hub.client.options,\n            mechanism={\"type\": \"asgi\", \"handled\": False},\n        )\n        hub.capture_event(event, hint=hint)\n\n\ndef _looks_like_asgi3(app):\n    # type: (Any) -> bool\n    \"\"\"\n    Try to figure out if an application object supports ASGI3.\n\n    This is how uvicorn figures out the application version as well.\n    \"\"\"\n    if inspect.isclass(app):\n        return hasattr(app, \"__await__\")\n    elif inspect.isfunction(app):\n        return asyncio.iscoroutinefunction(app)\n    else:\n        call = getattr(app, \"__call__\", None)  # noqa\n        return asyncio.iscoroutinefunction(call)\n\n\nclass SentryAsgiMiddleware:\n    __slots__ = (\"app\", \"__call__\")\n\n    def __init__(self, app, unsafe_context_data=False):\n        # type: (Any, bool) -> None\n        \"\"\"\n        Instrument an ASGI application with Sentry. Provides HTTP/websocket\n        data to sent events and basic handling for exceptions bubbling up\n        through the middleware.\n\n        :param unsafe_context_data: Disable errors when a proper contextvars installation could not be found. We do not recommend changing this from the default.\n        \"\"\"\n\n        if not unsafe_context_data and not HAS_REAL_CONTEXTVARS:\n            # We better have contextvars or we're going to leak state between\n            # requests.\n            raise RuntimeError(\n                \"The ASGI middleware for Sentry requires Python 3.7+ \"\n                \"or the aiocontextvars package.\" + CONTEXTVARS_ERROR_MESSAGE\n            )\n        self.app = app\n\n        if _looks_like_asgi3(app):\n            self.__call__ = self._run_asgi3  # type: Callable[..., Any]\n        else:\n            self.__call__ = self._run_asgi2\n\n    def _run_asgi2(self, scope):\n        # type: (Any) -> Any\n        async def inner(receive, send):\n            # type: (Any, Any) -> Any\n            return await self._run_app(scope, lambda: self.app(scope)(receive, send))\n\n        return inner\n\n    async def _run_asgi3(self, scope, receive, send):\n        # type: (Any, Any, Any) -> Any\n        return await self._run_app(scope, lambda: self.app(scope, receive, send))\n\n    async def _run_app(self, scope, callback):\n        # type: (Any, Any) -> Any\n        if _asgi_middleware_applied.get(False):\n            return await callback()\n\n        _asgi_middleware_applied.set(True)\n        try:\n            hub = Hub(Hub.current)\n            with hub:\n                with hub.configure_scope() as sentry_scope:\n                    sentry_scope.clear_breadcrumbs()\n                    sentry_scope._name = \"asgi\"\n                    processor = partial(self.event_processor, asgi_scope=scope)\n                    sentry_scope.add_event_processor(processor)\n\n                ty = scope[\"type\"]\n\n                if ty in (\"http\", \"websocket\"):\n                    transaction = Transaction.continue_from_headers(\n                        dict(scope[\"headers\"]),\n                        op=\"{}.server\".format(ty),\n                    )\n                else:\n                    transaction = Transaction(op=\"asgi.server\")\n\n                transaction.name = _DEFAULT_TRANSACTION_NAME\n                transaction.set_tag(\"asgi.type\", ty)\n\n                with hub.start_transaction(transaction):\n                    # XXX: Would be cool to have correct span status, but we\n                    # would have to wrap send(). That is a bit hard to do with\n                    # the current abstraction over ASGI 2/3.\n                    try:\n                        return await callback()\n                    except Exception as exc:\n                        _capture_exception(hub, exc)\n                        raise exc from None\n        finally:\n            _asgi_middleware_applied.set(False)\n\n    def event_processor(self, event, hint, asgi_scope):\n        # type: (Event, Hint, Any) -> Optional[Event]\n        request_info = event.get(\"request\", {})\n\n        ty = asgi_scope[\"type\"]\n        if ty in (\"http\", \"websocket\"):\n            request_info[\"method\"] = asgi_scope.get(\"method\")\n            request_info[\"headers\"] = headers = _filter_headers(\n                self._get_headers(asgi_scope)\n            )\n            request_info[\"query_string\"] = self._get_query(asgi_scope)\n\n            request_info[\"url\"] = self._get_url(\n                asgi_scope, \"http\" if ty == \"http\" else \"ws\", headers.get(\"host\")\n            )\n\n        client = asgi_scope.get(\"client\")\n        if client and _should_send_default_pii():\n            request_info[\"env\"] = {\"REMOTE_ADDR\": client[0]}\n\n        if (\n            event.get(\"transaction\", _DEFAULT_TRANSACTION_NAME)\n            == _DEFAULT_TRANSACTION_NAME\n        ):\n            endpoint = asgi_scope.get(\"endpoint\")\n            # Webframeworks like Starlette mutate the ASGI env once routing is\n            # done, which is sometime after the request has started. If we have\n            # an endpoint, overwrite our generic transaction name.\n            if endpoint:\n                event[\"transaction\"] = transaction_from_function(endpoint)\n\n        event[\"request\"] = request_info\n\n        return event\n\n    # Helper functions for extracting request data.\n    #\n    # Note: Those functions are not public API. If you want to mutate request\n    # data to your liking it's recommended to use the `before_send` callback\n    # for that.\n\n    def _get_url(self, scope, default_scheme, host):\n        # type: (Dict[str, Any], Literal[\"ws\", \"http\"], Optional[str]) -> str\n        \"\"\"\n        Extract URL from the ASGI scope, without also including the querystring.\n        \"\"\"\n        scheme = scope.get(\"scheme\", default_scheme)\n\n        server = scope.get(\"server\", None)\n        path = scope.get(\"root_path\", \"\") + scope.get(\"path\", \"\")\n\n        if host:\n            return \"%s://%s%s\" % (scheme, host, path)\n\n        if server is not None:\n            host, port = server\n            default_port = {\"http\": 80, \"https\": 443, \"ws\": 80, \"wss\": 443}[scheme]\n            if port != default_port:\n                return \"%s://%s:%s%s\" % (scheme, host, port, path)\n            return \"%s://%s%s\" % (scheme, host, path)\n        return path\n\n    def _get_query(self, scope):\n        # type: (Any) -> Any\n        \"\"\"\n        Extract querystring from the ASGI scope, in the format that the Sentry protocol expects.\n        \"\"\"\n        qs = scope.get(\"query_string\")\n        if not qs:\n            return None\n        return urllib.parse.unquote(qs.decode(\"latin-1\"))\n\n    def _get_headers(self, scope):\n        # type: (Any) -> Dict[str, str]\n        \"\"\"\n        Extract headers from the ASGI scope, in the format that the Sentry protocol expects.\n        \"\"\"\n        headers = {}  # type: Dict[str, str]\n        for raw_key, raw_value in scope[\"headers\"]:\n            key = raw_key.decode(\"latin-1\")\n            value = raw_value.decode(\"latin-1\")\n            if key in headers:\n                headers[key] = headers[key] + \", \" + value\n            else:\n                headers[key] = value\n        return headers\n", "path": "sentry_sdk/integrations/asgi.py"}], "after_files": [{"content": "\"\"\"\nAn ASGI middleware.\n\nBased on Tom Christie's `sentry-asgi <https://github.com/encode/sentry-asgi>`_.\n\"\"\"\n\nimport asyncio\nimport inspect\nimport urllib\n\nfrom sentry_sdk._functools import partial\nfrom sentry_sdk._types import MYPY\nfrom sentry_sdk.hub import Hub, _should_send_default_pii\nfrom sentry_sdk.integrations._wsgi_common import _filter_headers\nfrom sentry_sdk.utils import (\n    ContextVar,\n    event_from_exception,\n    transaction_from_function,\n    HAS_REAL_CONTEXTVARS,\n    CONTEXTVARS_ERROR_MESSAGE,\n)\nfrom sentry_sdk.tracing import Transaction\n\nif MYPY:\n    from typing import Dict\n    from typing import Any\n    from typing import Optional\n    from typing import Callable\n\n    from typing_extensions import Literal\n\n    from sentry_sdk._types import Event, Hint\n\n\n_asgi_middleware_applied = ContextVar(\"sentry_asgi_middleware_applied\")\n\n_DEFAULT_TRANSACTION_NAME = \"generic ASGI request\"\n\n\ndef _capture_exception(hub, exc):\n    # type: (Hub, Any) -> None\n\n    # Check client here as it might have been unset while streaming response\n    if hub.client is not None:\n        event, hint = event_from_exception(\n            exc,\n            client_options=hub.client.options,\n            mechanism={\"type\": \"asgi\", \"handled\": False},\n        )\n        hub.capture_event(event, hint=hint)\n\n\ndef _looks_like_asgi3(app):\n    # type: (Any) -> bool\n    \"\"\"\n    Try to figure out if an application object supports ASGI3.\n\n    This is how uvicorn figures out the application version as well.\n    \"\"\"\n    if inspect.isclass(app):\n        return hasattr(app, \"__await__\")\n    elif inspect.isfunction(app):\n        return asyncio.iscoroutinefunction(app)\n    else:\n        call = getattr(app, \"__call__\", None)  # noqa\n        return asyncio.iscoroutinefunction(call)\n\n\nclass SentryAsgiMiddleware:\n    __slots__ = (\"app\", \"__call__\")\n\n    def __init__(self, app, unsafe_context_data=False):\n        # type: (Any, bool) -> None\n        \"\"\"\n        Instrument an ASGI application with Sentry. Provides HTTP/websocket\n        data to sent events and basic handling for exceptions bubbling up\n        through the middleware.\n\n        :param unsafe_context_data: Disable errors when a proper contextvars installation could not be found. We do not recommend changing this from the default.\n        \"\"\"\n\n        if not unsafe_context_data and not HAS_REAL_CONTEXTVARS:\n            # We better have contextvars or we're going to leak state between\n            # requests.\n            raise RuntimeError(\n                \"The ASGI middleware for Sentry requires Python 3.7+ \"\n                \"or the aiocontextvars package.\" + CONTEXTVARS_ERROR_MESSAGE\n            )\n        self.app = app\n\n        if _looks_like_asgi3(app):\n            self.__call__ = self._run_asgi3  # type: Callable[..., Any]\n        else:\n            self.__call__ = self._run_asgi2\n\n    def _run_asgi2(self, scope):\n        # type: (Any) -> Any\n        async def inner(receive, send):\n            # type: (Any, Any) -> Any\n            return await self._run_app(scope, lambda: self.app(scope)(receive, send))\n\n        return inner\n\n    async def _run_asgi3(self, scope, receive, send):\n        # type: (Any, Any, Any) -> Any\n        return await self._run_app(scope, lambda: self.app(scope, receive, send))\n\n    async def _run_app(self, scope, callback):\n        # type: (Any, Any) -> Any\n        is_recursive_asgi_middleware = _asgi_middleware_applied.get(False)\n\n        if is_recursive_asgi_middleware:\n            try:\n                return await callback()\n            except Exception as exc:\n                _capture_exception(Hub.current, exc)\n                raise exc from None\n\n        _asgi_middleware_applied.set(True)\n        try:\n            hub = Hub(Hub.current)\n            with hub:\n                with hub.configure_scope() as sentry_scope:\n                    sentry_scope.clear_breadcrumbs()\n                    sentry_scope._name = \"asgi\"\n                    processor = partial(self.event_processor, asgi_scope=scope)\n                    sentry_scope.add_event_processor(processor)\n\n                ty = scope[\"type\"]\n\n                if ty in (\"http\", \"websocket\"):\n                    transaction = Transaction.continue_from_headers(\n                        dict(scope[\"headers\"]),\n                        op=\"{}.server\".format(ty),\n                    )\n                else:\n                    transaction = Transaction(op=\"asgi.server\")\n\n                transaction.name = _DEFAULT_TRANSACTION_NAME\n                transaction.set_tag(\"asgi.type\", ty)\n\n                with hub.start_transaction(transaction):\n                    # XXX: Would be cool to have correct span status, but we\n                    # would have to wrap send(). That is a bit hard to do with\n                    # the current abstraction over ASGI 2/3.\n                    try:\n                        return await callback()\n                    except Exception as exc:\n                        _capture_exception(hub, exc)\n                        raise exc from None\n        finally:\n            _asgi_middleware_applied.set(False)\n\n    def event_processor(self, event, hint, asgi_scope):\n        # type: (Event, Hint, Any) -> Optional[Event]\n        request_info = event.get(\"request\", {})\n\n        ty = asgi_scope[\"type\"]\n        if ty in (\"http\", \"websocket\"):\n            request_info[\"method\"] = asgi_scope.get(\"method\")\n            request_info[\"headers\"] = headers = _filter_headers(\n                self._get_headers(asgi_scope)\n            )\n            request_info[\"query_string\"] = self._get_query(asgi_scope)\n\n            request_info[\"url\"] = self._get_url(\n                asgi_scope, \"http\" if ty == \"http\" else \"ws\", headers.get(\"host\")\n            )\n\n        client = asgi_scope.get(\"client\")\n        if client and _should_send_default_pii():\n            request_info[\"env\"] = {\"REMOTE_ADDR\": client[0]}\n\n        if (\n            event.get(\"transaction\", _DEFAULT_TRANSACTION_NAME)\n            == _DEFAULT_TRANSACTION_NAME\n        ):\n            endpoint = asgi_scope.get(\"endpoint\")\n            # Webframeworks like Starlette mutate the ASGI env once routing is\n            # done, which is sometime after the request has started. If we have\n            # an endpoint, overwrite our generic transaction name.\n            if endpoint:\n                event[\"transaction\"] = transaction_from_function(endpoint)\n\n        event[\"request\"] = request_info\n\n        return event\n\n    # Helper functions for extracting request data.\n    #\n    # Note: Those functions are not public API. If you want to mutate request\n    # data to your liking it's recommended to use the `before_send` callback\n    # for that.\n\n    def _get_url(self, scope, default_scheme, host):\n        # type: (Dict[str, Any], Literal[\"ws\", \"http\"], Optional[str]) -> str\n        \"\"\"\n        Extract URL from the ASGI scope, without also including the querystring.\n        \"\"\"\n        scheme = scope.get(\"scheme\", default_scheme)\n\n        server = scope.get(\"server\", None)\n        path = scope.get(\"root_path\", \"\") + scope.get(\"path\", \"\")\n\n        if host:\n            return \"%s://%s%s\" % (scheme, host, path)\n\n        if server is not None:\n            host, port = server\n            default_port = {\"http\": 80, \"https\": 443, \"ws\": 80, \"wss\": 443}[scheme]\n            if port != default_port:\n                return \"%s://%s:%s%s\" % (scheme, host, port, path)\n            return \"%s://%s%s\" % (scheme, host, path)\n        return path\n\n    def _get_query(self, scope):\n        # type: (Any) -> Any\n        \"\"\"\n        Extract querystring from the ASGI scope, in the format that the Sentry protocol expects.\n        \"\"\"\n        qs = scope.get(\"query_string\")\n        if not qs:\n            return None\n        return urllib.parse.unquote(qs.decode(\"latin-1\"))\n\n    def _get_headers(self, scope):\n        # type: (Any) -> Dict[str, str]\n        \"\"\"\n        Extract headers from the ASGI scope, in the format that the Sentry protocol expects.\n        \"\"\"\n        headers = {}  # type: Dict[str, str]\n        for raw_key, raw_value in scope[\"headers\"]:\n            key = raw_key.decode(\"latin-1\")\n            value = raw_value.decode(\"latin-1\")\n            if key in headers:\n                headers[key] = headers[key] + \", \" + value\n            else:\n                headers[key] = value\n        return headers\n", "path": "sentry_sdk/integrations/asgi.py"}]}