Skip to content

Commit 9724720

Browse files
committed
feat(profiling): Set active thread id for ASGI frameworks
When running in ASGI sync views, the transaction gets started in the main thread then the request is dispatched to a handler thread. We want to set the handler thread as the active thread id to ensure that profiles will show it on first render.
1 parent b1290c6 commit 9724720

File tree

5 files changed

+85
-14
lines changed

5 files changed

+85
-14
lines changed

sentry_sdk/integrations/django/views.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import threading
2+
13
from sentry_sdk.consts import OP
24
from sentry_sdk.hub import Hub
35
from sentry_sdk._types import MYPY
@@ -62,9 +64,14 @@ def _wrap_sync_view(hub, callback):
6264
@_functools.wraps(callback)
6365
def sentry_wrapped_callback(request, *args, **kwargs):
6466
# type: (Any, *Any, **Any) -> Any
65-
with hub.start_span(
66-
op=OP.VIEW_RENDER, description=request.resolver_match.view_name
67-
):
68-
return callback(request, *args, **kwargs)
67+
with hub.configure_scope() as sentry_scope:
68+
# set the active thread id to the handler thread for sync views
69+
# this isn't necessary for async views since that runs on main
70+
sentry_scope.set_active_thread_id(threading.current_thread().ident)
71+
72+
with hub.start_span(
73+
op=OP.VIEW_RENDER, description=request.resolver_match.view_name
74+
):
75+
return callback(request, *args, **kwargs)
6976

7077
return sentry_wrapped_callback

sentry_sdk/integrations/fastapi.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import threading
2+
13
from sentry_sdk._types import MYPY
24
from sentry_sdk.hub import Hub, _should_send_default_pii
35
from sentry_sdk.integrations import DidNotEnable
@@ -62,6 +64,21 @@ def patch_get_request_handler():
6264

6365
def _sentry_get_request_handler(*args, **kwargs):
6466
# type: (*Any, **Any) -> Any
67+
dependant = kwargs.get("dependant")
68+
if dependant:
69+
old_call = dependant.call
70+
71+
def _sentry_call(*args, **kwargs):
72+
# type: (*Any, **Any) -> Any
73+
hub = Hub.current
74+
with hub.configure_scope() as sentry_scope:
75+
# set the active thread id to the handler thread for sync views
76+
# this isn't necessary for async views since that runs on main
77+
sentry_scope.set_active_thread_id(threading.current_thread().ident)
78+
return old_call(*args, **kwargs)
79+
80+
dependant.call = _sentry_call
81+
6582
old_app = old_get_request_handler(*args, **kwargs)
6683

6784
async def _sentry_app(*args, **kwargs):

sentry_sdk/integrations/quart.py

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import absolute_import
22

3+
import threading
4+
35
from sentry_sdk.hub import _should_send_default_pii, Hub
46
from sentry_sdk.integrations import DidNotEnable, Integration
57
from sentry_sdk.integrations._wsgi_common import _filter_headers
@@ -11,6 +13,7 @@
1113
event_from_exception,
1214
)
1315

16+
from sentry_sdk._functools import wraps
1417
from sentry_sdk._types import MYPY
1518

1619
if MYPY:
@@ -34,6 +37,7 @@
3437
request,
3538
websocket,
3639
)
40+
from quart.scaffold import Scaffold # type: ignore
3741
from quart.signals import ( # type: ignore
3842
got_background_exception,
3943
got_request_exception,
@@ -71,18 +75,56 @@ def setup_once():
7175
got_request_exception.connect(_capture_exception)
7276
got_websocket_exception.connect(_capture_exception)
7377

74-
old_app = Quart.__call__
78+
patch_asgi_app()
79+
patch_scaffold_route()
80+
81+
82+
def patch_asgi_app():
83+
# type: () -> None
84+
old_app = Quart.__call__
85+
86+
async def sentry_patched_asgi_app(self, scope, receive, send):
87+
# type: (Any, Any, Any, Any) -> Any
88+
if Hub.current.get_integration(QuartIntegration) is None:
89+
return await old_app(self, scope, receive, send)
90+
91+
middleware = SentryAsgiMiddleware(lambda *a, **kw: old_app(self, *a, **kw))
92+
middleware.__call__ = middleware._run_asgi3
93+
return await middleware(scope, receive, send)
94+
95+
Quart.__call__ = sentry_patched_asgi_app
96+
97+
98+
def patch_scaffold_route():
99+
# type: () -> None
100+
old_route = Scaffold.route
101+
102+
def _sentry_route(*args, **kwargs):
103+
# type: (*Any, **Any) -> Any
104+
old_decorator = old_route(*args, **kwargs)
105+
106+
def decorator(old_func):
107+
# type: (Any) -> Any
108+
@wraps(old_func)
109+
def _sentry_func(*args, **kwargs):
110+
# type: (*Any, **Any) -> Any
111+
hub = Hub.current
112+
integration = hub.get_integration(QuartIntegration)
113+
if integration is None:
114+
return old_func(*args, **kwargs)
115+
116+
with hub.configure_scope() as sentry_scope:
117+
# set the active thread id to the handler thread for sync views
118+
# this isn't necessary for async views since that runs on main
119+
sentry_scope.set_active_thread_id(threading.current_thread().ident)
120+
121+
return old_func(*args, **kwargs)
75122

76-
async def sentry_patched_asgi_app(self, scope, receive, send):
77-
# type: (Any, Any, Any, Any) -> Any
78-
if Hub.current.get_integration(QuartIntegration) is None:
79-
return await old_app(self, scope, receive, send)
123+
return old_decorator(_sentry_func)
80124

81-
middleware = SentryAsgiMiddleware(lambda *a, **kw: old_app(self, *a, **kw))
82-
middleware.__call__ = middleware._run_asgi3
83-
return await middleware(scope, receive, send)
125+
return decorator
84126

85-
Quart.__call__ = sentry_patched_asgi_app
127+
Scaffold.route = _sentry_route
86128

87129

88130
def _set_transaction_name_and_source(scope, transaction_style, request):

sentry_sdk/integrations/starlette.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import asyncio
44
import functools
5+
import threading
56

67
from sentry_sdk._compat import iteritems
78
from sentry_sdk._types import MYPY
@@ -403,6 +404,10 @@ def _sentry_sync_func(*args, **kwargs):
403404
return old_func(*args, **kwargs)
404405

405406
with hub.configure_scope() as sentry_scope:
407+
# set the active thread id to the handler thread for sync views
408+
# this isn't necessary for async views since that runs on main
409+
sentry_scope.set_active_thread_id(threading.current_thread().ident)
410+
406411
request = args[0]
407412

408413
_set_transaction_name_and_source(

tests/integrations/wsgi/test_wsgi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,8 @@ def sample_app(environ, start_response):
297297
],
298298
)
299299
def test_profile_sent(
300-
capture_envelopes,
301300
sentry_init,
301+
capture_envelopes,
302302
teardown_profiling,
303303
profiles_sample_rate,
304304
profile_count,

0 commit comments

Comments
 (0)