Files
iov_data_analysis_agent/utils/logger.py

114 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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()