Files
MuMuAINovel/backend/app/logger.py
T

158 lines
5.2 KiB
Python
Raw Normal View History

2025-10-30 11:14:43 +08:00
"""统一日志配置模块 - Uvicorn风格"""
import logging
import sys
from pathlib import Path
from logging.handlers import RotatingFileHandler
from typing import Optional
class UvicornFormatter(logging.Formatter):
"""Uvicorn风格的日志格式化器"""
# 日志级别颜色(ANSI转义码)
COLORS = {
'DEBUG': '\033[36m', # 青色
'INFO': '\033[32m', # 绿色
'WARNING': '\033[33m', # 黄色
'ERROR': '\033[31m', # 红色
'CRITICAL': '\033[35m', # 紫色
}
RESET = '\033[0m'
def __init__(self, use_colors: bool = True):
"""
初始化格式化器
Args:
use_colors: 是否使用颜色(控制台输出使用,文件输出不使用)
"""
super().__init__()
self.use_colors = use_colors
def format(self, record):
"""格式化日志记录为 Uvicorn 风格"""
# 获取日志级别名称
levelname = record.levelname
# 添加颜色(如果启用且终端支持)
if self.use_colors and sys.stderr.isatty():
colored_level = f"{self.COLORS.get(levelname, '')}{levelname}{self.RESET}"
else:
colored_level = levelname
# 添加请求追踪ID(如果存在)
request_id = getattr(record, 'request_id', None)
request_id_str = f" [{request_id}]" if request_id else ""
# Uvicorn风格格式: INFO: module_name - message [request_id]
# 注意:INFO后面有5个空格,保持对齐
return f"{colored_level}: {record.name}{request_id_str} - {record.getMessage()}"
# 全局标志,防止重复初始化
_logging_configured = False
def setup_logging(
level: str = "INFO",
log_to_file: bool = False,
log_file_path: Optional[str] = None,
max_bytes: int = 10 * 1024 * 1024,
backup_count: int = 30
):
"""
配置统一的 Uvicorn 风格日志系统
Args:
level: 日志级别 (DEBUG, INFO, WARNING, ERROR, CRITICAL)
log_to_file: 是否输出到文件
log_file_path: 日志文件路径
max_bytes: 单个日志文件最大字节数(默认10MB)
backup_count: 保留的备份文件数量(默认30个)
"""
global _logging_configured
# 如果已经配置过,直接返回
if _logging_configured:
return logging.getLogger()
# 获取根日志器
root_logger = logging.getLogger()
root_logger.setLevel(getattr(logging, level.upper()))
# 清除已有的处理器,避免重复
root_logger.handlers.clear()
# 1. 创建控制台处理器(带颜色)
console_handler = logging.StreamHandler(sys.stderr)
console_handler.setLevel(getattr(logging, level.upper()))
console_formatter = UvicornFormatter(use_colors=True)
console_handler.setFormatter(console_formatter)
root_logger.addHandler(console_handler)
# 2. 创建文件处理器(如果启用)
if log_to_file and log_file_path:
# 确保日志目录存在
log_file = Path(log_file_path)
log_file.parent.mkdir(parents=True, exist_ok=True)
# 使用RotatingFileHandler实现日志轮转
file_handler = RotatingFileHandler(
filename=log_file_path,
maxBytes=max_bytes,
backupCount=backup_count,
encoding='utf-8'
)
file_handler.setLevel(getattr(logging, level.upper()))
# 文件日志不使用颜色
file_formatter = UvicornFormatter(use_colors=False)
file_handler.setFormatter(file_formatter)
root_logger.addHandler(file_handler)
# 记录日志配置信息
root_logger.info(f"日志文件输出已启用: {log_file_path}")
root_logger.info(f"日志轮转配置: 单文件最大{max_bytes / 1024 / 1024:.1f}MB, 保留{backup_count}个备份")
# 配置第三方库的日志级别
_configure_third_party_loggers()
# 标记为已配置
_logging_configured = True
return root_logger
def _configure_third_party_loggers():
"""配置第三方库的日志级别"""
# SQLAlchemy - 禁用SQL日志
logging.getLogger('sqlalchemy.engine').setLevel(logging.WARNING)
logging.getLogger('sqlalchemy.pool').setLevel(logging.WARNING)
logging.getLogger('sqlalchemy.dialects').setLevel(logging.WARNING)
logging.getLogger('sqlalchemy.orm').setLevel(logging.WARNING)
# Watchfiles - 开发时的文件监控,降低级别
logging.getLogger('watchfiles').setLevel(logging.WARNING)
# httpx - HTTP客户端
logging.getLogger('httpx').setLevel(logging.WARNING)
# openai/anthropic - AI客户端库
logging.getLogger('openai').setLevel(logging.WARNING)
logging.getLogger('anthropic').setLevel(logging.WARNING)
# 应用模块 - 可根据需要调整
logging.getLogger('app.services.ai_service').setLevel(logging.WARNING)
logging.getLogger('app.api.wizard').setLevel(logging.WARNING)
def get_logger(name: str) -> logging.Logger:
"""
获取指定名称的日志器
Args:
name: 日志器名称,通常使用 __name__
Returns:
配置好的日志器实例
"""
return logging.getLogger(name)