structlog

raw JSON →
25.5.0 verified Tue May 12 auth: no python install: verified quickstart: verified

Structured logging for Python. Current version: 25.5.0 (Mar 2026). No level filtering by default — ALL log levels emitted until configured. structlog.configure() must be called before first use; loggers obtained before configure() use default settings. bind() is immutable — returns a new logger, does not mutate in place. structlog.contextvars.merge_contextvars needed for request-scoped context. stdlib interop requires ProcessorFormatter. _context attribute deprecated — use structlog.get_context().

pip install structlog
error ModuleNotFoundError: No module named 'structlog'
cause The structlog library has not been installed in your Python environment.
fix
Run pip install structlog to install the library.
error RuntimeError: structlog.configure() can only be called once.
cause The `structlog.configure()` function was invoked multiple times, which is prohibited as it's intended for a single, early application-wide setup.
fix
Ensure structlog.configure() is called only once at the very beginning of your application's lifecycle, typically during startup.
error AttributeError: '_Logger' object has no attribute '_context'
cause You are attempting to access the `_context` attribute directly on a `structlog` logger instance, which is deprecated and no longer accessible in current versions.
fix
Use structlog.get_context() or structlog.contextvars.get_context() to retrieve the current context, or use logger.bind(...) to add context for specific log calls.
error structlog bind not working
cause The `logger.bind()` method is immutable; it returns a *new* logger instance with the added context, and does not modify the original logger in-place.
fix
Always use the logger instance returned by logger.bind() for subsequent logging calls that should include the new context, e.g., bound_logger = logger.bind(key='value'); bound_logger.info('message').
error structlog contextvars context missing
cause Context set via `structlog.contextvars` will not automatically appear in logs unless `structlog.contextvars.merge_contextvars` is included as a processor in your `structlog.configure()` setup.
fix
Add structlog.contextvars.merge_contextvars to your list of processors when calling structlog.configure(), for example: structlog.configure(processors=[..., structlog.contextvars.merge_contextvars, ...]).
breaking No log level filtering by default — ALL levels (debug, info, warning, error) are emitted. Must use make_filtering_bound_logger(logging.INFO) in wrapper_class to filter.
fix wrapper_class=structlog.make_filtering_bound_logger(logging.INFO) in structlog.configure()
breaking bind() is immutable — returns a new logger, does NOT mutate the original. log.bind(key='val') without reassignment is silently discarded.
fix Always reassign: log = log.bind(key='val') or request_log = log.bind(request_id='123')
breaking structlog.configure() must be called before first use. With cache_logger_on_first_use=True (default in prod), loggers cached before configure() use default settings permanently.
fix Call structlog.configure() at app startup, before any module-level get_logger() calls are invoked.
gotcha _context attribute on bound loggers is deprecated since v21.1. Raises DeprecationWarning.
fix Use structlog.get_context(log) instead of log._context
gotcha contextvars (bind_contextvars) context is NOT automatically cleared between requests. Forgetting clear_contextvars() leaks context from one request to the next.
fix Call structlog.contextvars.clear_contextvars() at the start or end of each request in middleware.
gotcha structlog.stdlib.AsyncBoundLogger deprecated since v23.1. Use native async methods (ainfo, adebug, etc.) on the standard bound logger instead.
fix await log.ainfo('event') instead of configuring AsyncBoundLogger as wrapper_class.
python os / libc status wheel install import disk
3.10 alpine (musl) - - 0.01s 18.6M
3.10 slim (glibc) - - 0.01s 19M
3.11 alpine (musl) - - 0.03s 20.2M
3.11 slim (glibc) - - 0.03s 21M
3.12 alpine (musl) - - 0.02s 12.1M
3.12 slim (glibc) - - 0.02s 13M
3.13 alpine (musl) - - 0.02s 11.7M
3.13 slim (glibc) - - 0.02s 12M
3.9 alpine (musl) - - 0.01s 18.1M
3.9 slim (glibc) - - 0.01s 19M

structlog — configure, bind, contextvars, and event filtering.

# pip install structlog
import logging
import structlog

# Configure at app startup
structlog.configure(
    processors=[
        structlog.contextvars.merge_contextvars,
        structlog.stdlib.add_log_level,
        structlog.processors.TimeStamper(fmt='iso'),
        structlog.processors.JSONRenderer(),
    ],
    wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
    cache_logger_on_first_use=True,
)

log = structlog.get_logger()

# Basic structured logging
log.info('user_login', user_id='usr-123', method='oauth')
log.error('payment_failed', order_id='ord-456', reason='insufficient_funds')

# bind() — immutable, returns new logger
req_log = log.bind(request_id='req-789', ip='1.2.3.4')
req_log.info('request_received', path='/api/checkout')
req_log.info('request_completed', status=200, duration_ms=42)

# Request-scoped context via contextvars (for async/web frameworks)
structlog.contextvars.bind_contextvars(trace_id='abc123')
log.info('db_query', table='orders')  # trace_id auto-included
structlog.contextvars.clear_contextvars()

# Drop event from pipeline
def filter_health_checks(logger, method, event_dict):
    if event_dict.get('path') == '/health':
        raise structlog.DropEvent()
    return event_dict