Spaces:
Sleeping
Sleeping
Claude Code Claude Opus 4.6 commited on
Commit ·
9a2f3b7
1
Parent(s): 72a64e0
Claude Code: add system status logging module with /admin/system endpoint
Browse files- Add .openclaw/core/system_logger.py for startup/heartbeat logging
- Integrate logging into FastAPI lifespan manager
- Add heartbeat loop (30s intervals) with current status
- Add /admin/system endpoint to read last 50 lines of logs/system.log
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- .openclaw/core/system_logger.py +53 -0
- app.py +42 -1
.openclaw/core/system_logger.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
System status logging module for HuggingClaw.
|
| 4 |
+
Logs FastAPI startup events and heartbeat signals to logs/system.log
|
| 5 |
+
"""
|
| 6 |
+
import os
|
| 7 |
+
import threading
|
| 8 |
+
from datetime import datetime
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
# Paths
|
| 12 |
+
OPENCLAW_DIR = "/app/.openclaw"
|
| 13 |
+
LOG_DIR = f"{OPENCLAW_DIR}/logs"
|
| 14 |
+
SYSTEM_LOG = f"{LOG_DIR}/system.log"
|
| 15 |
+
|
| 16 |
+
# Ensure log directory exists
|
| 17 |
+
Path(LOG_DIR).mkdir(parents=True, exist_ok=True)
|
| 18 |
+
|
| 19 |
+
# Thread lock for safe concurrent writes
|
| 20 |
+
_lock = threading.Lock()
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def _write_log(message: str):
|
| 24 |
+
"""Internal: write a log entry with timestamp."""
|
| 25 |
+
with _lock:
|
| 26 |
+
timestamp = datetime.utcnow().isoformat() + "+00:00"
|
| 27 |
+
with open(SYSTEM_LOG, "a") as f:
|
| 28 |
+
f.write(f"[{timestamp}] {message}\n")
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def log_startup(app_name: str = "Cain", version: str = "1.0.0"):
|
| 32 |
+
"""Log FastAPI application startup."""
|
| 33 |
+
_write_log(f"STARTUP {app_name} v{version} - FastAPI server starting")
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def log_heartbeat(status: str = "RUNNING"):
|
| 37 |
+
"""Log a heartbeat signal with current status."""
|
| 38 |
+
_write_log(f"HEARTBEAT status={status}")
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def log_event(event_type: str, message: str):
|
| 42 |
+
"""Log a custom event."""
|
| 43 |
+
_write_log(f"EVENT {event_type} - {message}")
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def get_last_lines(n: int = 50) -> list[str]:
|
| 47 |
+
"""Read the last n lines from the system log."""
|
| 48 |
+
try:
|
| 49 |
+
with open(SYSTEM_LOG, "r") as f:
|
| 50 |
+
lines = f.readlines()
|
| 51 |
+
return [line.strip() for line in lines[-n:]]
|
| 52 |
+
except FileNotFoundError:
|
| 53 |
+
return []
|
app.py
CHANGED
|
@@ -14,11 +14,40 @@ import os
|
|
| 14 |
import sys
|
| 15 |
from datetime import datetime
|
| 16 |
import asyncio
|
|
|
|
| 17 |
|
| 18 |
# Add /app to sys.path for proper package imports
|
| 19 |
sys.path.insert(0, "/app")
|
| 20 |
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
# Mount static files directory
|
| 24 |
static_dir = "/app/static"
|
|
@@ -161,6 +190,18 @@ async def websocket_endpoint(websocket: WebSocket):
|
|
| 161 |
await websocket.close()
|
| 162 |
|
| 163 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
if __name__ == "__main__":
|
| 165 |
import uvicorn
|
| 166 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
|
| 14 |
import sys
|
| 15 |
from datetime import datetime
|
| 16 |
import asyncio
|
| 17 |
+
from contextlib import asynccontextmanager
|
| 18 |
|
| 19 |
# Add /app to sys.path for proper package imports
|
| 20 |
sys.path.insert(0, "/app")
|
| 21 |
|
| 22 |
+
# Import system logger
|
| 23 |
+
from openclaw.core.system_logger import log_startup, log_heartbeat, get_last_lines
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@asynccontextmanager
|
| 27 |
+
async def lifespan(app: FastAPI):
|
| 28 |
+
"""Lifespan context manager for startup/shutdown events."""
|
| 29 |
+
# Startup: log system startup
|
| 30 |
+
log_startup("Cain", "1.0.0")
|
| 31 |
+
|
| 32 |
+
# Start heartbeat task
|
| 33 |
+
heartbeat_task = asyncio.create_task(heartbeat_loop())
|
| 34 |
+
|
| 35 |
+
yield
|
| 36 |
+
|
| 37 |
+
# Shutdown: cancel heartbeat
|
| 38 |
+
heartbeat_task.cancel()
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
# Background heartbeat loop
|
| 42 |
+
async def heartbeat_loop():
|
| 43 |
+
"""Write heartbeat signal every 30 seconds."""
|
| 44 |
+
while True:
|
| 45 |
+
await asyncio.sleep(30)
|
| 46 |
+
status = get_cain_status().get("current_state", "unknown")
|
| 47 |
+
log_heartbeat(status)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
app = FastAPI(title="HuggingClaw - Cain", version="1.0.0", lifespan=lifespan)
|
| 51 |
|
| 52 |
# Mount static files directory
|
| 53 |
static_dir = "/app/static"
|
|
|
|
| 190 |
await websocket.close()
|
| 191 |
|
| 192 |
|
| 193 |
+
@app.get("/admin/system")
|
| 194 |
+
async def admin_system():
|
| 195 |
+
"""Admin endpoint - read last 50 lines of system log."""
|
| 196 |
+
lines = get_last_lines(50)
|
| 197 |
+
return {
|
| 198 |
+
"log_file": "logs/system.log",
|
| 199 |
+
"line_count": len(lines),
|
| 200 |
+
"lines": lines,
|
| 201 |
+
"timestamp": datetime.utcnow().isoformat() + "+00:00"
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
|
| 205 |
if __name__ == "__main__":
|
| 206 |
import uvicorn
|
| 207 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|