grep your logs to follow a single request end to end.
The user sends a message; the same correlation id appears in bot logs, agent tools, and delivery traces for end-to-end debugging.
Quick Start
How It Works
The id travels through Python’scontextvars — no thread-locals, no globals. Every async task or thread that inherits the context gets the same id automatically.
Configuration Options
Adopt-key precedence
Whencorrelation_id_from(inbound) is called on a dict or object, it checks keys in order:
| Priority | Key | Example |
|---|---|---|
| 1 | correlation_id | {"correlation_id": "abc123"} |
| 2 | message_id | {"message_id": "tg-9182"} |
| 3 | id | {"id": "msg-001"} |
| — | (none found) | mints a new id |
ID format
A freshly minted id is the first 8 hex characters of auuid4: e.g. a3f1c9de. Short enough to type, unique enough for log correlation.
Public API
| Symbol | Signature | Behaviour |
|---|---|---|
new_correlation_id | () -> str | Mint a fresh short correlation id. |
correlation_id_from | (inbound: Any = None) -> str | Adopt from dict/attr-bag, else mint. |
current_correlation_id | () -> Optional[str] | Read the contextvar. |
ensure_correlation_id | () -> str | Return current, minting + setting if absent. |
set_correlation_id | (cid: Optional[str]) -> contextvars.Token | Bind on the contextvar. |
reset_correlation_id | (token) -> None | Restore previous value. |
use_correlation_id | (cid: Optional[str] = None) -> Iterator[str] | Context manager: with use_correlation_id(cid): |
correlation_log_fields | (extra: Optional[Dict[str, Any]] = None) -> Dict[str, Any] | Merge {"correlation_id": <current>} into logging extra. |
Common Patterns
Full turn with structured logging
correlation_id=tg-9182 — the tool, the session manager’s bot turn start debug line, and every other hop.
Forwarding an upstream id from a webhook
Passing through to downstream HTTP calls
Manual token management (advanced)
Best Practices
Always read via current_correlation_id() — never store globally
Always read via current_correlation_id() — never store globally
The id lives in a
ContextVar. Reading it with current_correlation_id() is safe across async tasks and threads. Storing it in a module-level variable breaks concurrent request isolation.Use correlation_log_fields() to avoid log-field drift
Use correlation_log_fields() to avoid log-field drift
Calling
correlation_log_fields({"channel": "telegram"}) merges the current id into your extra dict in one call. This prevents typos like "correlaton_id" from silently breaking your log queries.Forward the id to downstream HTTP calls as X-Correlation-Id
Forward the id to downstream HTTP calls as X-Correlation-Id
Downstream services can adopt the same id, extending your log trail across service boundaries without any shared state.
Always reset tokens via the context manager
Always reset tokens via the context manager
Calling
set_correlation_id without a paired reset_correlation_id leaks the id into the parent context. Use with use_correlation_id(cid): — the reset happens automatically on block exit, even on exceptions.The
correlation_id is also the join key for spans emitted through Gateway Tracing Hook.Related
Gateway Metrics
Prometheus counters and gauges for every hop of the message flow.
Gateway Server
The gateway wires
set_correlation_id on every turn automatically.
