feat: proactive host_silence scan for stale agent.heartbeat (0.8.3)

Background scan every 5 min opens rule:host_silence and notifies without
waiting for the next ingest event. CLI job + optional systemd timer.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-10 09:07:32 +10:00
parent 2a0eb6e90c
commit aafb80fa31
13 changed files with 411 additions and 10 deletions
@@ -0,0 +1,51 @@
"""Фоновый asyncio-цикл сканирования stale heartbeat (в процессе sac-api)."""
from __future__ import annotations
import asyncio
import logging
from app.config import get_settings
from app.database import SessionLocal
from app.services.host_silence_scan import dispatch_host_silence_notifications, run_host_silence_scan
logger = logging.getLogger("sac.host_silence_scan")
async def host_silence_scan_loop(stop_event: asyncio.Event) -> None:
settings = get_settings()
interval_sec = max(60, int(settings.sac_host_silence_scan_interval_minutes) * 60)
logger.info(
"host_silence background scan enabled (interval=%s min, stale=%s min)",
settings.sac_host_silence_scan_interval_minutes,
settings.sac_heartbeat_stale_minutes,
)
while not stop_event.is_set():
try:
await asyncio.to_thread(_run_scan_once)
except Exception:
logger.exception("host_silence background scan error")
try:
await asyncio.wait_for(stop_event.wait(), timeout=interval_sec)
except asyncio.TimeoutError:
continue
def _run_scan_once() -> None:
db = SessionLocal()
try:
results = run_host_silence_scan(db)
notified = dispatch_host_silence_notifications(db, results)
db.commit()
if results:
logger.info(
"host_silence background: stale=%s created=%s notified=%s",
len(results),
sum(1 for r in results if r.created),
notified,
)
except Exception:
db.rollback()
raise
finally:
db.close()
+38
View File
@@ -0,0 +1,38 @@
"""CLI: python -m app.jobs.host_silence_scan"""
from __future__ import annotations
import logging
import sys
from app.database import SessionLocal
from app.services.host_silence_scan import dispatch_host_silence_notifications, run_host_silence_scan
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger("sac.host_silence_scan")
def main() -> int:
db = SessionLocal()
try:
results = run_host_silence_scan(db)
notified = dispatch_host_silence_notifications(db, results)
db.commit()
created = sum(1 for r in results if r.created)
logger.info(
"host_silence scan done stale_hosts=%s created=%s notified=%s",
len(results),
created,
notified,
)
return 0
except Exception:
db.rollback()
logger.exception("host_silence scan failed")
return 1
finally:
db.close()
if __name__ == "__main__":
sys.exit(main())