Observability: Prometheus metrics, log panel, runbook

- GET /api/metrics renders Prometheus text (equity, P&L, per-strategy
  equity/pnl/allocation/halted, info) with no client library.
- In-memory ring log handler (app/logs.py) exposed at GET /api/logs and a
  dashboard "Server logs" panel; Runtime mirrors bot events into the logger.
- docs/runbook.md covers deploys, the kill switch, resetting paper state and
  going live. Adds tests for metrics rendering and the log ring.
This commit is contained in:
matitos committed 2026-09-22 17:10:38 +00:00
1 parent c605a0f316
commit ac92dd5ac4
12 files changed
+462 -1

No files matched your search

+16
View File
@@ -32,6 +32,15 @@ from app.store import Store
logger = logging.getLogger(__name__)
_LOG_LEVELS = {
"debug": logging.DEBUG,
"info": logging.INFO,
"trade": logging.INFO,
"warning": logging.WARNING,
"error": logging.ERROR,
"critical": logging.CRITICAL,
}
FeedFactory = Callable[[Settings], MarketData]
BrokerFactory = Callable[[Settings], Broker]
NotifierFactory = Callable[[Settings], Notifier | None]
@@ -91,8 +100,15 @@ class Runtime:
with suppress(asyncio.QueueFull):
queue.put_nowait(payload)
if payload.get("type") == "event":
self._log_event(payload["data"])
self._maybe_notify(payload["data"])
def _log_event(self, event: dict[str, Any]) -> None:
"""Mirror bot events into the logger so the dashboard log view is useful."""
level = str(event.get("level", "info")).lower()
message = event.get("message", "")
logger.log(_LOG_LEVELS.get(level, logging.INFO), "%s", message)
def _maybe_notify(self, event: dict[str, Any]) -> None:
if event.get("level") not in self.notify_levels:
return