# -*- coding: utf-8 -*- """ 统一日志模块 - 替代全局 sys.stdout 劫持 提供线程安全的日志记录,支持同时输出到终端和文件。 每个会话拥有独立的日志文件,不会互相干扰。 """ import logging import os import sys from datetime import datetime from typing import Optional def create_session_logger( session_id: str, log_dir: str, log_filename: str = "process.log", level: int = logging.INFO, ) -> logging.Logger: """ 为指定会话创建独立的 Logger 实例。 Args: session_id: 会话唯一标识 log_dir: 日志文件所在目录 log_filename: 日志文件名 level: 日志级别 Returns: 配置好的 Logger 实例 """ logger = logging.getLogger(f"session.{session_id}") logger.setLevel(level) # 避免重复添加 handler if logger.handlers: return logger formatter = logging.Formatter( fmt="%(asctime)s %(message)s", datefmt="%H:%M:%S", ) # 文件 handler — 写入会话专属日志 os.makedirs(log_dir, exist_ok=True) log_path = os.path.join(log_dir, log_filename) file_handler = logging.FileHandler(log_path, encoding="utf-8", mode="a") file_handler.setFormatter(formatter) logger.addHandler(file_handler) # 终端 handler — 输出到 stderr(不干扰 stdout) console_handler = logging.StreamHandler(sys.stderr) console_handler.setFormatter(formatter) logger.addHandler(console_handler) # 不向父 logger 传播 logger.propagate = False return logger class PrintCapture: """ 轻量级 print 捕获器,将 print 输出同时写入日志文件。 用于兼容现有大量使用 print() 的代码,无需逐行改造。 用法: with PrintCapture(log_path) as cap: print("hello") # 同时输出到终端和文件 # 退出后 sys.stdout 自动恢复 """ def __init__(self, log_path: str, filter_patterns: Optional[list] = None): self.log_path = log_path self.filter_patterns = filter_patterns or ["[TOOL] 执行代码:"] self._original_stdout = None self._log_file = None def __enter__(self): os.makedirs(os.path.dirname(self.log_path), exist_ok=True) self._original_stdout = sys.stdout self._log_file = open(self.log_path, "a", encoding="utf-8", buffering=1) sys.stdout = self._DualWriter( self._original_stdout, self._log_file, self.filter_patterns ) return self def __exit__(self, exc_type, exc_val, exc_tb): sys.stdout = self._original_stdout if self._log_file: self._log_file.close() return False class _DualWriter: """同时写入两个流,支持过滤""" def __init__(self, terminal, log_file, filter_patterns): self.terminal = terminal self.log_file = log_file self.filter_patterns = filter_patterns def write(self, message): self.terminal.write(message) # 过滤不需要写入日志的内容 if any(p in message for p in self.filter_patterns): return self.log_file.write(message) def flush(self): self.terminal.flush() self.log_file.flush()