320 lines
11 KiB
Python
320 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
WebSocket 脚本日志订阅测试
|
||
用于监控 VWED 脚本日志推送
|
||
"""
|
||
|
||
import asyncio
|
||
import json
|
||
from datetime import datetime
|
||
import signal
|
||
import sys
|
||
|
||
try:
|
||
import websockets
|
||
except ImportError:
|
||
print("❌ 缺少 websockets 依赖,请运行: pip install websockets")
|
||
sys.exit(1)
|
||
|
||
|
||
class VWEDWebSocketLogMonitor:
|
||
"""VWED WebSocket 日志监控器"""
|
||
|
||
def __init__(self, host="localhost", port=8000, client_id="monitor_test", client_type="monitor"):
|
||
self.host = host
|
||
self.port = port
|
||
self.client_id = client_id
|
||
self.client_type = client_type
|
||
self.websocket = None
|
||
self.running = False
|
||
self.subscribed_scripts = set()
|
||
|
||
async def connect(self):
|
||
"""连接到 WebSocket 服务器"""
|
||
uri = f"ws://{self.host}:{self.port}/api/script/ws/script-logs?client_id={self.client_id}&client_type={self.client_type}"
|
||
|
||
try:
|
||
print(f"正在连接到 WebSocket 服务器: {uri}")
|
||
self.websocket = await websockets.connect(uri)
|
||
self.running = True
|
||
print(f"✅ WebSocket 连接成功建立")
|
||
return True
|
||
except Exception as e:
|
||
print(f"❌ WebSocket 连接失败: {e}")
|
||
return False
|
||
|
||
async def disconnect(self):
|
||
"""断开连接"""
|
||
self.running = False
|
||
if self.websocket:
|
||
await self.websocket.close()
|
||
print("🔌 WebSocket 连接已断开")
|
||
|
||
async def subscribe_script(self, script_id):
|
||
"""订阅脚本日志"""
|
||
if not self.websocket:
|
||
print("WebSocket 未连接")
|
||
return False
|
||
|
||
try:
|
||
message = {
|
||
"type": "subscribe",
|
||
"script_id": script_id
|
||
}
|
||
await self.websocket.send(json.dumps(message))
|
||
self.subscribed_scripts.add(script_id)
|
||
print(f"📡 已发送订阅请求: {script_id}")
|
||
return True
|
||
except Exception as e:
|
||
print(f"订阅脚本失败: {e}")
|
||
return False
|
||
|
||
async def unsubscribe_script(self, script_id):
|
||
"""取消订阅脚本日志"""
|
||
if not self.websocket:
|
||
print("WebSocket 未连接")
|
||
return False
|
||
|
||
try:
|
||
message = {
|
||
"type": "unsubscribe",
|
||
"script_id": script_id
|
||
}
|
||
await self.websocket.send(json.dumps(message))
|
||
self.subscribed_scripts.discard(script_id)
|
||
print(f" 已发送取消订阅请求: {script_id}")
|
||
return True
|
||
except Exception as e:
|
||
print(f"取消订阅脚本失败: {e}")
|
||
return False
|
||
|
||
async def get_status(self):
|
||
"""获取连接状态"""
|
||
if not self.websocket:
|
||
print("WebSocket 未连接")
|
||
return False
|
||
|
||
try:
|
||
message = {"type": "get_status"}
|
||
await self.websocket.send(json.dumps(message))
|
||
print("已发送状态查询请求")
|
||
return True
|
||
except Exception as e:
|
||
print(f"获取状态失败: {e}")
|
||
return False
|
||
|
||
async def send_ping(self):
|
||
"""发送心跳"""
|
||
if not self.websocket:
|
||
return False
|
||
|
||
try:
|
||
message = {
|
||
"type": "ping",
|
||
"timestamp": datetime.now().isoformat()
|
||
}
|
||
await self.websocket.send(json.dumps(message))
|
||
return True
|
||
except Exception as e:
|
||
print(f"❌ 发送心跳失败: {e}")
|
||
return False
|
||
|
||
def format_message(self, message):
|
||
"""格式化消息显示"""
|
||
msg_type = message.get("type", "unknown")
|
||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||
|
||
if msg_type == "welcome":
|
||
return f"🎉 [{timestamp}] 欢迎消息: {message.get('message')} (连接ID: {message.get('connection_id')})"
|
||
|
||
elif msg_type == "script_log":
|
||
script_id = message.get("script_id", "unknown")
|
||
level = message.get("level", "INFO")
|
||
log_msg = message.get("message", "")
|
||
|
||
# 根据日志级别选择emoji
|
||
level_emoji = {
|
||
"INFO": "ℹ️",
|
||
"WARNING": "⚠️",
|
||
"ERROR": "❌",
|
||
"DEBUG": "🐛"
|
||
}
|
||
emoji = level_emoji.get(level, "📝")
|
||
|
||
return f"{emoji} [{timestamp}] [{script_id}] {level}: {log_msg}"
|
||
|
||
elif msg_type == "script_status":
|
||
script_id = message.get("script_id", "unknown")
|
||
status = message.get("status", "unknown")
|
||
status_msg = message.get("message", "")
|
||
return f"🔄 [{timestamp}] [{script_id}] 状态变更: {status} - {status_msg}"
|
||
|
||
elif msg_type == "function_execution":
|
||
script_id = message.get("script_id", "unknown")
|
||
func_name = message.get("function_name", "unknown")
|
||
result = message.get("result", {})
|
||
return f"⚡ [{timestamp}] [{script_id}] 函数执行: {func_name} -> {json.dumps(result, ensure_ascii=False)}"
|
||
|
||
elif msg_type == "subscription_success":
|
||
return f"✅ [{timestamp}] 订阅成功: {message.get('message')}"
|
||
|
||
elif msg_type == "unsubscription_success":
|
||
return f"✅ [{timestamp}] 取消订阅成功: {message.get('message')}"
|
||
|
||
elif msg_type == "status":
|
||
data = message.get("data", {})
|
||
total_connections = data.get("total_connections", 0)
|
||
total_scripts = data.get("total_script_subscriptions", 0)
|
||
return f"📊 [{timestamp}] 连接状态: {total_connections} 个连接, {total_scripts} 个脚本订阅"
|
||
|
||
elif msg_type == "error":
|
||
return f"❌ [{timestamp}] 错误: {message.get('message')}"
|
||
|
||
elif msg_type == "pong":
|
||
return f"💓 [{timestamp}] 心跳响应"
|
||
|
||
else:
|
||
return f"❓ [{timestamp}] 未知消息类型 {msg_type}: {json.dumps(message, ensure_ascii=False)}"
|
||
|
||
async def listen_messages(self):
|
||
"""监听消息"""
|
||
if not self.websocket:
|
||
print("❌ WebSocket 未连接")
|
||
return
|
||
|
||
try:
|
||
while self.running:
|
||
try:
|
||
# 等待消息,设置超时避免无限阻塞
|
||
message = await asyncio.wait_for(
|
||
self.websocket.recv(),
|
||
timeout=1.0
|
||
)
|
||
|
||
try:
|
||
data = json.loads(message)
|
||
formatted_msg = self.format_message(data)
|
||
print(formatted_msg)
|
||
except json.JSONDecodeError:
|
||
print(f"❌ JSON 解析失败: {message}")
|
||
|
||
except asyncio.TimeoutError:
|
||
# 超时是正常的,继续循环
|
||
continue
|
||
except websockets.exceptions.ConnectionClosed:
|
||
print("🔌 WebSocket 连接已关闭")
|
||
break
|
||
except Exception as e:
|
||
print(f"❌ 接收消息异常: {e}")
|
||
break
|
||
|
||
except Exception as e:
|
||
print(f"❌ 监听消息异常: {e}")
|
||
|
||
async def interactive_monitor(self):
|
||
"""交互式监控"""
|
||
print("\n" + "="*50)
|
||
print("VWED WebSocket 脚本日志监控器")
|
||
print("="*50)
|
||
print("可用命令:")
|
||
print(" sub <script_id> - 订阅脚本日志")
|
||
print(" unsub <script_id> - 取消订阅脚本日志")
|
||
print(" status - 获取连接状态")
|
||
print(" ping - 发送心跳")
|
||
print(" list - 显示已订阅的脚本")
|
||
print(" quit - 退出监控")
|
||
print("="*50)
|
||
|
||
# 启动消息监听任务
|
||
listen_task = asyncio.create_task(self.listen_messages())
|
||
|
||
try:
|
||
while self.running:
|
||
try:
|
||
# 等待用户输入
|
||
user_input = await asyncio.get_event_loop().run_in_executor(
|
||
None, input, "请输入命令 (sub/unsub/status/ping/list/quit): "
|
||
)
|
||
|
||
command_parts = user_input.strip().split()
|
||
if not command_parts:
|
||
continue
|
||
|
||
command = command_parts[0].lower()
|
||
|
||
if command == "quit":
|
||
print("👋 退出监控...")
|
||
break
|
||
elif command == "sub" and len(command_parts) > 1:
|
||
script_id = command_parts[1]
|
||
await self.subscribe_script(script_id)
|
||
elif command == "unsub" and len(command_parts) > 1:
|
||
script_id = command_parts[1]
|
||
await self.unsubscribe_script(script_id)
|
||
elif command == "status":
|
||
await self.get_status()
|
||
elif command == "ping":
|
||
await self.send_ping()
|
||
elif command == "list":
|
||
if self.subscribed_scripts:
|
||
print(f"📋 已订阅的脚本: {', '.join(self.subscribed_scripts)}")
|
||
else:
|
||
print("📋 当前没有订阅任何脚本")
|
||
else:
|
||
print("❌ 未知命令或参数不足")
|
||
|
||
except KeyboardInterrupt:
|
||
print("\n👋 收到退出信号...")
|
||
break
|
||
except Exception as e:
|
||
print(f"❌ 处理命令异常: {e}")
|
||
|
||
finally:
|
||
self.running = False
|
||
listen_task.cancel()
|
||
try:
|
||
await listen_task
|
||
except asyncio.CancelledError:
|
||
pass
|
||
|
||
|
||
async def main():
|
||
"""主函数"""
|
||
monitor = VWEDWebSocketLogMonitor()
|
||
|
||
# 设置信号处理
|
||
def signal_handler(signum, _):
|
||
print(f"\n👋 收到信号 {signum},正在退出...")
|
||
monitor.running = False
|
||
|
||
signal.signal(signal.SIGINT, signal_handler)
|
||
signal.signal(signal.SIGTERM, signal_handler)
|
||
|
||
try:
|
||
# 连接 WebSocket
|
||
if await monitor.connect():
|
||
# 开始交互式监控
|
||
await monitor.interactive_monitor()
|
||
else:
|
||
print("❌ 无法连接到 WebSocket 服务器")
|
||
return 1
|
||
|
||
except Exception as e:
|
||
print(f"❌ 监控异常: {e}")
|
||
return 1
|
||
|
||
finally:
|
||
# 清理连接
|
||
await monitor.disconnect()
|
||
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
try:
|
||
exit_code = asyncio.run(main())
|
||
sys.exit(exit_code)
|
||
except KeyboardInterrupt:
|
||
print("\n👋 程序已退出")
|
||
sys.exit(0) |