Context Binding

Add request_id, user_id, or any context once—it automatically appears in every log from that request. No need to pass context through your entire call stack.

How it works

When you call logger.bind(request_id="123", user_id="abc"), that context automatically appears in every subsequent log from that request:

  • Bind once at the request boundary - typically in middleware or at the start of a handler

  • Every log includes it - no need to pass request_id as a parameter through 10 function calls

  • Each request gets its own context - one user’s logs won’t leak into another’s

  • Clear when done - logger.clear_context() removes bound values (optional, automatic with runtime())

Sync example

from fapilog import runtime

with runtime() as logger:
    logger.bind(request_id="req-123", user_id="u-1")
    logger.info("Request started")
    logger.clear_context()

Async example

import asyncio
from fapilog import runtime_async

async def worker(name, logger):
    await logger.info(f"{name} started")

async def main():
    async with runtime_async() as logger:
        logger.bind(request_id="req-123")
        await asyncio.gather(worker("t1", logger), worker("t2", logger))
        logger.clear_context()

asyncio.run(main())

Tips

  • Bind at the start of each request - In FastAPI, this happens automatically with FastAPIBuilder. In other frameworks, bind in your middleware.

  • Use runtime() or runtime_async() - These context managers automatically clear context when the request ends, preventing leakage.

  • Common fields to bind: request_id, user_id, tenant_id, trace_id, correlation_id