"""Application logging setup using structlog.""" from __future__ import annotations import logging import sys import structlog from app.config import get_settings def configure_logging() -> None: """Configure structlog + stdlib logging once at startup.""" cfg = get_settings() level = getattr(logging, cfg.log_level.upper(), logging.INFO) # stdlib root logger logging.basicConfig( level=level, format="%(message)s", stream=sys.stdout, ) # structlog processors — JSON output in prod, pretty console in dev shared_processors = [ structlog.contextvars.merge_contextvars, structlog.processors.add_log_level, structlog.processors.TimeStamper(fmt="iso"), structlog.processors.StackInfoRenderer(), structlog.processors.format_exc_info, ] if cfg.debug: renderer = structlog.dev.ConsoleRenderer(colors=True) else: renderer = structlog.processors.JSONRenderer() structlog.configure( processors=shared_processors + [renderer], wrapper_class=structlog.make_filtering_bound_logger(level), logger_factory=structlog.PrintLoggerFactory(), cache_logger_on_first_use=True, ) def get_logger(name: str | None = None) -> structlog.stdlib.BoundLogger: """Return a structlog logger bound to `name`.""" return structlog.get_logger(name) # type: ignore[return-value]