-
Notifications
You must be signed in to change notification settings - Fork 391
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Fix agent bug, remove alias * Allow ws on logs
- Loading branch information
Showing
8 changed files
with
574 additions
and
210 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>R2R Log Viewer</title> | ||
<style> | ||
body { | ||
margin: 20px; | ||
font-family: monospace; | ||
background: #f8f9fa; | ||
} | ||
#logs { | ||
white-space: pre-wrap; | ||
background: white; | ||
padding: 20px; | ||
border-radius: 4px; | ||
height: 80vh; | ||
overflow-y: auto; | ||
border: 1px solid #e9ecef; | ||
box-shadow: 0 2px 4px rgba(0,0,0,0.05); | ||
} | ||
.log-entry { | ||
margin: 2px 0; | ||
border-bottom: 1px solid #f0f0f0; | ||
} | ||
.status { | ||
color: #666; | ||
font-style: italic; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<h2>R2R Log Viewer</h2> | ||
<div id="logs"><span class="status">Connecting to log stream...</span></div> | ||
|
||
<!-- Include ansi_up via a CDN --> | ||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/ansi_up.min.js"></script> | ||
<script> | ||
let ws = null; | ||
let ansi_up = new AnsiUp(); | ||
|
||
function connect() { | ||
if (ws) { | ||
ws.close(); | ||
} | ||
|
||
ws = new WebSocket(`ws://${window.location.host}/v3/logs/stream`); | ||
|
||
ws.onmessage = function(event) { | ||
const logsDiv = document.getElementById("logs"); | ||
const newEntry = document.createElement('div'); | ||
newEntry.className = 'log-entry'; | ||
|
||
// Convert ANSI to HTML | ||
const htmlContent = ansi_up.ansi_to_html(event.data); | ||
newEntry.innerHTML = htmlContent; | ||
logsDiv.appendChild(newEntry); | ||
|
||
// Keep only the last 1000 entries | ||
while (logsDiv.children.length > 1000) { | ||
logsDiv.removeChild(logsDiv.firstChild); | ||
} | ||
|
||
logsDiv.scrollTop = logsDiv.scrollHeight; | ||
}; | ||
|
||
ws.onclose = function() { | ||
const logsDiv = document.getElementById("logs"); | ||
const msg = document.createElement('div'); | ||
msg.className = 'status'; | ||
msg.textContent = 'Connection lost. Reconnecting...'; | ||
logsDiv.appendChild(msg); | ||
setTimeout(connect, 1000); | ||
}; | ||
|
||
ws.onerror = function(err) { | ||
console.error('WebSocket error:', err); | ||
}; | ||
} | ||
|
||
connect(); | ||
|
||
window.onbeforeunload = function() { | ||
if (ws) { | ||
ws.close(); | ||
} | ||
}; | ||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
from fastapi import WebSocket | ||
import contextlib | ||
from fastapi.requests import Request | ||
from fastapi.templating import Jinja2Templates | ||
from pathlib import Path | ||
import asyncio | ||
import logging | ||
import aiofiles | ||
|
||
from core.providers import ( | ||
HatchetOrchestrationProvider, | ||
SimpleOrchestrationProvider, | ||
) | ||
from core.base.logger.base import RunType | ||
from .base_router import BaseRouterV3 | ||
|
||
|
||
class LogsRouter(BaseRouterV3): | ||
def __init__( | ||
self, | ||
providers, | ||
services, | ||
orchestration_provider: ( | ||
HatchetOrchestrationProvider | SimpleOrchestrationProvider | ||
), | ||
run_type: RunType = RunType.UNSPECIFIED, | ||
): | ||
super().__init__(providers, services, orchestration_provider, run_type) | ||
CURRENT_DIR = Path(__file__).resolve().parent | ||
TEMPLATES_DIR = CURRENT_DIR.parent / "templates" | ||
self.templates = Jinja2Templates(directory=str(TEMPLATES_DIR)) | ||
self.services = services | ||
self.log_file = Path.cwd() / "logs" / "app.log" | ||
self.log_file.parent.mkdir(exist_ok=True) | ||
if not self.log_file.exists(): | ||
self.log_file.touch(mode=0o666) | ||
|
||
# Start from the beginning of the file | ||
self.last_position = 0 | ||
|
||
async def read_full_file(self) -> str: | ||
"""Read the entire log file from the start.""" | ||
if not self.log_file.exists(): | ||
return "Initializing logging system..." | ||
|
||
try: | ||
async with aiofiles.open(self.log_file, mode="r") as f: | ||
# Start at beginning | ||
await f.seek(0) | ||
full_content = await f.read() | ||
# Move last_position to end of file after reading full content | ||
self.last_position = await f.tell() | ||
return full_content | ||
except Exception as e: | ||
logging.error(f"Error reading full logs: {str(e)}") | ||
return f"Error accessing full log file: {str(e)}" | ||
|
||
async def read_new_logs(self) -> str: | ||
"""Read new logs appended after last_position.""" | ||
if not self.log_file.exists(): | ||
return "Initializing logging system..." | ||
|
||
try: | ||
async with aiofiles.open(self.log_file, mode="r") as f: | ||
await f.seek(self.last_position) | ||
new_content = await f.read() | ||
self.last_position = await f.tell() | ||
return new_content or "" | ||
except Exception as e: | ||
logging.error(f"Error reading logs: {str(e)}") | ||
return f"Error accessing log file: {str(e)}" | ||
|
||
def _setup_routes(self): | ||
@self.router.websocket("/logs/stream") | ||
async def stream_logs(websocket: WebSocket): | ||
await websocket.accept() | ||
try: | ||
# Send the entire file content upon initial connection | ||
full_logs = await self.read_full_file() | ||
if full_logs: | ||
await websocket.send_text(full_logs) | ||
|
||
# Now send incremental updates only | ||
while True: | ||
new_logs = await self.read_new_logs() | ||
if new_logs: | ||
await websocket.send_text(new_logs) | ||
await asyncio.sleep(0.5) | ||
except Exception as e: | ||
logging.error(f"WebSocket error: {str(e)}") | ||
finally: | ||
with contextlib.suppress(Exception): | ||
await websocket.close() | ||
|
||
@self.router.get("/logs/viewer") | ||
async def get_log_viewer(request: Request): | ||
return self.templates.TemplateResponse( | ||
"log_viewer.html", {"request": request} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.