Skip to content

Query source is never set on DB spans from async SQLAlchemy #8205

Open

Description

@ryanhiizy

How do you use Sentry?

Sentry Saas (sentry.io)

Version

2.74.0 (also 2.73.0)

Steps to Reproduce

Query source (code.function, code.namespace, code.filepath, code.lineno) is never set on DB spans from SQLAlchemy's asyncio extension, though it is for the same query run synchronously.

SQLAlchemy 2.1.8, aiosqlite 0.22.1, greenlet 3.5.6, Python 3.13:

import asyncio

import sentry_sdk
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
from sqlalchemy import create_engine, text
from sqlalchemy.ext.asyncio import create_async_engine

spans = []
sentry_sdk.init(
    dsn="https://public@example.invalid/1",
    traces_sample_rate=1.0,
    integrations=[SqlalchemyIntegration()],
    db_query_source_threshold_ms=0,
    before_send_transaction=lambda event, hint: spans.extend(event["spans"]),
)


def sync_query():
    with create_engine("sqlite://").connect() as connection:
        connection.execute(text("SELECT 1"))


async def async_query():
    engine = create_async_engine("sqlite+aiosqlite://")
    async with engine.connect() as connection:
        await connection.execute(text("SELECT 1"))
    await engine.dispose()


with sentry_sdk.start_transaction(name="sync"):
    sync_query()
with sentry_sdk.start_transaction(name="async"):
    asyncio.run(async_query())
sentry_sdk.flush()

for span in spans:
    if span.get("op") == "db":
        print(span["description"], "->", span["data"].get("code.function"))

Expected Result

SELECT 1 -> sync_query
SELECT 1 -> async_query

Actual Result

SELECT 1 -> sync_query
SELECT 1 -> None

The same happens with trace_lifecycle="stream".

SQLAlchemy's asyncio extension runs the sync driver inside a greenlet that greenlet_spawn starts from the awaiting coroutine (sqlalchemy.util._concurrency_py3k), so the cursor events that create the span fire inside that greenlet. add_source (sentry_sdk/tracing_utils.py) walks f_back from the current frame, and a greenlet's frame chain ends at its own entry point. Every frame it sees belongs to SQLAlchemy or the SDK, so it finds no in-app frame. The application frames are in the parent greenlet, suspended at the switch() in greenlet_spawn.

One possible fix: when the current frame chain runs out, continue the walk into greenlet.getcurrent().parent.gr_frame (only when greenlet is already imported, one parent deep, so a gevent hub's parent chain is never reached). I have this on a branch, with a test that runs a sync engine's execute in a child greenlet the way greenlet_spawn does: master...ryanhiizy:sentry-python:fix/query-source-greenlet-parent. I'm happy to open it as a PR if that approach suits you.

Written by Opus 5.5.

Activity

  1. linear-code commented on Nov 7, 2026

    @linear-code
  2. AboodFares commented on Nov 8, 2026

    @AboodFares

    Hi maintainers, I’m interested in working on this issue. Could I open a PR for it?

  3. sentrivana commented on Nov 9, 2026

    @sentrivana
    Collaborator

    Hey @ryanhiizy, thanks for the bug report. Feel free to open a PR with what you have on your branch, thank you!

  4. sentrivana commented on Nov 9, 2026

    @sentrivana
    Collaborator

    @AboodFares Since OP already has a branch ready with the fix, let's not duplicate work here, but thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions