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

@ -10,6 +10,7 @@ from starlette.responses import Response
from onguard24.domain.entities import Alert, Severity
from onguard24.grafana_sources import sources_by_slug, webhook_authorized
from onguard24.ingress.grafana_payload import extract_alert_row_from_grafana_body
from onguard24.ingress.json_sanitize import sanitize_for_jsonb
from onguard24.ingress.team_match import resolve_team_id_for_labels
logger = logging.getLogger(__name__)
@ -99,6 +100,8 @@ async def _grafana_webhook_impl(
body = {}
if not isinstance(body, dict):
body = {}
else:
body = sanitize_for_jsonb(body)
derived = extract_grafana_source_key(body)
path_key: str | None = None
@ -131,7 +134,7 @@ async def _grafana_webhook_impl(
RETURNING id
""",
"grafana",
json.dumps(body),
json.dumps(body, ensure_ascii=False, allow_nan=False),
stored_org_slug,
service_name,
)
@ -151,7 +154,7 @@ async def _grafana_webhook_impl(
sev_row,
stored_org_slug,
service_name,
json.dumps(labels_row),
json.dumps(labels_row, ensure_ascii=False, allow_nan=False),
fp_row,
team_id,
)
@ -165,12 +168,17 @@ async def _grafana_webhook_impl(
payload=body,
received_at=datetime.now(timezone.utc),
)
await bus.publish_alert_received(
alert,
raw_payload_ref=raw_id,
grafana_org_slug=stored_org_slug,
service_name=service_name,
)
try:
await bus.publish_alert_received(
alert,
raw_payload_ref=raw_id,
grafana_org_slug=stored_org_slug,
service_name=service_name,
)
except Exception:
logger.exception(
"ingress: событие alert.received не доставлено подписчикам (БД уже сохранена)"
)
return Response(status_code=202)