Describe your environment
opentelemetry-instrumentation-dbapi 0.62b1
opentelemetry-instrumentation-psycopg 0.62b1
- psycopg 3 with
AsyncConnection / AsyncConnectionPool
- Python 3.14
What happened?
Spans are still created (and exported) for queries executed inside
opentelemetry.instrumentation.utils.suppress_instrumentation() when the
cursor is async. The sync path correctly suppresses.
Steps to Reproduce
from opentelemetry.instrumentation.psycopg import PsycopgInstrumentor
from opentelemetry.instrumentation.utils import suppress_instrumentation
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
from psycopg import AsyncConnection
exporter = InMemorySpanExporter()
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(exporter))
PsycopgInstrumentor().instrument(tracer_provider=provider)
async def main():
async with await AsyncConnection.connect(DSN) as conn:
async with conn.cursor() as cur:
with suppress_instrumentation():
await cur.execute("SELECT 1")
# after running main():
assert exporter.get_finished_spans() == () # FAILS — one span was exported
The same code with a sync psycopg.connect() passes.
Expected Result
No span — the async path should honor suppression exactly as the sync path
does.
Actual Result
One span was created and exported for the SELECT 1 executed inside suppress_instrumentation() — exporter.get_finished_spans() returns a span named SELECT with db.statement = "SELECT 1", and the final assertion in the reproducer fails.
Running the identical code with a sync psycopg.connect() connection produces no span — the sync path honors suppression, the async path does not.
Additional context
The common use of suppress_instrumentation() around background polls
(outbox sweeps, health/metric COUNT queries) silently stops working the day a
codebase migrates from sync to async connections — every poll becomes a root
span again, with no error and no signal that suppression is inert. Tests that
assert is_instrumentation_enabled() inside the block keep passing, because
the flag is set correctly; it is simply never read.
Would you like to implement a fix?
None
Tip
React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it. Learn more here.
Describe your environment
opentelemetry-instrumentation-dbapi0.62b1opentelemetry-instrumentation-psycopg0.62b1AsyncConnection/AsyncConnectionPoolWhat happened?
Spans are still created (and exported) for queries executed inside
opentelemetry.instrumentation.utils.suppress_instrumentation()when thecursor is async. The sync path correctly suppresses.
Steps to Reproduce
The same code with a sync
psycopg.connect()passes.Expected Result
No span — the async path should honor suppression exactly as the sync path
does.
Actual Result
One span was created and exported for the SELECT 1 executed inside suppress_instrumentation() — exporter.get_finished_spans() returns a span named SELECT with db.statement = "SELECT 1", and the final assertion in the reproducer fails.
Running the identical code with a sync psycopg.connect() connection produces no span — the sync path honors suppression, the async path does not.
Additional context
The common use of
suppress_instrumentation()around background polls(outbox sweeps, health/metric COUNT queries) silently stops working the day a
codebase migrates from sync to async connections — every poll becomes a root
span again, with no error and no signal that suppression is inert. Tests that
assert
is_instrumentation_enabled()inside the block keep passing, becausethe flag is set correctly; it is simply never read.
Would you like to implement a fix?
None
Tip
React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding
+1orme too, to help us triage it. Learn more here.