feat: страница логов /ui/logs с SSE real-time потоком
Some checks failed
CI / test (push) Successful in 39s
Deploy / deploy (push) Failing after 15s

- log_buffer: RingBufferHandler, кольцевой буфер 600 записей, fan-out SSE
- ui_logs: GET /ui/logs (HTML), GET /ui/logs/stream (EventSource)
- main: install_log_handler при старте, подключён router логов
- nav_rail: ссылка Логи, root_html: кнопка-ссылка Логи
- Исправлено: NaN/Inf/NUL в теле вебхука → 500 от PostgreSQL jsonb
- Тесты: test_log_buffer, test_json_sanitize; 51 passed

Made-with: Cursor
This commit is contained in:
Alexandr
2026-04-03 15:56:58 +03:00
parent 18ba48e8d0
commit 80645713a0
12 changed files with 465 additions and 12 deletions

View File

@ -1,3 +1,4 @@
import asyncio
import logging
from contextlib import asynccontextmanager
@ -5,14 +6,16 @@ from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from starlette.responses import HTMLResponse, Response
from onguard24 import __version__ as app_version
from onguard24.config import get_settings
from onguard24.db import create_pool
from onguard24.domain.events import InMemoryEventBus
from onguard24.ingress import grafana as grafana_ingress
from onguard24.log_buffer import install_log_handler
from onguard24.modules.registry import MODULE_MOUNTS, register_module_events
from onguard24.root_html import render_root_page
from onguard24.status_snapshot import build as build_status
from onguard24 import __version__ as app_version
from onguard24.ui_logs import router as logs_router
logging.basicConfig(level=logging.INFO)
logging.getLogger("httpx").setLevel(logging.WARNING)
@ -31,6 +34,7 @@ def parse_addr(http_addr: str) -> tuple[str, int]:
@asynccontextmanager
async def lifespan(app: FastAPI):
install_log_handler(asyncio.get_event_loop())
settings = get_settings()
pool = await create_pool(settings)
bus = InMemoryEventBus()
@ -38,7 +42,7 @@ async def lifespan(app: FastAPI):
app.state.pool = pool
app.state.settings = settings
app.state.event_bus = bus
log.info("onGuard24 started, db=%s", "ok" if pool else "disabled")
log.info("onGuard24 started v%s, db=%s", app_version, "ok" if pool else "disabled")
yield
if pool:
await pool.close()
@ -82,6 +86,7 @@ def create_app() -> FastAPI:
return await build_status(request)
app.include_router(grafana_ingress.router, prefix="/api/v1")
app.include_router(logs_router)
for mount in MODULE_MOUNTS:
app.include_router(mount.router, prefix=mount.url_prefix)
if mount.ui_router is not None: