mcp_logger.py 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. # -*- coding: utf-8 -*-
  2. """
  3. MCP 调用日志工具
  4. 从 .env 读取 LOG_LEVEL 配置,低于该级别的日志静默忽略。
  5. MCP 调用的详细日志(工具名、参数、返回值)统一通过此模块输出。
  6. 注意:fastmcp 库自身会输出大量内部日志(连接握手、SSE 消息等),
  7. 本模块在导入时将 fastmcp 相关 logger 强制设为 WARNING,避免噪音。
  8. 日志级别(从低到高):DEBUG < INFO < WARNING < ERROR
  9. 使用方式:
  10. from tools.mcp_logger import log_mcp_call
  11. log_mcp_call(tool_name, arguments, result_raw)
  12. """
  13. import json
  14. import logging
  15. import os
  16. from pathlib import Path
  17. from dotenv import dotenv_values
  18. # ── 压制 fastmcp 库的内部日志噪音 ──
  19. for _name in ("mcp", "mcp.client", "mcp.server", "fastmcp", "httpx", "httpcore"):
  20. logging.getLogger(_name).setLevel(logging.WARNING)
  21. # ── 读取 .env 中的 LOG_LEVEL ──
  22. _env_file = Path(__file__).parent.parent / ".env"
  23. _cfg = dotenv_values(str(_env_file))
  24. _level_str = _cfg.get("LOG_LEVEL", "info").strip().upper()
  25. _level_map = {
  26. "DEBUG": logging.DEBUG,
  27. "INFO": logging.INFO,
  28. "WARNING": logging.WARNING,
  29. "ERROR": logging.ERROR,
  30. "CRITICAL": logging.CRITICAL,
  31. }
  32. _level = _level_map.get(_level_str, logging.INFO)
  33. # ── 配置业务 logger(与 fastmcp 隔离)──
  34. _logger = logging.getLogger("vent.mcp")
  35. _logger.setLevel(_level)
  36. if not _logger.handlers:
  37. _handler = logging.StreamHandler()
  38. _handler.setLevel(_level)
  39. _handler.setFormatter(logging.Formatter(
  40. "[MCP] %(asctime)s %(message)s",
  41. datefmt="%H:%M:%S",
  42. ))
  43. _logger.addHandler(_handler)
  44. def log_mcp_call(tool_name: str, arguments: dict, result_raw: str, error: str | None = None):
  45. """记录一次 MCP 工具调用的完整信息(INFO 级别,始终可见)。
  46. 输出内容:
  47. - 工具名 + 参数
  48. - 返回结果总字符数
  49. - 返回结果前 1000 字符(截断标记)
  50. - 若出错,输出错误信息(ERROR 级别)
  51. Args:
  52. tool_name: MCP 工具名称
  53. arguments: 调用参数字典
  54. result_raw: 返回的原始字符串
  55. error: 异常信息(成功时为 None)
  56. """
  57. params_str = json.dumps(arguments, ensure_ascii=False)
  58. result_len = len(result_raw) if result_raw else 0
  59. if error:
  60. _logger.error(
  61. "✗ %s 调用失败 | 参数: %s | 错误: %s",
  62. tool_name, params_str, error,
  63. )
  64. else:
  65. preview = result_raw[:1000]
  66. suffix = "…" if len(result_raw) > 1000 else ""
  67. _logger.info(
  68. "→ %s | 参数: %s\n"
  69. "← 返回 %d 字符 | 预览: %s%s",
  70. tool_name, params_str,
  71. result_len, preview, suffix,
  72. )