New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(profiling): Set active thread id for ASGI frameworks #1778
Changes from all commits
9724720
0779d0e
29bceca
b486628
48d5cb5
da818e0
1950d78
a11d30b
cc43016
9521fbf
ccb2381
57f5489
5e54692
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
import asyncio | ||
import threading | ||
|
||
from sentry_sdk._types import MYPY | ||
from sentry_sdk.hub import Hub, _should_send_default_pii | ||
from sentry_sdk.integrations import DidNotEnable | ||
|
@@ -62,6 +65,21 @@ def patch_get_request_handler(): | |
|
||
def _sentry_get_request_handler(*args, **kwargs): | ||
# type: (*Any, **Any) -> Any | ||
dependant = kwargs.get("dependant") | ||
if dependant and not asyncio.iscoroutinefunction(dependant.call): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it safe to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes I think so. @antonpirker knows this stuff better since he worked on it. |
||
old_call = dependant.call | ||
|
||
def _sentry_call(*args, **kwargs): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is setting the thread in fastapi still necessary if we already do it in |
||
# type: (*Any, **Any) -> Any | ||
hub = Hub.current | ||
with hub.configure_scope() as sentry_scope: | ||
# set the active thread id to the handler thread for sync views | ||
# this isn't necessary for async views since that runs on main | ||
sentry_scope.set_active_thread_id(threading.current_thread().ident) | ||
return old_call(*args, **kwargs) | ||
|
||
dependant.call = _sentry_call | ||
|
||
old_app = old_get_request_handler(*args, **kwargs) | ||
|
||
async def _sentry_app(*args, **kwargs): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,8 @@ | ||
from __future__ import absolute_import | ||
|
||
import inspect | ||
import threading | ||
|
||
from sentry_sdk.hub import _should_send_default_pii, Hub | ||
from sentry_sdk.integrations import DidNotEnable, Integration | ||
from sentry_sdk.integrations._wsgi_common import _filter_headers | ||
|
@@ -11,6 +14,7 @@ | |
event_from_exception, | ||
) | ||
|
||
from sentry_sdk._functools import wraps | ||
from sentry_sdk._types import MYPY | ||
|
||
if MYPY: | ||
|
@@ -34,13 +38,15 @@ | |
request, | ||
websocket, | ||
) | ||
from quart.scaffold import Scaffold # type: ignore | ||
from quart.signals import ( # type: ignore | ||
got_background_exception, | ||
got_request_exception, | ||
got_websocket_exception, | ||
request_started, | ||
websocket_started, | ||
) | ||
from quart.utils import is_coroutine_function # type: ignore | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was introduced in quart==0.11.1, do we need to support older versions? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nope, tox says min 0.16.1, so all good |
||
except ImportError: | ||
raise DidNotEnable("Quart is not installed") | ||
|
||
|
@@ -71,18 +77,63 @@ def setup_once(): | |
got_request_exception.connect(_capture_exception) | ||
got_websocket_exception.connect(_capture_exception) | ||
|
||
old_app = Quart.__call__ | ||
patch_asgi_app() | ||
patch_scaffold_route() | ||
|
||
|
||
def patch_asgi_app(): | ||
# type: () -> None | ||
old_app = Quart.__call__ | ||
|
||
async def sentry_patched_asgi_app(self, scope, receive, send): | ||
# type: (Any, Any, Any, Any) -> Any | ||
if Hub.current.get_integration(QuartIntegration) is None: | ||
return await old_app(self, scope, receive, send) | ||
|
||
middleware = SentryAsgiMiddleware(lambda *a, **kw: old_app(self, *a, **kw)) | ||
middleware.__call__ = middleware._run_asgi3 | ||
return await middleware(scope, receive, send) | ||
|
||
Quart.__call__ = sentry_patched_asgi_app | ||
|
||
|
||
def patch_scaffold_route(): | ||
# type: () -> None | ||
old_route = Scaffold.route | ||
|
||
def _sentry_route(*args, **kwargs): | ||
# type: (*Any, **Any) -> Any | ||
old_decorator = old_route(*args, **kwargs) | ||
|
||
def decorator(old_func): | ||
# type: (Any) -> Any | ||
|
||
if inspect.isfunction(old_func) and not is_coroutine_function(old_func): | ||
|
||
@wraps(old_func) | ||
def _sentry_func(*args, **kwargs): | ||
# type: (*Any, **Any) -> Any | ||
hub = Hub.current | ||
integration = hub.get_integration(QuartIntegration) | ||
if integration is None: | ||
return old_func(*args, **kwargs) | ||
|
||
with hub.configure_scope() as sentry_scope: | ||
# set the active thread id to the handler thread for sync views | ||
# this isn't necessary for async views since that runs on main | ||
sentry_scope.set_active_thread_id( | ||
threading.current_thread().ident | ||
) | ||
|
||
return old_func(*args, **kwargs) | ||
|
||
return old_decorator(_sentry_func) | ||
|
||
async def sentry_patched_asgi_app(self, scope, receive, send): | ||
# type: (Any, Any, Any, Any) -> Any | ||
if Hub.current.get_integration(QuartIntegration) is None: | ||
return await old_app(self, scope, receive, send) | ||
return old_decorator(old_func) | ||
|
||
middleware = SentryAsgiMiddleware(lambda *a, **kw: old_app(self, *a, **kw)) | ||
middleware.__call__ = middleware._run_asgi3 | ||
return await middleware(scope, receive, send) | ||
return decorator | ||
|
||
Quart.__call__ = sentry_patched_asgi_app | ||
Scaffold.route = _sentry_route | ||
|
||
|
||
def _set_transaction_name_and_source(scope, transaction_style, request): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will enable profiling for all the ASGI frameworks as well. This should be removed in #1797 as that will enable it for all transactions.