logging.py•4.61 kB
"""
Centralized logging configuration for the application.
This module provides a consistent logging setup across all components
of the FastAPI + MCP application.
"""
import logging
import logging.handlers
import sys
from pathlib import Path
from typing import Optional
from .config import settings
class ColoredFormatter(logging.Formatter):
"""
Custom formatter that adds colors to log levels for console output.
This makes it easier to distinguish between different log levels
when viewing logs in the terminal during development.
"""
# ANSI color codes for different log levels
COLORS = {
'DEBUG': '\033[36m', # Cyan
'INFO': '\033[32m', # Green
'WARNING': '\033[33m', # Yellow
'ERROR': '\033[31m', # Red
'CRITICAL': '\033[35m', # Magenta
}
RESET = '\033[0m' # Reset color
def format(self, record):
"""Format the log record with appropriate colors."""
# Add color to the levelname
levelname = record.levelname
if levelname in self.COLORS:
colored_levelname = f"{self.COLORS[levelname]}{levelname}{self.RESET}"
record.levelname = colored_levelname
# Format the message
formatted = super().format(record)
# Reset the levelname for other formatters
record.levelname = levelname
return formatted
def setup_logging(
log_level: Optional[str] = None,
log_file: Optional[str] = None,
enable_console: bool = True,
enable_file: bool = True
) -> logging.Logger:
"""
Set up centralized logging configuration.
Args:
log_level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
log_file: Path to log file (relative to project root)
enable_console: Whether to enable console logging
enable_file: Whether to enable file logging
Returns:
Configured root logger
Example:
>>> logger = setup_logging()
>>> logger.info("Application started")
>>> logger.error("Something went wrong", exc_info=True)
"""
# Use settings if parameters not provided
log_level = log_level or settings.log_level
log_file = log_file or getattr(settings, 'log_file', 'logs/app.log')
# Configure root logger
root_logger = logging.getLogger()
root_logger.setLevel(getattr(logging, log_level.upper()))
# Clear existing handlers to avoid duplicates
root_logger.handlers.clear()
# Create formatters
console_formatter = ColoredFormatter(
fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
file_formatter = logging.Formatter(
fmt='%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# Console handler
if enable_console:
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(console_formatter)
console_handler.setLevel(getattr(logging, log_level.upper()))
root_logger.addHandler(console_handler)
# File handler
if enable_file and log_file:
# Ensure log directory exists
log_path = Path(log_file)
log_path.parent.mkdir(parents=True, exist_ok=True)
# Rotating file handler to prevent huge log files
file_handler = logging.handlers.RotatingFileHandler(
log_file,
maxBytes=10*1024*1024, # 10MB
backupCount=5,
encoding='utf-8'
)
file_handler.setFormatter(file_formatter)
file_handler.setLevel(getattr(logging, log_level.upper()))
root_logger.addHandler(file_handler)
# Set levels for third-party loggers to reduce noise
logging.getLogger('uvicorn.access').setLevel(logging.WARNING)
logging.getLogger('uvicorn.error').setLevel(logging.INFO)
logging.getLogger('fastapi').setLevel(logging.INFO)
return root_logger
def get_logger(name: str) -> logging.Logger:
"""
Get a logger instance for a specific module.
Args:
name: Logger name (usually __name__)
Returns:
Logger instance
Example:
>>> logger = get_logger(__name__)
>>> logger.info("Module loaded successfully")
"""
return logging.getLogger(name)
# Initialize logging when module is imported
setup_logging()
# Export commonly used logger
logger = get_logger(__name__)
logger.debug("Logging system initialized")