sentry-python
sentry-python copied to clipboard
asyncio.gather tracing produces nested spans
I've got the following code:
import asyncio
import sentry_sdk
async def foo():
with sentry_sdk.start_span(op="foo"):
await asyncio.sleep(1)
async def bar():
with sentry_sdk.start_span(op="bar"):
await asyncio.sleep(1)
with sentry_sdk.start_span(op="root"):
await asyncio.gather(foo(), bar(), return_exceptions=True)
I see that bar
is nested under foo
, and foo
is nested under root
in the Performance UI.
root
|- foo
|- bar
Expected behavior: both foo
and bar
are nested under root
and are on the same level.
root
|- foo
|- bar
yes, our asyncio support is somewhat limited at the moment. As a workaround you can do:
import asyncio
import sentry_sdk
async def foo():
with sentry_sdk.Hub(sentry_sdk.Hub.current):
with sentry_sdk.start_span(op="foo"):
await asyncio.sleep(1)
async def bar():
with sentry_sdk.Hub(sentry_sdk.Hub.current):
with sentry_sdk.start_span(op="bar"):
await asyncio.sleep(1)
with sentry_sdk.start_span(op="root"):
await asyncio.gather(asyncio.create_task(foo()), asyncio.create_task(bar()), return_exceptions=True)
I am not sure if create_task
is strictly necessary or if gather
does that for you. If gather
already creates independent tasks then we can potentially handle this case transparently.
Yes, asyncio.gather
launches new Task
-s, via https://docs.python.org/3/library/asyncio-task.html#asyncio.gather
If any awaitable in aws is a coroutine, it is automatically scheduled as a Task.
All right, thanks for the workaround!
@untitaker do you have any plans to add this as first-class support? we are heavy users of this asyncio and this creates unreadable traces
@yoav-orca not at the moment. It would also require heavy monkeypatching of the asyncio runtime afaik
We're currently looking into this and as far as I can tell we can tell asyncio to use a custom task factory which will wrap all tasks with hub cloning and that should fix this issue.
https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.set_task_factory
We just have to be careful to take into account if there's another factory override and be good citizens on our end in both cases (default/existing custom factory).
I have now created a reproduction repo: https://github.com/getsentry/minimum_python_async/blob/main/task1.py
And I created a new AsyncioIntegration
that produces now this output:
Which is the output we want to have :)