aiotrace – Async context propagation for OpenTelemetry
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:
-
Wrapping
create_task— Captures the current OTEL context before a task is spawned and restores it when the task's coroutine begins execution. -
Patching queues and locks — For synchronization primitives, the context that a waiter had when it called
get()oracquire()is stored and re-attached right before the waiter is resumed, using proper token-based attach/detach to prevent leaks. -
Propagating to threads —
run_in_executor_with_contextcopies the context to the worker thread viaotel_context.attach()inside the thread's callable. -
TaskGroup support (Python 3.11+) — Overrides
create_taskto ensure child tasks inherit the group's context.
All modifications are opt-in via install() or by using explicit classes like PropagatingQueue.
Limitations
asyncio.Eventandasyncio.Semaphoreare patched but not yet verified for all edge cases (contributions welcome).- Monkey-patching
asyncio.Queueglobally may conflict with other libraries that also replace it. Use explicitPropagatingQueuewhen 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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.