""" loguru 统一日志配置 + InterceptHandler 拦截标准库 logging。 日志约定: - INFO:面向运维的稳定摘要,避免敏感字段与高频噪音。 - DEBUG:可记录完整上下文、用户内容、连接串、URL 等敏感信息;仅用于受控环境排查, 生产环境勿长期开启 DEBUG。 由 ``Settings.log_level`` 控制(环境变量 ``LOG_LEVEL``,默认 ``INFO``); 设为 ``DEBUG`` 时上述详细日志才会输出。 """ import logging import sys from loguru import logger from app.core.config import settings def _sink_min_level() -> str: raw = (settings.log_level or "INFO").strip().upper() if raw in ("TRACE", "DEBUG", "INFO", "SUCCESS", "WARNING", "ERROR", "CRITICAL"): return raw return "INFO" 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=_sink_min_level(), 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)