Files
onGuard24/onguard24/main.py
Alexandr c9b97814a5
All checks were successful
CI / test (push) Successful in 47s
feat: логирование вебхука до БД + файловый лог с ротацией
- Каждый входящий POST /ingress/grafana: INFO-строка (status, кол-во алертов,
  первые лейблы) и DEBUG-блок с полным JSON телом (до 8КБ)
  — видно даже если БД упала с 500
- LOG_FILE в .env / env: RotatingFileHandler 10MB×5 файлов
- LOG_LEVEL=debug теперь показывает полные тела вебхуков
- basicConfig уровень DEBUG (uvicorn.access / asyncio приглушены)

Made-with: Cursor
2026-04-03 15:59:17 +03:00

119 lines
3.6 KiB
Python

import asyncio
import logging
from contextlib import asynccontextmanager
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.ui_logs import router as logs_router
logging.basicConfig(level=logging.DEBUG)
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("asyncio").setLevel(logging.WARNING)
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
log = logging.getLogger("onguard24")
def parse_addr(http_addr: str) -> tuple[str, int]:
s = http_addr.strip()
if s.startswith(":"):
return "0.0.0.0", int(s[1:])
if ":" in s:
h, p = s.rsplit(":", 1)
return (h or "0.0.0.0"), int(p)
return "0.0.0.0", int(s)
@asynccontextmanager
async def lifespan(app: FastAPI):
settings = get_settings()
install_log_handler(asyncio.get_event_loop(), log_file=settings.log_file)
pool = await create_pool(settings)
bus = InMemoryEventBus()
register_module_events(bus, pool)
app.state.pool = pool
app.state.settings = settings
app.state.event_bus = bus
log.info("onGuard24 started v%s, db=%s", app_version, "ok" if pool else "disabled")
yield
if pool:
await pool.close()
log.info("database pool closed")
def create_app() -> FastAPI:
app = FastAPI(title="onGuard24", version=app_version, lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173", "http://127.0.0.1:5173"],
allow_credentials=True,
allow_methods=["GET", "POST", "OPTIONS"],
allow_headers=["*"],
)
@app.get("/")
async def root(request: Request):
if request.query_params.get("format") == "json":
data = await build_status(request)
data["links"] = {
"docs": "/docs",
"openapi": "/openapi.json",
"health": "/health",
"status_api": "/api/v1/status",
}
return data
return HTMLResponse(await render_root_page(request))
@app.get("/favicon.ico", include_in_schema=False)
async def favicon():
return Response(status_code=204)
@app.get("/health")
@app.get("/api/v1/health")
async def health():
return {"status": "ok", "service": "onGuard24"}
@app.get("/api/v1/status")
async def status(request: Request):
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:
app.include_router(mount.ui_router, prefix=f"/ui/modules/{mount.slug}")
return app
app = create_app()
def run() -> None:
import uvicorn
settings = get_settings()
host, port = parse_addr(settings.http_addr)
lvl = settings.log_level.upper()
uvicorn.run(
"onguard24.main:app",
host=host,
port=port,
log_level=lvl.lower() if lvl in {"DEBUG", "INFO", "WARNING", "ERROR"} else "info",
)
if __name__ == "__main__":
run()