pythonintermediate

Structured Logging with structlog

Configure structlog for JSON-formatted structured logging with request context, timestamps, and log levels.

python
import structlog
import logging
import sys


def setup_logging(json_output: bool = True, level: str = "INFO") -> None:
    """Configure structlog with processors and stdlib integration."""

    shared_processors: list[structlog.types.Processor] = [
        structlog.contextvars.merge_contextvars,
        structlog.stdlib.add_log_level,
        structlog.stdlib.add_logger_name,
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.StackInfoRenderer(),
        structlog.processors.UnicodeDecoder(),
    ]

    if json_output:
        renderer = structlog.processors.JSONRenderer()
    else:
        renderer = structlog.dev.ConsoleRenderer()

    structlog.configure(
        processors=[
            *shared_processors,
            structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
        ],
        logger_factory=structlog.stdlib.LoggerFactory(),
        wrapper_class=structlog.stdlib.BoundLogger,
        cache_logger_on_first_use=True,
    )

    formatter = structlog.stdlib.ProcessorFormatter(
        processors=[*shared_processors, renderer]
    )

    handler = logging.StreamHandler(sys.stdout)
    handler.setFormatter(formatter)

    root = logging.getLogger()
    root.handlers.clear()
    root.addHandler(handler)
    root.setLevel(level)


# Usage:
# setup_logging(json_output=True)
# log = structlog.get_logger()
#
# log.info("user_login", user_id=42, ip="1.2.3.4")
# Output: {"event": "user_login", "user_id": 42, "ip": "1.2.3.4", ...}
#
# # Bind context for request lifecycle
# structlog.contextvars.bind_contextvars(request_id="abc-123")
# log.info("processing")  # includes request_id automatically

Sponsored

Datadog — Cloud monitoring and logging

Use Cases

  • Application logging
  • Request tracing
  • Production observability

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.