VWED_server/services/online_script/script_output_capture.py

123 lines
4.1 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.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
脚本输出捕获工具
用于捕获脚本执行过程中的标准输出和错误输出并通过WebSocket推送给订阅的客户端
"""
import asyncio
import sys
from io import StringIO
from contextlib import contextmanager
from .script_websocket_service import get_websocket_manager
from utils.logger import get_logger
logger = get_logger("services.script_output_capture")
class ScriptOutputCapture:
"""脚本输出捕获类"""
def __init__(self, script_id: str, level: str = "INFO", original_stream=None):
self.script_id = script_id
self.level = level
self.websocket_manager = get_websocket_manager()
self.buffer = StringIO()
self.original_stream = original_stream # 保留原始输出流
def write(self, text):
"""写入文本同时推送到WebSocket和原始输出流"""
# 1. 写入原始输出流(如控制台)
if self.original_stream:
try:
self.original_stream.write(text)
self.original_stream.flush()
except Exception as e:
logger.debug(f"写入原始输出流失败: {e}")
# 2. 推送到WebSocket仅对非空内容
if text and text.strip():
try:
# 尝试在当前事件循环中创建任务
asyncio.create_task(self._push_output(text.strip()))
except RuntimeError:
# 如果没有运行中的事件循环,记录日志但不报错
logger.debug(f"无法推送输出到WebSocket没有运行中的事件循环: [{self.script_id}] {text.strip()}")
# 3. 写入缓冲区保留原有行为
self.buffer.write(text)
def flush(self):
"""刷新缓冲区"""
self.buffer.flush()
if self.original_stream:
try:
self.original_stream.flush()
except Exception:
pass
async def _push_output(self, message: str):
"""推送输出到WebSocket"""
try:
await self.websocket_manager.broadcast_script_log(
self.script_id, self.level, message
)
except Exception as e:
logger.debug(f"推送脚本输出失败 [{self.script_id}]: {e}")
@contextmanager
def capture_script_output(script_id: str):
"""
上下文管理器,用于捕获脚本执行过程中的标准输出和错误输出
使用示例:
with capture_script_output("script_123"):
print("这条消息会被捕获并推送到WebSocket同时显示在控制台")
# 执行脚本代码
"""
# 备份原始输出流
original_stdout = sys.stdout
original_stderr = sys.stderr
# 创建捕获器,传入原始输出流以保持双重输出
stdout_capture = ScriptOutputCapture(script_id, "INFO", original_stdout)
stderr_capture = ScriptOutputCapture(script_id, "ERROR", original_stderr)
try:
# 替换标准输出流
sys.stdout = stdout_capture
sys.stderr = stderr_capture
yield {
'stdout_capture': stdout_capture,
'stderr_capture': stderr_capture
}
finally:
# 恢复原始输出流
sys.stdout = original_stdout
sys.stderr = original_stderr
async def push_script_error(script_id: str, error_message: str):
"""推送脚本错误信息到WebSocket"""
try:
websocket_manager = get_websocket_manager()
await websocket_manager.broadcast_script_log(
script_id, "ERROR", error_message
)
except Exception as e:
logger.debug(f"推送脚本错误信息失败 [{script_id}]: {e}")
async def push_script_info(script_id: str, info_message: str):
"""推送脚本信息到WebSocket"""
try:
websocket_manager = get_websocket_manager()
await websocket_manager.broadcast_script_log(
script_id, "INFO", info_message
)
except Exception as e:
logger.debug(f"推送脚本信息失败 [{script_id}]: {e}")