""" Structured logging configuration for VoiceAuth API. Uses structlog for JSON-formatted logs in production and human-readable logs in development. """ import logging import sys from typing import Any import structlog from structlog.types import Processor from app.config import get_settings def setup_logging() -> None: """ Configure structured logging based on application settings. Sets up structlog processors for either JSON or console output based on the LOG_FORMAT setting. """ settings = get_settings() # Convert string log level to logging constant log_level = getattr(logging, settings.LOG_LEVEL.upper(), logging.INFO) # Common processors for all output formats shared_processors: list[Processor] = [ structlog.contextvars.merge_contextvars, structlog.stdlib.add_log_level, structlog.stdlib.add_logger_name, structlog.stdlib.PositionalArgumentsFormatter(), structlog.processors.TimeStamper(fmt="iso"), structlog.processors.StackInfoRenderer(), structlog.processors.UnicodeDecoder(), ] if settings.LOG_FORMAT == "json": # JSON format for production processors: list[Processor] = shared_processors + [ structlog.processors.format_exc_info, structlog.processors.JSONRenderer(), ] else: # Console format for development processors = shared_processors + [ structlog.dev.ConsoleRenderer(colors=True), ] # Configure structlog structlog.configure( processors=processors, wrapper_class=structlog.stdlib.BoundLogger, context_class=dict, logger_factory=structlog.stdlib.LoggerFactory(), cache_logger_on_first_use=True, ) # Configure standard library logging logging.basicConfig( format="%(message)s", stream=sys.stdout, level=log_level, ) # Set log levels for noisy libraries logging.getLogger("uvicorn").setLevel(logging.WARNING) logging.getLogger("httpx").setLevel(logging.WARNING) logging.getLogger("httpcore").setLevel(logging.WARNING) def get_logger(name: str | None = None) -> structlog.stdlib.BoundLogger: """ Get a configured logger instance. Args: name: Optional logger name. If not provided, uses the calling module. Returns: A configured structlog bound logger. """ return structlog.get_logger(name) class LoggerMixin: """Mixin class that provides a logger property.""" @property def logger(self) -> structlog.stdlib.BoundLogger: """Get a logger bound to the class name.""" return get_logger(self.__class__.__name__) def log_request( method: str, path: str, status_code: int, duration_ms: float, **kwargs: Any, ) -> None: """ Log an HTTP request with structured data. Args: method: HTTP method (GET, POST, etc.) path: Request path status_code: Response status code duration_ms: Request duration in milliseconds **kwargs: Additional fields to log """ logger = get_logger("http") logger.info( "http_request", method=method, path=path, status_code=status_code, duration_ms=round(duration_ms, 2), **kwargs, )