sentry-python icon indicating copy to clipboard operation
sentry-python copied to clipboard

asyncio.gather tracing produces nested spans

Open vmarkovtsev opened this issue 4 years ago • 6 comments

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

vmarkovtsev avatar Jul 30 '20 21:07 vmarkovtsev

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.

untitaker avatar Jul 31 '20 10:07 untitaker

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!

vmarkovtsev avatar Jul 31 '20 12:07 vmarkovtsev

@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 avatar Dec 13 '20 08:12 yoav-orca

@yoav-orca not at the moment. It would also require heavy monkeypatching of the asyncio runtime afaik

untitaker avatar Dec 15 '20 10:12 untitaker

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).

sl0thentr0py avatar Oct 06 '22 13:10 sl0thentr0py

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: Image

Which is the output we want to have :)

antonpirker avatar Oct 10 '22 15:10 antonpirker