How to attach a request ID to exception messages in Python

This code shows how to enrich exception messages with contextual request IDs using context variables, making error logs more traceable across concurrent requests.

Medium Python 3.7+ Aug 9, 2026 Errors & debugging 13 views 0 copies

Python code

22 lines
Python 3.7+
import logging
from contextvars import ContextVar

request_id_var = ContextVar("request_id", default="unknown")

def add_request_id(exc: Exception) -> Exception:
    exc.args = (f"request_id={request_id_var.get()} | {exc.args[0]}" if exc.args else f"request_id={request_id_var.get()}",) + exc.args[1:]
    return exc

def handle_request(request_id: str) -> None:
    request_id_var.set(request_id)
    try:
        raise ValueError("Database connection failed")
    except ValueError as e:
        raise add_request_id(e)

if __name__ == "__main__":
    request_id_var.set("req-12345")
    try:
        handle_request("req-12345")
    except ValueError as e:
        logging.error("Error caught: %s", e)

Output

stdout
ERROR:root:Error caught: request_id=req-12345 | Database connection failed

How it works

The ContextVar provides a thread-safe way to store per-request data (like request IDs) that persists across async boundaries. When an exception is raised, the add_request_id function mutates exc.args to prepend the request ID to the message while preserving any remaining arguments. This approach is lightweight and doesn't require global state, making it ideal for concurrent web server contexts. Since the same request_id_var is reused, every exception raised within the same request context automatically gets tagged with the correct ID.

Common mistakes

  • Forgetting to reset the ContextVar after a request completes, causing stale IDs to leak into unrelated operations
  • Assuming exc.args always has a string as its first element — need to handle empty args case
  • Not using ContextVar.copy_context() in async frameworks, leading to incorrect ID propagation
  • Modifying exception args instead of using exception groups or custom exception classes for complex cases

Variations

  1. Use `functools.wraps` with a decorator pattern to automatically wrap exceptions from any function
  2. Create a custom exception subclass (e.g., `RequestScopedError`) that carries the request ID as a separate attribute

Real-world use cases

  • In a Flask or FastAPI middleware, attaching request IDs to all errors before logging them for structured log aggregation
  • In background job workers (e.g., Celery), tagging each task's exceptions with a correlation ID for distributed tracing
  • In an async web server handling multiple concurrent requests, ensuring each error log shows which request it belongs to

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Errors & debugging

Related tutorials and quizzes for this topic.