Skip to main content

aiotrace – Async context propagation for OpenTelemetry

PyPI Python Versions License GitHub Tests Downloads

Fixes OpenTelemetry context propagation across asyncio boundaries: create_task, Queue, Lock, Event, Semaphore, TaskGroup, and run_in_executor (thread pool).

Problem

OpenTelemetry stores the current span in a contextvars.ContextVar. When asyncio tasks are created or resumed, Python copies/shallow-copies these contextvars. On Python < 3.9.17 / 3.10.7 / 3.11.1, Task.__step does not restore the task's own context, causing:

  • Spans created in child tasks appearing under the wrong parent
  • Context leaking between producer/consumer queues
  • Lost context when using run_in_executor (threads)

Quick Start

import asyncio
from opentelemetry import trace
from aiotrace import install

install()

tracer = trace.get_tracer(__name__)

async def child():
    with tracer.start_as_current_span("child"):
        pass

async def main():
    with tracer.start_as_current_span("parent"):
        await asyncio.create_task(child())

asyncio.run(main())

Manual Usage (no monkey-patching)

from aiotrace import create_task_with_context, PropagatingQueue

# Use explicit wrapper instead of monkey-patch
task = create_task_with_context(some_coro())

# Explicit propagating queue
queue = PropagatingQueue()
await queue.put(item)
item = await queue.get()

Installation

pip install aiotrace

What Gets Patched

Primitive Replacement Description
asyncio.create_task Wrapped Captures OTEL context at task creation, restores inside child
asyncio.Queue PropagatingQueue Re-attaches context after get()/put() resume
asyncio.Lock PropagatingLock Re-attaches context after acquire()
asyncio.Event PropagatingEvent Re-attaches context after wait()
asyncio.Semaphore PropagatingSemaphore Re-attaches context after acquire()
asyncio.Condition PropagatingCondition Re-attaches context after wait()
asyncio.TaskGroup (3.11+) PropagatingTaskGroup Captures context at create_task()

API

install(patch_queue=True, patch_locks=True)

Monkey-patches asyncio primitives. Safe to call multiple times (idempotent).

uninstall()

Restores original asyncio primitives.

PropagatingQueue(maxsize=0)

Drop-in replacement for asyncio.Queue with context propagation.

PropagatingLock, PropagatingEvent, PropagatingSemaphore, PropagatingCondition

Drop-in replacements for synchronization primitives.

create_task_with_context(coro, *, name=None, otel_ctx=None)

Create a task with explicit OTEL context propagation.

run_in_executor_with_context(executor, func, *args)

Schedule a function in a thread pool with OTEL context.

How It Works

asyncio creates new tasks by shallow-copying contextvars, which can break OpenTelemetry's span hierarchy when tasks resume in the wrong context. aiotrace fixes this at four levels:

  1. Wrapping create_task — Captures the current OTEL context before a task is spawned and restores it when the task's coroutine begins execution.

  2. Patching queues and locks — For synchronization primitives, the context that a waiter had when it called get() or acquire() is stored and re-attached right before the waiter is resumed, using proper token-based attach/detach to prevent leaks.

  3. Propagating to threads — run_in_executor_with_context copies the context to the worker thread via otel_context.attach() inside the thread's callable.

  4. TaskGroup support (Python 3.11+) — Overrides create_task to ensure child tasks inherit the group's context.

All modifications are opt-in via install() or by using explicit classes like PropagatingQueue.

Limitations

  • asyncio.Event and asyncio.Semaphore are patched but not yet verified for all edge cases (contributions welcome).
  • Monkey-patching asyncio.Queue globally may conflict with other libraries that also replace it. Use explicit PropagatingQueue when possible.
  • High-frequency context switching adds a small overhead (approx. 1–2 µs per operation). For most applications this is negligible.

Requirements

  • Python 3.8–3.12
  • opentelemetry-api >= 1.20.0

License

MIT

Metadata

Release files for aiotrace 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aiotrace 0.1.1
File Size Uploaded Provenance Actions
aiotrace-0.1.1.tar.gz 9.6 kB Without provenance Details for aiotrace-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for aiotrace 0.1.1
File Interpreter ABI Platform Actions
aiotrace-0.1.1-py2.py3-none-any.whl Python 2, Python 3 none any Details for aiotrace-0.1.1-py2.py3-none-any.whl

Total release size: 19.3 kB

Release files / aiotrace-0.1.1.tar.gz

Download URL aiotrace-0.1.1.tar.gz
Size 9.6 kB
Tags Source
SHA-256 checksum
How to use checksums
e6f16a0e48a241a4096aad366095f71004720461175bbaae5a2b5aeb9cc753cd
BLAKE2b-256 checksum
How to use checksums
065c8b1d5ad6213a0abdc0d04865266726898937a03fe638254516064cfca6b5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.10

Release files / aiotrace-0.1.1-py2.py3-none-any.whl

Download URL aiotrace-0.1.1-py2.py3-none-any.whl
Size 9.7 kB
Tags Python 2 Python 3
SHA-256 checksum
How to use checksums
e72dca2b9847d7581d036e8fc3c6075566f3c9b6d946da215c384f07f5b16d06
BLAKE2b-256 checksum
How to use checksums
fecac4fdfb8ffe5b1c81568c265775e604053fda05f117a2b98bd7517621bd05
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.10

Security for aiotrace 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page