Repository: getsentry/sentry-python

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.


Discussion:
Can you try replacing the last line with `app = SentryAsgiMiddleware(app)`? (and move it as far down the file as necessary)
still doesn't work, response shows `{"last_event_id": null}` and if I add 
```python
                        from sentry_sdk import last_event_id
                        print(last_event_id())
```
to line https://github.com/getsentry/sentry-python/blob/master/sentry_sdk/integrations/asgi.py#L144 it prints proper event id to console but response still shows null.
Ok but if you wrap your entire app in that middleware, surely the exception handler runs also in the context manager? not sure what's going on
here is the full example to reproduce this issue:

```python
from sentry_sdk import last_event_id, init
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(request, exc):
    return JSONResponse({"last_event_id": last_event_id()})


init(dsn='***')

app = Starlette(
    routes=[Route('/', test_endpoint)],
    exception_handlers={
        Exception: exception_handler,
    }
)
app = SentryAsgiMiddleware(app)
```
Probably if you wrap whole starlette app into `SentryAsgiMiddleware` (as in example above) the exception handler does not see last_event_id as it is generated later, in SentryAsgiMiddleware exception catch block.
oh damn you're right. I suppose the workaround for now is to do `capture_exception` in your exception handler, but... yeah. I think that's just how it works if you are a generic asgi middleware and don't know about errorhandlers.

If you know a way to restructure the asgi middleware to enable this usecase I am all ears though
Actually I have an idea, give me a couple hours

Resolving patch:
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_asgi3(self, scope, receive, send):
 
     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:


Test patch:
diff --git a/tests/integrations/asgi/test_asgi.py b/tests/integrations/asgi/test_asgi.py
--- a/tests/integrations/asgi/test_asgi.py
+++ b/tests/integrations/asgi/test_asgi.py
@@ -1,7 +1,7 @@
 import sys
 
 import pytest
-from sentry_sdk import Hub, capture_message
+from sentry_sdk import Hub, capture_message, last_event_id
 from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
 from starlette.applications import Starlette
 from starlette.responses import PlainTextResponse
@@ -179,3 +179,26 @@ async def app(scope, receive, send):
             "url": "ws://testserver/",
         }
     )
+
+
+def test_starlette_last_event_id(app, sentry_init, capture_events, request):
+    sentry_init(send_default_pii=True)
+    events = capture_events()
+
+    @app.route("/handlederror")
+    def handlederror(request):
+        raise ValueError("oh no")
+
+    @app.exception_handler(500)
+    def handler(*args, **kwargs):
+        return PlainTextResponse(last_event_id(), status_code=500)
+
+    client = TestClient(SentryAsgiMiddleware(app), raise_server_exceptions=False)
+    response = client.get("/handlederror")
+    assert response.status_code == 500
+
+    (event,) = events
+    assert response.content.strip().decode("ascii") == event["event_id"]
+    (exception,) = event["exception"]["values"]
+    assert exception["type"] == "ValueError"
+    assert exception["value"] == "oh no"