""" Text2SQL API 仓库级日志:控制台 + 单一磁盘文件(每次进程启动清空 logs/*.log 后重写)。 由 ``api_server`` 在导入早期调用;与 ``uvicorn.run(..., log_config=False)`` 配合, 使 uvicorn / FastAPI 的日志经 root 统一落到文件。 """ from __future__ import annotations import logging from pathlib import Path def configure_text2sql_api_logging(repo_root: Path) -> Path: """ 配置 root logger:控制台(INFO,简短格式)+ ``logs/text2sql_api.log``(INFO,含文件名/行号/函数)。 每次调用会删除 ``logs`` 目录下所有 ``*.log``,再新建 ``text2sql_api.log``(覆盖写), 保证一次运行仅保留一个日志文件。 Returns: 主日志文件绝对路径。 """ log_dir = (repo_root / "logs").resolve() log_dir.mkdir(parents=True, exist_ok=True) for p in log_dir.glob("*.log"): try: p.unlink() except OSError: pass log_path = log_dir / "text2sql_api.log" root = logging.getLogger() root.setLevel(logging.INFO) for h in list(root.handlers): root.removeHandler(h) try: h.close() except Exception: pass fmt_console = logging.Formatter( "%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) fmt_file = logging.Formatter( "%(asctime)s %(levelname)-7s [%(name)s] %(filename)s:%(lineno)d %(funcName)s() | %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) fh = logging.FileHandler(log_path, mode="w", encoding="utf-8") fh.setLevel(logging.INFO) fh.setFormatter(fmt_file) ch = logging.StreamHandler() ch.setLevel(logging.INFO) ch.setFormatter(fmt_console) root.addHandler(fh) root.addHandler(ch) # 依赖库降噪(避免 httpx 每条请求刷屏;Chroma 遥测与新版 posthog 不兼容时会打 ERROR) logging.getLogger("httpx").setLevel(logging.WARNING) logging.getLogger("httpcore").setLevel(logging.WARNING) for _name in ( "chromadb.telemetry", "chromadb.telemetry.product.posthog", "posthog", ): logging.getLogger(_name).disabled = True # Uvicorn 默认自带 handler;与 log_config=False 合用时改为只往 root 冒泡 for name in ("uvicorn", "uvicorn.error", "uvicorn.access"): lg = logging.getLogger(name) lg.handlers.clear() lg.propagate = True logging.getLogger(__name__).info("日志文件: %s", log_path) return log_path