{"id":193,"library":"structlog","title":"structlog","description":"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().","status":"active","version":"25.5.0","language":"python","source_language":"en","source_url":"https://github.com/hynek/structlog","tags":["structlog","logging","structured-logging","python","observability"],"install":[{"cmd":"pip install structlog","lang":"bash","label":"Python"}],"dependencies":[],"imports":[{"note":"bind() is immutable — always reassign: log = log.bind(key='val'). Configure before first get_logger() call, especially with cache_logger_on_first_use=True.","wrong":"import structlog\n\n# Wrong: using logger before configure() — uses default unconfigured settings\nlog = structlog.get_logger()  # obtained before configure\nlog.info('too early')\n\n# Later...\nstructlog.configure(processors=[...])  # too late for above logger if cache=True\n\n# Wrong: expecting bind() to mutate in place\nlog = structlog.get_logger()\nlog.bind(user_id='123')  # no-op — bind() returns new logger, original unchanged\nlog.info('event')  # user_id NOT in this log","symbol":"structlog.configure + get_logger","correct":"import logging\nimport structlog\n\n# Configure BEFORE any get_logger() calls\nstructlog.configure(\n    processors=[\n        structlog.contextvars.merge_contextvars,\n        structlog.stdlib.add_log_level,\n        structlog.stdlib.add_logger_name,\n        structlog.processors.TimeStamper(fmt='iso'),\n        structlog.processors.StackInfoRenderer(),\n        structlog.processors.JSONRenderer(),   # JSON output\n    ],\n    wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),  # filter by level\n    logger_factory=structlog.PrintLoggerFactory(),\n    cache_logger_on_first_use=True,\n)\n\nlog = structlog.get_logger()\nlog.info('app_started', version='1.0.0')\nlog.warning('high_memory', usage_pct=95)\n\n# bind() returns NEW logger — immutable\nrequest_log = log.bind(request_id='req-123', user_id='usr-456')\nrequest_log.info('payment_initiated', amount=500)\n# original log is unchanged"},{"note":"Without ProcessorFormatter, structlog and stdlib logging produce different formats simultaneously. Use ProcessorFormatter to unify both into the same pipeline.","wrong":"# Using structlog alongside stdlib without ProcessorFormatter\n# results in two different log formats in the same output\nimport logging\nimport structlog\nlogging.basicConfig()  # plain text format\nlog = structlog.get_logger()  # different format","symbol":"stdlib interop with ProcessorFormatter","correct":"import logging\nimport sys\nimport structlog\n\n# Make stdlib logging go through structlog processors\ntimestamper = structlog.processors.TimeStamper(fmt='iso')\npre_chain = [\n    structlog.stdlib.add_log_level,\n    timestamper,\n]\n\nhandler = logging.StreamHandler(sys.stdout)\nhandler.setFormatter(\n    structlog.stdlib.ProcessorFormatter(\n        processor=structlog.processors.JSONRenderer(),\n        foreign_pre_chain=pre_chain,\n    )\n)\n\nroot_logger = logging.getLogger()\nroot_logger.addHandler(handler)\nroot_logger.setLevel(logging.INFO)\n\n# Now both structlog AND stdlib logging output same JSON format\nstructlog.configure(\n    processors=[\n        structlog.stdlib.add_log_level,\n        timestamper,\n        structlog.stdlib.ProcessorFormatter.wrap_for_formatter,\n    ],\n    logger_factory=structlog.stdlib.LoggerFactory(),\n)\n\nlog = structlog.get_logger()\nlog.info('structured', key='val')\nlogging.getLogger('uvicorn').info('stdlib log')  # also JSON"}],"quickstart":{"code":"# pip install structlog\nimport logging\nimport structlog\n\n# Configure at app startup\nstructlog.configure(\n    processors=[\n        structlog.contextvars.merge_contextvars,\n        structlog.stdlib.add_log_level,\n        structlog.processors.TimeStamper(fmt='iso'),\n        structlog.processors.JSONRenderer(),\n    ],\n    wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),\n    cache_logger_on_first_use=True,\n)\n\nlog = structlog.get_logger()\n\n# Basic structured logging\nlog.info('user_login', user_id='usr-123', method='oauth')\nlog.error('payment_failed', order_id='ord-456', reason='insufficient_funds')\n\n# bind() — immutable, returns new logger\nreq_log = log.bind(request_id='req-789', ip='1.2.3.4')\nreq_log.info('request_received', path='/api/checkout')\nreq_log.info('request_completed', status=200, duration_ms=42)\n\n# Request-scoped context via contextvars (for async/web frameworks)\nstructlog.contextvars.bind_contextvars(trace_id='abc123')\nlog.info('db_query', table='orders')  # trace_id auto-included\nstructlog.contextvars.clear_contextvars()\n\n# Drop event from pipeline\ndef filter_health_checks(logger, method, event_dict):\n    if event_dict.get('path') == '/health':\n        raise structlog.DropEvent()\n    return event_dict","lang":"python","description":"structlog — configure, bind, contextvars, and event filtering."},"warnings":[{"fix":"wrapper_class=structlog.make_filtering_bound_logger(logging.INFO) in structlog.configure()","message":"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.","severity":"breaking","affected_versions":"all"},{"fix":"Always reassign: log = log.bind(key='val') or request_log = log.bind(request_id='123')","message":"bind() is immutable — returns a new logger, does NOT mutate the original. log.bind(key='val') without reassignment is silently discarded.","severity":"breaking","affected_versions":"all"},{"fix":"Call structlog.configure() at app startup, before any module-level get_logger() calls are invoked.","message":"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.","severity":"breaking","affected_versions":"all"},{"fix":"Use structlog.get_context(log) instead of log._context","message":"_context attribute on bound loggers is deprecated since v21.1. Raises DeprecationWarning.","severity":"gotcha","affected_versions":">= 21.1"},{"fix":"Call structlog.contextvars.clear_contextvars() at the start or end of each request in middleware.","message":"contextvars (bind_contextvars) context is NOT automatically cleared between requests. Forgetting clear_contextvars() leaks context from one request to the next.","severity":"gotcha","affected_versions":"all"},{"fix":"await log.ainfo('event') instead of configuring AsyncBoundLogger as wrapper_class.","message":"structlog.stdlib.AsyncBoundLogger deprecated since v23.1. Use native async methods (ainfo, adebug, etc.) on the standard bound logger instead.","severity":"gotcha","affected_versions":">= 23.1"}],"env_vars":null,"last_verified":"2026-05-12T09:56:33.575Z","next_check":"2026-06-25T00:00:00.000Z","problems":[{"fix":"Run `pip install structlog` to install the library.","cause":"The structlog library has not been installed in your Python environment.","error":"ModuleNotFoundError: No module named 'structlog'"},{"fix":"Ensure `structlog.configure()` is called only once at the very beginning of your application's lifecycle, typically during startup.","cause":"The `structlog.configure()` function was invoked multiple times, which is prohibited as it's intended for a single, early application-wide setup.","error":"RuntimeError: structlog.configure() can only be called once."},{"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.","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.","error":"AttributeError: '_Logger' object has no attribute '_context'"},{"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')`.","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.","error":"structlog bind not working"},{"fix":"Add `structlog.contextvars.merge_contextvars` to your list of processors when calling `structlog.configure()`, for example: `structlog.configure(processors=[..., structlog.contextvars.merge_contextvars, ...])`.","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.","error":"structlog contextvars context missing"}],"ecosystem":"pypi","meta_description":null,"install_score":100,"install_tag":"verified","quickstart_score":80,"quickstart_tag":"verified","pypi_latest":null,"install_checks":{"last_tested":"2026-05-12","tag":"verified","tag_description":"installs cleanly on critical runtimes, fast import, recently tested","results":[{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.01,"mem_mb":0.7,"disk_size":"18.6M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.01,"mem_mb":0.7,"disk_size":"19M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.03,"mem_mb":1.1,"disk_size":"20.2M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.03,"mem_mb":1.1,"disk_size":"21M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.02,"mem_mb":0.9,"disk_size":"12.1M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.02,"mem_mb":0.9,"disk_size":"13M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.02,"mem_mb":1.2,"disk_size":"11.7M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.02,"mem_mb":1,"disk_size":"12M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.01,"mem_mb":0.6,"disk_size":"18.1M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.01,"mem_mb":0.6,"disk_size":"19M"}]},"quickstart_checks":{"last_tested":"2026-04-23","tag":"verified","tag_description":"quickstart runs on critical runtimes, recently tested","results":[{"runtime":"python:3.10-alpine","exit_code":0},{"runtime":"python:3.10-slim","exit_code":0},{"runtime":"python:3.11-alpine","exit_code":0},{"runtime":"python:3.11-slim","exit_code":0},{"runtime":"python:3.12-alpine","exit_code":0},{"runtime":"python:3.12-slim","exit_code":0},{"runtime":"python:3.13-alpine","exit_code":0},{"runtime":"python:3.13-slim","exit_code":0},{"runtime":"python:3.9-alpine","exit_code":0},{"runtime":"python:3.9-slim","exit_code":0}]}}