"""Liveness, readiness and metrics. Mounted at the root, outside ``/api/v1``. ``/healthz`` must never touch a dependency: if it did, a slow database would make the orchestrator kill healthy processes. ``/readyz`` is the one that checks dependencies, because a process that cannot reach PostgreSQL should be taken out of the load-balancer rotation but not restarted. """ from __future__ import annotations from fastapi import APIRouter, Response from prometheus_client import CONTENT_TYPE_LATEST, generate_latest from sqlalchemy import text from packages.shared.config import get_settings from packages.shared.db import get_session_factory from packages.shared.errors import AppError, ErrorCode from packages.shared.logging import get_logger from packages.shared.observability import REGISTRY from packages.shared.redis_client import get_redis from packages.shared.response import ok router = APIRouter(tags=["ops"]) logger = get_logger(__name__) @router.get("/healthz", summary="Liveness probe") async def healthz() -> dict[str, object]: settings = get_settings() return ok({"status": "ok", "app": settings.app_name, "env": settings.app_env}) @router.get("/readyz", summary="Readiness probe (checks PostgreSQL and Redis)") async def readyz() -> dict[str, object]: checks: dict[str, str] = {} try: factory = get_session_factory() async with factory() as session: await session.execute(text("SELECT 1")) checks["postgres"] = "ok" except Exception as exc: checks["postgres"] = f"error: {type(exc).__name__}" try: await get_redis().ping() checks["redis"] = "ok" except Exception as exc: checks["redis"] = f"error: {type(exc).__name__}" if any(v != "ok" for v in checks.values()): logger.warning("readiness_failed", checks=checks) raise AppError(ErrorCode.UPSTREAM_ERROR, message="依赖未就绪", data={"checks": checks}) return ok({"status": "ready", "checks": checks}) @router.get("/metrics", summary="Prometheus scrape endpoint", include_in_schema=False) async def metrics() -> Response: return Response(content=generate_latest(REGISTRY), media_type=CONTENT_TYPE_LATEST)