第一步
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
"""健康检查。
|
||||
|
||||
/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]
|
||||
Reference in New Issue
Block a user