63 lines
1.9 KiB
Python
63 lines
1.9 KiB
Python
"""统一配置应用、Uvicorn 和第三方库日志"""
|
|
|
|
import logging
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from loguru import logger
|
|
|
|
LOG_PATH = Path(__file__).resolve().parents[2] / "logs" / "app.log"
|
|
LOG_FORMAT = "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {extra[thread_id]} | {extra[node]} | {message}"
|
|
_initialized = False
|
|
|
|
|
|
class InterceptHandler(logging.Handler):
|
|
"""将标准 logging 转发到 Loguru,兼容第三方库"""
|
|
|
|
def emit(self, record: logging.LogRecord) -> None:
|
|
# FastAPI 已记录的异常不再由 Uvicorn 重复打印堆栈
|
|
if record.exc_info and getattr(record.exc_info[1], "_logged_by_api", False):
|
|
return
|
|
try:
|
|
level = logger.level(record.levelname).name
|
|
except ValueError:
|
|
level = record.levelno
|
|
logger.opt(exception=record.exc_info).log(level, record.getMessage())
|
|
|
|
|
|
def setup_logging() -> None:
|
|
"""应用启动或独立测试入口调用,多次调用不会重复添加输出"""
|
|
global _initialized
|
|
if _initialized:
|
|
return
|
|
|
|
LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
logger.remove()
|
|
logger.configure(extra={"thread_id": "-", "node": "-"})
|
|
|
|
options = {
|
|
"level": "INFO",
|
|
"format": LOG_FORMAT,
|
|
"enqueue": True,
|
|
"diagnose": False,
|
|
"backtrace": False,
|
|
}
|
|
|
|
logger.add(sys.stderr, colorize=True, **options)
|
|
logger.add(
|
|
LOG_PATH,
|
|
rotation="20 MB",
|
|
retention="14 days",
|
|
compression="zip",
|
|
encoding="utf-8",
|
|
**options,
|
|
)
|
|
|
|
# 清理已有输出处理器,让标准日志只经过根处理器输出一次
|
|
logging.basicConfig(handlers=[InterceptHandler()], level=logging.INFO, force=True)
|
|
for name in list(logging.root.manager.loggerDict):
|
|
existing = logging.getLogger(name)
|
|
existing.handlers.clear()
|
|
existing.propagate = True
|
|
_initialized = True
|