54 lines
2.5 KiB
Python
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}
|