Repository navigation
Description
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
linear-code commentedon Nov 7, 2026
PY-3062
AboodFares commentedon Nov 8, 2026
Hi maintainers, I’m interested in working on this issue. Could I open a PR for it?
sentrivana commentedon Nov 9, 2026
Hey @ryanhiizy, thanks for the bug report. Feel free to open a PR with what you have on your branch, thank you!
sentrivana commentedon Nov 9, 2026
@AboodFares Since OP already has a branch ready with the fix, let's not duplicate work here, but thank you!