diff --git a/sentry_sdk/integrations/asyncio.py b/sentry_sdk/integrations/asyncio.py index c18089a492..2c61b85962 100644 --- a/sentry_sdk/integrations/asyncio.py +++ b/sentry_sdk/integrations/asyncio.py @@ -1,9 +1,12 @@ from __future__ import absolute_import +import sys +from sentry_sdk._compat import reraise from sentry_sdk.consts import OP from sentry_sdk.hub import Hub from sentry_sdk.integrations import Integration, DidNotEnable from sentry_sdk._types import MYPY +from sentry_sdk.utils import event_from_exception try: import asyncio @@ -15,6 +18,8 @@ if MYPY: from typing import Any + from sentry_sdk._types import ExcInfo + def patch_asyncio(): # type: () -> None @@ -31,7 +36,10 @@ async def _coro_creating_hub_and_span(): hub = Hub(Hub.current) with hub: with hub.start_span(op=OP.FUNCTION, description=coro.__qualname__): - await coro + try: + await coro + except Exception: + reraise(*_capture_exception(hub)) # Trying to use user set task factory (if there is one) if orig_task_factory: @@ -56,6 +64,25 @@ async def _coro_creating_hub_and_span(): pass +def _capture_exception(hub): + # type: (Hub) -> ExcInfo + exc_info = sys.exc_info() + + integration = hub.get_integration(AsyncioIntegration) + if integration is not None: + # If an integration is there, a client has to be there. + client = hub.client # type: Any + + event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "asyncio", "handled": False}, + ) + hub.capture_event(event, hint=hint) + + return exc_info + + class AsyncioIntegration(Integration): identifier = "asyncio" diff --git a/tests/integrations/asyncio/test_asyncio.py b/tests/integrations/asyncio/test_asyncio.py index 2e0643c4d2..380c614f65 100644 --- a/tests/integrations/asyncio/test_asyncio.py +++ b/tests/integrations/asyncio/test_asyncio.py @@ -22,6 +22,10 @@ async def bar(): await asyncio.sleep(0.01) +async def boom(): + 1 / 0 + + @pytest_asyncio.fixture(scope="session") def event_loop(request): """Create an instance of the default event loop for each test case.""" @@ -116,3 +120,38 @@ async def test_gather( transaction_event["spans"][2]["parent_span_id"] == transaction_event["spans"][0]["span_id"] ) + + +@minimum_python_36 +@pytest.mark.asyncio +async def test_exception( + sentry_init, + capture_events, + event_loop, +): + sentry_init( + traces_sample_rate=1.0, + send_default_pii=True, + debug=True, + integrations=[ + AsyncioIntegration(), + ], + ) + + events = capture_events() + + with sentry_sdk.start_transaction(name="test_exception"): + with sentry_sdk.start_span(op="root", description="not so important"): + tasks = [event_loop.create_task(boom()), event_loop.create_task(bar())] + await asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION) + + sentry_sdk.flush() + + (error_event, _) = events + + assert error_event["transaction"] == "test_exception" + assert error_event["contexts"]["trace"]["op"] == "function" + assert error_event["exception"]["values"][0]["type"] == "ZeroDivisionError" + assert error_event["exception"]["values"][0]["value"] == "division by zero" + assert error_event["exception"]["values"][0]["mechanism"]["handled"] is False + assert error_event["exception"]["values"][0]["mechanism"]["type"] == "asyncio"