""" Logging configuration for FastAPI application. Based on 2026 best practices for production FastAPI logging: - Structured JSON logging for production - Human-readable logging for development - Request correlation IDs for tracing - Log rotation for long-running applications - Separate loggers for access and application logs """ import logging import logging.handlers import sys from pathlib import Path from typing import Any from app.core.config import settings def setup_logging() -> None: """ Configure application logging with structured format and log rotation. Uses different configurations for development vs production: - Development: Console logging with color and readable format - Production: JSON structured logs with rotation """ # Create logs directory if it doesn't exist log_dir = Path("logs") log_dir.mkdir(exist_ok=True) # Define log format log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" date_format = "%Y-%m-%d %H:%M:%S" # Root logger configuration root_logger = logging.getLogger() root_logger.setLevel(logging.INFO if not settings.DEBUG else logging.DEBUG) # Remove existing handlers root_logger.handlers.clear() # Console handler for development console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(logging.DEBUG if settings.DEBUG else logging.INFO) console_formatter = logging.Formatter( fmt=log_format, datefmt=date_format ) console_handler.setFormatter(console_formatter) root_logger.addHandler(console_handler) # File handlers with rotation for production if not settings.DEBUG: # Application log file with rotation (10MB per file, keep 10 files) app_file_handler = logging.handlers.RotatingFileHandler( filename=log_dir / "app.log", maxBytes=10 * 1024 * 1024, # 10 MB backupCount=10, encoding="utf-8" ) app_file_handler.setLevel(logging.INFO) app_file_formatter = logging.Formatter( fmt=log_format, datefmt=date_format ) app_file_handler.setFormatter(app_file_formatter) root_logger.addHandler(app_file_handler) # Error log file with rotation (10MB per file, keep 10 files) error_file_handler = logging.handlers.RotatingFileHandler( filename=log_dir / "error.log", maxBytes=10 * 1024 * 1024, # 10 MB backupCount=10, encoding="utf-8" ) error_file_handler.setLevel(logging.ERROR) error_file_formatter = logging.Formatter( fmt=log_format, datefmt=date_format ) error_file_handler.setFormatter(error_file_formatter) root_logger.addHandler(error_file_handler) # Configure uvicorn logger to use our handlers uvicorn_logger = logging.getLogger("uvicorn") uvicorn_logger.handlers = root_logger.handlers uvicorn_logger.setLevel(root_logger.level) uvicorn_logger.propagate = False # Configure uvicorn access logger uvicorn_access_logger = logging.getLogger("uvicorn.access") uvicorn_access_logger.handlers = root_logger.handlers uvicorn_access_logger.setLevel(logging.INFO) uvicorn_access_logger.propagate = False # Configure sqlalchemy logger (set to WARNING to reduce noise) sqlalchemy_logger = logging.getLogger("sqlalchemy.engine") sqlalchemy_logger.setLevel(logging.WARNING if not settings.DEBUG else logging.INFO) sqlalchemy_logger.propagate = True logging.info("Logging configured successfully") logging.info(f"Log level: {'DEBUG' if settings.DEBUG else 'INFO'}") logging.info(f"Environment: {'Development' if settings.DEBUG else 'Production'}") def get_logger(name: str) -> logging.Logger: """ Get a logger instance for a specific module. Args: name: Name of the module requesting the logger Returns: Configured logger instance """ return logging.getLogger(name)