Files
config_manager/src/config_manager/v2/management/health_aggregator.py
T
2026-02-21 22:53:52 +03:00

54 lines
2.5 KiB
Python

"""Собирает состояние жизненного цикла и здоровья в один ответ для /health.
Здоровье = был успешный execute() за последние health_timeout секунд; иначе unhealthy с деталью (ошибка или таймаут)."""
from __future__ import annotations
import time
from collections.abc import Callable
from ..types import HealthPayload, LifecycleState
class HealthAggregator:
"""Формирует ответ здоровья по времени последнего успешного execute() и таймауту."""
def __init__(
self,
get_state: Callable[[], LifecycleState],
get_last_error: Callable[[], str | None],
get_last_success_timestamp: Callable[[], float | None],
health_timeout: int,
get_app_health: Callable[[], HealthPayload],
):
self._get_state = get_state
self._get_last_error = get_last_error
self._get_last_success_timestamp = get_last_success_timestamp
self._health_timeout = health_timeout
self._get_app_health = get_app_health
async def collect(self) -> HealthPayload:
"""Вернуть ok, если был успешный execute() за последние health_timeout сек; иначе unhealthy. Всегда добавляем state."""
state = self._get_state()
state_value = state.value
# Только при state=RUNNING возможен status=ok; при остановке (STOPPING/STOPPED) сразу unhealthy.
if state != LifecycleState.RUNNING:
return {"status": "unhealthy", "detail": f"state={state_value}", "state": state_value}
last_success = self._get_last_success_timestamp()
now = time.monotonic()
if last_success is None:
detail = self._get_last_error() or "no successful run yet"
return {"status": "unhealthy", "detail": detail, "state": state_value}
if (now - last_success) > self._health_timeout:
detail = self._get_last_error() or f"no successful run within {self._health_timeout}s"
return {"status": "unhealthy", "detail": detail, "state": state_value}
result = self._get_app_health()
status = result.get("status", "unhealthy")
if status != "ok":
return {"status": "unhealthy", "detail": result.get("detail", "app reported non-ok"), "state": state_value}
return {**result, "state": state_value}