Structlog integration

Updated Jul 20, 2026

For applications using structlog for structured logging, the correlation ID and user ID context variables can be included in structured log output without any additional library code.

How it works

The falcon-correlate middleware stores the correlation ID and user ID in standard contextvars.ContextVar instances (correlation_id_var and user_id_var). Structlog provides a merge_contextvars processor that merges context variables into the log event dictionary, but this processor only picks up variables bound via structlog.contextvars.bind_contextvars() — it does not automatically read arbitrary ContextVar instances such as correlation_id_var and user_id_var.

To bridge this gap, a small custom processor can be added to the structlog configuration that reads directly from falcon-correlate's context variables.

Alternative: `bind_contextvars` in middleware

As an alternative to the custom processor, the context variables can be bridged by calling structlog.contextvars.bind_contextvars() in a second Falcon middleware that runs after CorrelationIDMiddleware:

import structlog
from falcon_correlate import correlation_id_var, user_id_var


class StructlogContextMiddleware:
    """Bridge falcon-correlate context variables into structlog."""

    def process_request(self, req, resp):
        cid = correlation_id_var.get()
        uid = user_id_var.get()
        structlog.contextvars.bind_contextvars(
            correlation_id=cid or "-",
            user_id=uid or "-",
        )

    def process_response(self, req, resp, resource, req_succeeded):
        structlog.contextvars.clear_contextvars()

This approach works with structlog.contextvars.merge_contextvars() in the processor chain without needing a custom processor. However, the custom processor approach above is preferred because it requires no additional middleware and no per-request bridging calls.

Note on `merge_contextvars`

structlog.contextvars.merge_contextvars() only merges context variables bound via structlog.contextvars.bind_contextvars(). It does not automatically pick up arbitrary contextvars.ContextVar instances such as correlation_id_var and user_id_var. This is why a bridging step — either the custom processor or the middleware approach above — is required.