Files
amb_rag/backend/app/api/health.py
T
2026-09-01 11:53:59 +08:00

83 lines
2.2 KiB
Python

"""健康检查。
/healthz → 存活探针(liveness):不触碰依赖,快速返回。
/api/health → 就绪探针(readiness):探测 SQLite + 文件存储。
"""
import time
from fastapi import APIRouter, Response, status
from pydantic import BaseModel
from sqlalchemy import text
from app.core.config import get_settings
from app.core.db import get_session_factory
router = APIRouter()
class HealthComponent(BaseModel):
status: str
latency_ms: int | None = None
error: str | None = None
class HealthReport(BaseModel):
status: str
environment: str
components: dict[str, HealthComponent]
@router.get("/healthz")
async def liveness() -> dict[str, str]:
return {"status": "ok"}
@router.get("/health", response_model=HealthReport)
async def readiness(response: Response) -> HealthReport:
report = HealthReport(
status="ok",
environment=get_settings().environment,
components={
"database": _check_db(),
"storage": _check_storage(),
},
)
if any(c.status != "ok" for c in report.components.values()):
report.status = "degraded"
response.status_code = status.HTTP_503_SERVICE_UNAVAILABLE
return report
def _check_db() -> HealthComponent:
start = time.perf_counter()
try:
factory = get_session_factory()
with factory() as session:
session.execute(text("SELECT 1"))
except Exception as exc: # noqa: BLE001
return HealthComponent(status="down", error=_brief(exc))
return HealthComponent(status="ok", latency_ms=_ms(start))
def _check_storage() -> HealthComponent:
start = time.perf_counter()
try:
from app.storage.local_storage import get_storage
storage = get_storage()
root = storage._root
if not root.exists():
root.mkdir(parents=True, exist_ok=True)
except Exception as exc: # noqa: BLE001
return HealthComponent(status="down", error=_brief(exc))
return HealthComponent(status="ok", latency_ms=_ms(start))
def _ms(start: float) -> int:
return int((time.perf_counter() - start) * 1000)
def _brief(exc: Exception) -> str:
text = f"{type(exc).__name__}: {exc}"
return text.split("\n")[0][:200]