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>

Files changed (2) hide show
  1. .openclaw/core/system_logger.py +53 -0
  2. 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
- app = FastAPI(title="HuggingClaw - Cain", version="1.0.0")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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)