Skip to main content

OpenTelemetry-native async context propagation for asyncio

Project description

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 threadsrun_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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aiotrace-0.1.1.tar.gz (9.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aiotrace-0.1.1-py2.py3-none-any.whl (9.7 kB view details)

Uploaded Python 2Python 3

File details

Details for the file aiotrace-0.1.1.tar.gz.

File metadata

  • Download URL: aiotrace-0.1.1.tar.gz
  • Upload date:
  • Size: 9.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for aiotrace-0.1.1.tar.gz
Algorithm Hash digest
SHA256 e6f16a0e48a241a4096aad366095f71004720461175bbaae5a2b5aeb9cc753cd
MD5 fe851cb793df8c807d08c589a33cf7e7
BLAKE2b-256 065c8b1d5ad6213a0abdc0d04865266726898937a03fe638254516064cfca6b5

See more details on using hashes here.

File details

Details for the file aiotrace-0.1.1-py2.py3-none-any.whl.

File metadata

  • Download URL: aiotrace-0.1.1-py2.py3-none-any.whl
  • Upload date:
  • Size: 9.7 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for aiotrace-0.1.1-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 e72dca2b9847d7581d036e8fc3c6075566f3c9b6d946da215c384f07f5b16d06
MD5 ba98ca4130b92cd201371b7abf47e488
BLAKE2b-256 fecac4fdfb8ffe5b1c81568c265775e604053fda05f117a2b98bd7517621bd05

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page