"""
loguru 统一日志配置 + InterceptHandler 拦截标准库 logging。
"""
import logging
import sys
from loguru import logger
class InterceptHandler(logging.Handler):
"""Route standard-library logging messages into loguru."""
def emit(self, record: logging.LogRecord) -> None:
try:
level = logger.level(record.levelname).name
except ValueError:
level = record.levelno
frame, depth = logging.currentframe(), 2
while frame and 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_logging() -> None:
"""Call once at process entry (API:`main`;Worker:`celery_app` 在首行调用)。
Celery 需在 `app.tasks.celery_app` 中设置 `worker_hijack_root_logger=False`,否则
会覆盖根 logger,无法与下方 InterceptHandler + loguru 格式对齐。
"""
logger.remove()
logger.add(
sys.stderr,
level="INFO",
format=(
"{time:YYYY-MM-DD HH:mm:ss.SSS} | "
"{level.name: <8} | "
"{name}:{function}:{line} | "
"{extra[request_id]} | "
"{message}"
),
backtrace=True,
diagnose=False,
)
# 将根 logger 重定向到 loguru,不再使用 basicConfig(文档要求:统一走 loguru)
root = logging.getLogger()
root.handlers = [InterceptHandler()]
root.setLevel(0)
for name in (
"uvicorn",
"uvicorn.error",
"uvicorn.access",
"sqlalchemy.engine",
"celery",
"celery.worker",
):
logging.getLogger(name).handlers = [InterceptHandler()]
logger.configure(extra={"request_id": "-"})
def get_logger(name: str) -> logging.Logger:
"""获取具名 logger,统一走 loguru 拦截。各模块应使用此函数而非直接 import logging。"""
return logging.getLogger(name)