|
|
@@ -0,0 +1,77 @@
|
|
|
+import asyncio
|
|
|
+import json
|
|
|
+import time
|
|
|
+import threading
|
|
|
+from typing import List, Dict, Any, Optional
|
|
|
+from pathlib import Path
|
|
|
+import logging
|
|
|
+
|
|
|
+from config import config
|
|
|
+
|
|
|
+class RealtimeLogger:
|
|
|
+ _instance = None
|
|
|
+ _lock = threading.Lock()
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ self.logger = logging.getLogger("realtime_logger")
|
|
|
+ self._loop = None
|
|
|
+ self._connections: List[Dict[str, Any]] = []
|
|
|
+ self._log_buffer: List[Dict[str, Any]] = []
|
|
|
+ self._buffer_lock = threading.Lock()
|
|
|
+ self._max_buffer_size = 100
|
|
|
+
|
|
|
+ def set_loop(self, loop):
|
|
|
+ self._loop = loop
|
|
|
+
|
|
|
+ def add_connection(self, websocket):
|
|
|
+ with self._buffer_lock:
|
|
|
+ self._connections.append({"websocket": websocket, "last_active": time.time()})
|
|
|
+
|
|
|
+ def remove_connection(self, websocket):
|
|
|
+ with self._buffer_lock:
|
|
|
+ self._connections = [conn for conn in self._connections if conn["websocket"] != websocket]
|
|
|
+
|
|
|
+ async def broadcast_log(self, message: str, level: str = "INFO", source: str = "system"):
|
|
|
+ log_entry = {
|
|
|
+ "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
|
+ "level": level,
|
|
|
+ "source": source,
|
|
|
+ "message": message
|
|
|
+ }
|
|
|
+
|
|
|
+ with self._buffer_lock:
|
|
|
+ self._log_buffer.append(log_entry)
|
|
|
+ if len(self._log_buffer) > self._max_buffer_size:
|
|
|
+ self._log_buffer.pop(0)
|
|
|
+
|
|
|
+ disconnected = []
|
|
|
+ for connection in self._connections:
|
|
|
+ try:
|
|
|
+ await connection["websocket"].send_text(json.dumps(log_entry))
|
|
|
+ connection["last_active"] = time.time()
|
|
|
+ except Exception as e:
|
|
|
+ self.logger.warning(f"Failed to send log to websocket: {e}")
|
|
|
+ disconnected.append(connection["websocket"])
|
|
|
+
|
|
|
+ for ws in disconnected:
|
|
|
+ self.remove_connection(ws)
|
|
|
+
|
|
|
+ def broadcast_log_sync(self, message: str, level: str = "INFO", source: str = "system"):
|
|
|
+ if self._loop is None:
|
|
|
+ self.logger.warning("Event loop not set for RealtimeLogger")
|
|
|
+ return
|
|
|
+
|
|
|
+ asyncio.run_coroutine_threadsafe(
|
|
|
+ self.broadcast_log(message, level, source),
|
|
|
+ self._loop
|
|
|
+ )
|
|
|
+
|
|
|
+ def get_recent_logs(self, count: int = 10) -> List[Dict[str, Any]]:
|
|
|
+ with self._buffer_lock:
|
|
|
+ return self._log_buffer[-count:].copy()
|
|
|
+
|
|
|
+ def clear_buffer(self):
|
|
|
+ with self._buffer_lock:
|
|
|
+ self._log_buffer.clear()
|
|
|
+
|
|
|
+realtime_logger = RealtimeLogger()
|