101 lines
3.3 KiB
Python
101 lines
3.3 KiB
Python
"""日志配置模块"""
|
||
|
||
import os
|
||
import sys
|
||
import logging
|
||
from datetime import date
|
||
from logging.handlers import TimedRotatingFileHandler
|
||
from pathlib import Path
|
||
|
||
from loguru import logger
|
||
|
||
|
||
def _should_rotate(message, file):
|
||
"""自定义轮转条件:每天0点轮转,或单文件超过10M。"""
|
||
if not file:
|
||
return False
|
||
try:
|
||
# 单文件超过 10M 则轮转
|
||
if os.path.getsize(file.name) > 10 * 1024 * 1024:
|
||
return True
|
||
# 跨天则轮转:日志日期 ≠ 文件最后修改日期
|
||
file_date = date.fromtimestamp(os.path.getmtime(file.name))
|
||
log_date = message.record["time"].date()
|
||
return file_date != log_date
|
||
except OSError:
|
||
return False
|
||
|
||
|
||
def _configure_standard_logging(level: str, log_dir: Path) -> None:
|
||
"""将 Uvicorn/FastAPI 的标准日志同步到控制台和文件。"""
|
||
log_level = getattr(logging, level.upper(), logging.INFO)
|
||
formatter = logging.Formatter(
|
||
"%(asctime)s | %(levelname)-8s | %(name)s | %(message)s"
|
||
)
|
||
|
||
console_handler = logging.StreamHandler(sys.stdout)
|
||
console_handler.setLevel(log_level)
|
||
console_handler.setFormatter(formatter)
|
||
|
||
file_handler = TimedRotatingFileHandler(
|
||
log_dir / "server.log", when="midnight", backupCount=7, encoding="utf-8"
|
||
)
|
||
file_handler.setLevel(log_level)
|
||
file_handler.setFormatter(formatter)
|
||
|
||
for name in ("uvicorn", "uvicorn.error", "uvicorn.access", "fastapi", "starlette"):
|
||
standard_logger = logging.getLogger(name)
|
||
standard_logger.handlers = [console_handler, file_handler]
|
||
standard_logger.setLevel(log_level)
|
||
standard_logger.propagate = False
|
||
|
||
|
||
def setup_logger(level: str = "INFO", log_dir: str = None, log_file: str = None) -> None:
|
||
"""
|
||
配置日志
|
||
|
||
Args:
|
||
level: 日志级别
|
||
log_dir: 日志目录路径(文件名按日期自动生成,如 app-2026-06-24.log)
|
||
log_file: 日志文件路径(兼容旧接口,优先级低于 log_dir)
|
||
"""
|
||
# 移除默认handler
|
||
logger.remove()
|
||
|
||
# 控制台输出
|
||
logger.add(
|
||
sys.stdout,
|
||
level=level,
|
||
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | "
|
||
"<level>{level: <8}</level> | "
|
||
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | "
|
||
"<level>{message}</level>",
|
||
colorize=True,
|
||
)
|
||
|
||
# 确定日志文件路径
|
||
if log_dir:
|
||
# 新接口:按目录 + 日期命名
|
||
dir_path = Path(log_dir)
|
||
dir_path.mkdir(parents=True, exist_ok=True)
|
||
_configure_standard_logging(level, dir_path)
|
||
file_path = str(dir_path / "app-{time:YYYY-MM-DD}.log")
|
||
elif log_file:
|
||
# 兼容旧接口
|
||
file_path = Path(log_file)
|
||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||
file_path = str(file_path)
|
||
else:
|
||
return
|
||
|
||
# 文件输出:按天命名 + 单文件最大10M轮转 + 保留7天
|
||
logger.add(
|
||
file_path,
|
||
level=level,
|
||
format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} | {message}",
|
||
rotation=_should_rotate, # 超过10M轮转
|
||
retention="7 days", # 保留7天
|
||
compression="zip", # 旧日志自动压缩节省空间
|
||
encoding="utf-8",
|
||
)
|