about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/sentry_sdk/integrations/asyncio.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/sentry_sdk/integrations/asyncio.py')
-rw-r--r--.venv/lib/python3.12/site-packages/sentry_sdk/integrations/asyncio.py144
1 files changed, 144 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/sentry_sdk/integrations/asyncio.py b/.venv/lib/python3.12/site-packages/sentry_sdk/integrations/asyncio.py
new file mode 100644
index 00000000..9326c16e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sentry_sdk/integrations/asyncio.py
@@ -0,0 +1,144 @@
+import sys
+import signal
+
+import sentry_sdk
+from sentry_sdk.consts import OP
+from sentry_sdk.integrations import Integration, DidNotEnable
+from sentry_sdk.utils import event_from_exception, logger, reraise
+
+try:
+    import asyncio
+    from asyncio.tasks import Task
+except ImportError:
+    raise DidNotEnable("asyncio not available")
+
+from typing import cast, TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from typing import Any
+    from collections.abc import Coroutine
+
+    from sentry_sdk._types import ExcInfo
+
+
+def get_name(coro):
+    # type: (Any) -> str
+    return (
+        getattr(coro, "__qualname__", None)
+        or getattr(coro, "__name__", None)
+        or "coroutine without __name__"
+    )
+
+
+def patch_asyncio():
+    # type: () -> None
+    orig_task_factory = None
+    try:
+        loop = asyncio.get_running_loop()
+        orig_task_factory = loop.get_task_factory()
+
+        # Add a shutdown handler to log a helpful message
+        def shutdown_handler():
+            # type: () -> None
+            logger.info(
+                "AsyncIO is shutting down. If you see 'Task was destroyed but it is pending!' "
+                "errors with '_task_with_sentry_span_creation', these are normal during shutdown "
+                "and not a problem with your code or Sentry."
+            )
+
+        try:
+            loop.add_signal_handler(signal.SIGINT, shutdown_handler)
+            loop.add_signal_handler(signal.SIGTERM, shutdown_handler)
+        except (NotImplementedError, AttributeError):
+            # Signal handlers might not be supported on all platforms
+            pass
+
+        def _sentry_task_factory(loop, coro, **kwargs):
+            # type: (asyncio.AbstractEventLoop, Coroutine[Any, Any, Any], Any) -> asyncio.Future[Any]
+
+            async def _task_with_sentry_span_creation():
+                # type: () -> Any
+                result = None
+
+                with sentry_sdk.isolation_scope():
+                    with sentry_sdk.start_span(
+                        op=OP.FUNCTION,
+                        name=get_name(coro),
+                        origin=AsyncioIntegration.origin,
+                    ):
+                        try:
+                            result = await coro
+                        except Exception:
+                            reraise(*_capture_exception())
+
+                return result
+
+            task = None
+
+            # Trying to use user set task factory (if there is one)
+            if orig_task_factory:
+                task = orig_task_factory(
+                    loop, _task_with_sentry_span_creation(), **kwargs
+                )
+
+            if task is None:
+                # The default task factory in `asyncio` does not have its own function
+                # but is just a couple of lines in `asyncio.base_events.create_task()`
+                # Those lines are copied here.
+
+                # WARNING:
+                # If the default behavior of the task creation in asyncio changes,
+                # this will break!
+                task = Task(_task_with_sentry_span_creation(), loop=loop, **kwargs)
+                if task._source_traceback:  # type: ignore
+                    del task._source_traceback[-1]  # type: ignore
+
+            # Set the task name to include the original coroutine's name
+            try:
+                cast("asyncio.Task[Any]", task).set_name(
+                    f"{get_name(coro)} (Sentry-wrapped)"
+                )
+            except AttributeError:
+                # set_name might not be available in all Python versions
+                pass
+
+            return task
+
+        loop.set_task_factory(_sentry_task_factory)  # type: ignore
+
+    except RuntimeError:
+        # When there is no running loop, we have nothing to patch.
+        logger.warning(
+            "There is no running asyncio loop so there is nothing Sentry can patch. "
+            "Please make sure you call sentry_sdk.init() within a running "
+            "asyncio loop for the AsyncioIntegration to work. "
+            "See https://docs.sentry.io/platforms/python/integrations/asyncio/"
+        )
+
+
+def _capture_exception():
+    # type: () -> ExcInfo
+    exc_info = sys.exc_info()
+
+    client = sentry_sdk.get_client()
+
+    integration = client.get_integration(AsyncioIntegration)
+    if integration is not None:
+        event, hint = event_from_exception(
+            exc_info,
+            client_options=client.options,
+            mechanism={"type": "asyncio", "handled": False},
+        )
+        sentry_sdk.capture_event(event, hint=hint)
+
+    return exc_info
+
+
+class AsyncioIntegration(Integration):
+    identifier = "asyncio"
+    origin = f"auto.function.{identifier}"
+
+    @staticmethod
+    def setup_once():
+        # type: () -> None
+        patch_asyncio()