"""Structured logging setup using structlog. Produces human-friendly console logs in development and JSON logs in production so they can be ingested by log aggregators. """ from __future__ import annotations import logging import sys import structlog from app.core.config import settings def configure_logging() -> None: """Configure stdlib logging + structlog. Idempotent.""" log_level = logging.DEBUG if settings.debug else logging.INFO logging.basicConfig( format="%(message)s", stream=sys.stdout, level=log_level, ) shared_processors: list = [ structlog.contextvars.merge_contextvars, structlog.processors.add_log_level, structlog.processors.TimeStamper(fmt="iso"), structlog.processors.StackInfoRenderer(), structlog.processors.format_exc_info, ] if settings.environment == "production": renderer: structlog.types.Processor = structlog.processors.JSONRenderer() else: renderer = structlog.dev.ConsoleRenderer(colors=True) structlog.configure( processors=[*shared_processors, renderer], wrapper_class=structlog.make_filtering_bound_logger(log_level), logger_factory=structlog.PrintLoggerFactory(), cache_logger_on_first_use=True, ) def get_logger(name: str | None = None) -> structlog.stdlib.BoundLogger: return structlog.get_logger(name)