11import sys
2+ import signal
23
34import sentry_sdk
45from sentry_sdk .consts import OP
56from sentry_sdk .integrations import Integration , DidNotEnable
6- from sentry_sdk .utils import event_from_exception , reraise
7+ from sentry_sdk .utils import event_from_exception , logger , reraise
78
89try :
910 import asyncio
1011 from asyncio .tasks import Task
1112except ImportError :
1213 raise DidNotEnable ("asyncio not available" )
1314
14- from typing import TYPE_CHECKING
15+ from typing import cast , TYPE_CHECKING
1516
1617if TYPE_CHECKING :
1718 from typing import Any
@@ -36,10 +37,26 @@ def patch_asyncio():
3637 loop = asyncio .get_running_loop ()
3738 orig_task_factory = loop .get_task_factory ()
3839
40+ # Add a shutdown handler to log a helpful message
41+ def shutdown_handler ():
42+ # type: () -> None
43+ logger .info (
44+ "AsyncIO is shutting down. If you see 'Task was destroyed but it is pending!' "
45+ "errors with '_task_with_sentry_span_creation', these are normal during shutdown "
46+ "and not a problem with your code or Sentry."
47+ )
48+
49+ try :
50+ loop .add_signal_handler (signal .SIGINT , shutdown_handler )
51+ loop .add_signal_handler (signal .SIGTERM , shutdown_handler )
52+ except (NotImplementedError , AttributeError ):
53+ # Signal handlers might not be supported on all platforms
54+ pass
55+
3956 def _sentry_task_factory (loop , coro , ** kwargs ):
4057 # type: (asyncio.AbstractEventLoop, Coroutine[Any, Any, Any], Any) -> asyncio.Future[Any]
4158
42- async def _coro_creating_hub_and_span ():
59+ async def _task_with_sentry_span_creation ():
4360 # type: () -> Any
4461 result = None
4562
@@ -56,27 +73,47 @@ async def _coro_creating_hub_and_span():
5673
5774 return result
5875
76+ task = None
77+
5978 # Trying to use user set task factory (if there is one)
6079 if orig_task_factory :
61- return orig_task_factory (loop , _coro_creating_hub_and_span (), ** kwargs )
62-
63- # The default task factory in `asyncio` does not have its own function
64- # but is just a couple of lines in `asyncio.base_events.create_task()`
65- # Those lines are copied here.
66-
67- # WARNING:
68- # If the default behavior of the task creation in asyncio changes,
69- # this will break!
70- task = Task (_coro_creating_hub_and_span (), loop = loop , ** kwargs )
71- if task ._source_traceback : # type: ignore
72- del task ._source_traceback [- 1 ] # type: ignore
80+ task = orig_task_factory (
81+ loop , _task_with_sentry_span_creation (), ** kwargs
82+ )
83+
84+ if task is None :
85+ # The default task factory in `asyncio` does not have its own function
86+ # but is just a couple of lines in `asyncio.base_events.create_task()`
87+ # Those lines are copied here.
88+
89+ # WARNING:
90+ # If the default behavior of the task creation in asyncio changes,
91+ # this will break!
92+ task = Task (_task_with_sentry_span_creation (), loop = loop , ** kwargs )
93+ if task ._source_traceback : # type: ignore
94+ del task ._source_traceback [- 1 ] # type: ignore
95+
96+ # Set the task name to include the original coroutine's name
97+ try :
98+ cast ("asyncio.Task[Any]" , task ).set_name (
99+ f"{ get_name (coro )} (Sentry-wrapped)"
100+ )
101+ except AttributeError :
102+ # set_name might not be available in all Python versions
103+ pass
73104
74105 return task
75106
76107 loop .set_task_factory (_sentry_task_factory ) # type: ignore
108+
77109 except RuntimeError :
78110 # When there is no running loop, we have nothing to patch.
79- pass
111+ logger .warning (
112+ "There is no running asyncio loop so there is nothing Sentry can patch. "
113+ "Please make sure you call sentry_sdk.init() within a running "
114+ "asyncio loop for the AsyncioIntegration to work. "
115+ "See https://docs.sentry.io/platforms/python/integrations/asyncio/"
116+ )
80117
81118
82119def _capture_exception ():
0 commit comments