125 lines
3.4 KiB
Python
125 lines
3.4 KiB
Python
"""
|
|
Centralized logging configuration for the backtest engine.
|
|
|
|
Provides colored console output and rotating file logs.
|
|
"""
|
|
|
|
import logging
|
|
import sys
|
|
from logging.handlers import RotatingFileHandler
|
|
from pathlib import Path
|
|
|
|
|
|
# ANSI color codes for terminal output
|
|
class Colors:
|
|
"""ANSI escape codes for colored terminal output."""
|
|
|
|
RESET = "\033[0m"
|
|
BOLD = "\033[1m"
|
|
|
|
# Log level colors
|
|
DEBUG = "\033[36m" # Cyan
|
|
INFO = "\033[32m" # Green
|
|
WARNING = "\033[33m" # Yellow
|
|
ERROR = "\033[31m" # Red
|
|
CRITICAL = "\033[35m" # Magenta
|
|
|
|
|
|
class ColoredFormatter(logging.Formatter):
|
|
"""
|
|
Custom formatter that adds colors to log level names in terminal output.
|
|
"""
|
|
|
|
LEVEL_COLORS = {
|
|
logging.DEBUG: Colors.DEBUG,
|
|
logging.INFO: Colors.INFO,
|
|
logging.WARNING: Colors.WARNING,
|
|
logging.ERROR: Colors.ERROR,
|
|
logging.CRITICAL: Colors.CRITICAL,
|
|
}
|
|
|
|
def __init__(self, fmt: str = None, datefmt: str = None):
|
|
super().__init__(fmt, datefmt)
|
|
|
|
def format(self, record: logging.LogRecord) -> str:
|
|
# Save original levelname
|
|
original_levelname = record.levelname
|
|
|
|
# Add color to levelname
|
|
color = self.LEVEL_COLORS.get(record.levelno, Colors.RESET)
|
|
record.levelname = f"{color}{record.levelname}{Colors.RESET}"
|
|
|
|
# Format the message
|
|
result = super().format(record)
|
|
|
|
# Restore original levelname
|
|
record.levelname = original_levelname
|
|
|
|
return result
|
|
|
|
|
|
def setup_logging(
|
|
log_dir: str = "logs",
|
|
log_level: int = logging.INFO,
|
|
console_level: int = logging.INFO,
|
|
max_bytes: int = 5 * 1024 * 1024, # 5MB
|
|
backup_count: int = 3
|
|
) -> None:
|
|
"""
|
|
Configure logging for the application.
|
|
|
|
Args:
|
|
log_dir: Directory for log files
|
|
log_level: File logging level
|
|
console_level: Console logging level
|
|
max_bytes: Max size per log file before rotation
|
|
backup_count: Number of backup files to keep
|
|
"""
|
|
log_path = Path(log_dir)
|
|
log_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Get root logger
|
|
root_logger = logging.getLogger()
|
|
root_logger.setLevel(logging.DEBUG) # Capture all, handlers filter
|
|
|
|
# Clear existing handlers
|
|
root_logger.handlers.clear()
|
|
|
|
# Console handler with colors
|
|
console_handler = logging.StreamHandler(sys.stdout)
|
|
console_handler.setLevel(console_level)
|
|
console_fmt = ColoredFormatter(
|
|
fmt="[%(asctime)s] %(levelname)s - %(message)s",
|
|
datefmt="%H:%M:%S"
|
|
)
|
|
console_handler.setFormatter(console_fmt)
|
|
root_logger.addHandler(console_handler)
|
|
|
|
# File handler with rotation
|
|
file_handler = RotatingFileHandler(
|
|
log_path / "backtest.log",
|
|
maxBytes=max_bytes,
|
|
backupCount=backup_count,
|
|
encoding="utf-8"
|
|
)
|
|
file_handler.setLevel(log_level)
|
|
file_fmt = logging.Formatter(
|
|
fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
datefmt="%Y-%m-%d %H:%M:%S"
|
|
)
|
|
file_handler.setFormatter(file_fmt)
|
|
root_logger.addHandler(file_handler)
|
|
|
|
|
|
def get_logger(name: str) -> logging.Logger:
|
|
"""
|
|
Get a logger instance for the given module name.
|
|
|
|
Args:
|
|
name: Module name (typically __name__)
|
|
|
|
Returns:
|
|
Configured logger instance
|
|
"""
|
|
return logging.getLogger(name)
|