""" 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)