import logging
import sys
from loguru import logger
class InterceptHandler(logging.Handler):
def emit(self, record):
# 获取对应的 Loguru 级别
try:
level = logger.level(record.levelname).name
except ValueError:
level = record.levelno
# 从 logging 记录中找到调用位置
frame, depth = logging.currentframe(), 2
while frame.f_code.co_filename == logging.__file__:
frame = frame.f_back
depth += 1
logger.opt(depth=depth, exception=record.exc_info).log(
level, record.getMessage()
)
def setup_logger():
"""
配置 Loguru 日志记录器
- 移除默认的 handler
- 添加一个用于 INFO 级别日志的文件 sink
- 添加一个用于 ERROR 级别日志的文件 sink
- 保持控制台输出(级别为 DEBUG)
"""
# 检查是否已经初始化,防止重复初始化
if hasattr(setup_logger, '_initialized') and setup_logger._initialized:
return
# 1. 移除默认的控制台 handler
logger.remove()
# 2. 添加一个新的控制台 handler,设置级别为 DEBUG
logger.add(
sys.stderr,
level="INFO",
format="{time:YYYY-MM-DD HH:mm:ss.SSS} | "
"{level: <8} | "
"{name}:{function}:{line} - {message}",
colorize=True, # 开启颜色
)
# 3. 添加 INFO 日志文件 sink
# - level="INFO": 只记录 INFO 及以上级别的日志
# - filter: 确保 ERROR 级别的日志不会写入这个文件
# - rotation: 每天午夜创建一个新文件
# - retention: 只保留最近 14 天的日志
# - enqueue=True: 异步写入,提升性能
# - encoding="utf-8": 确保中文不会乱码
logger.add(
"logs/app.info.log",
level="INFO",
filter=lambda record: record["level"].name in ["INFO", "WARNING"],
rotation="00:00", # 每天午夜轮转
retention="14 days", # 保留 14 天
enqueue=True, # 异步写入
backtrace=True, # 记录堆栈信息
diagnose=True, # 诊断信息
encoding="utf-8",
)
# 4. 添加 ERROR 日志文件 sink
# - level="ERROR": 只记录 ERROR 及以上级别的日志 (ERROR, CRITICAL)
# - rotation: 文件大小超过 10 MB 时轮转
# - retention: 只保留最近 30 天的日志
logger.add(
"logs/app.error.log",
level="ERROR",
rotation="10 MB", # 按文件大小轮转
retention="30 days", # 保留 30 天
enqueue=True, # 异步写入
backtrace=True,
diagnose=True,
encoding="utf-8",
)
# 拦截 logging,以便将所有日志都转发到 Loguru
logging.basicConfig(handlers=[InterceptHandler()], level=0, force=True)
# 设置已初始化标志
setup_logger._initialized = True
logger.info("日志系统配置完成。")