"""日志配置:使用 loguru 替代标准 logging,统一管理日志输出 - 控制台输出:始终启用 - 文件输出:生产环境默认启用,支持日志轮转、压缩和保留策略 - 拦截标准 logging:将 uvicorn 等第三方库的日志转发到 loguru """ from __future__ import annotations import logging import sys from pathlib import Path from types import FrameType from loguru import logger as loguru_logger from app.core.config import Settings class InterceptHandler(logging.Handler): """拦截标准 logging 日志,转发到 loguru""" def emit(self, record: logging.LogRecord) -> None: try: level: str | int = loguru_logger.level(record.levelname).name except ValueError: level = record.levelno frame: FrameType | None = logging.currentframe() depth = 2 while frame and frame.f_code.co_filename == logging.__file__: frame = frame.f_back depth += 1 loguru_logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage()) def configure_logging(settings: Settings) -> None: """根据配置初始化日志系统 - 移除 loguru 默认 handler - 添加控制台输出 - 生产环境添加文件输出(轮转、压缩、保留策略) - 拦截标准 logging,统一走 loguru 输出 """ loguru_logger.remove() loguru_logger.add( sys.stdout, level=settings.log_level.upper(), format=settings.log_format, enqueue=settings.log_enqueue, backtrace=False, diagnose=False, ) if settings.log_to_file_effective: log_dir = Path(settings.log_dir) log_dir.mkdir(parents=True, exist_ok=True) log_path = log_dir / f"app_{settings.app_env}.log" loguru_logger.add( str(log_path), level=settings.log_level.upper(), format=settings.log_format, rotation=settings.log_rotation, retention=settings.log_retention, compression=settings.log_compression, enqueue=settings.log_enqueue, encoding="utf-8", backtrace=False, diagnose=False, ) intercept = InterceptHandler() logging.basicConfig(handlers=[intercept], level=getattr(logging, settings.log_level.upper(), logging.INFO), force=True) # 将 uvicorn 的日志也转发到 loguru for name in ("uvicorn", "uvicorn.error", "uvicorn.access"): logger = logging.getLogger(name) logger.handlers = [intercept] logger.propagate = False # 降低 asyncio 的日志级别,避免过多输出 logging.getLogger("asyncio").setLevel(logging.WARNING)