diff --git a/main.py b/main.py index 716384f9..6088b1f7 100644 --- a/main.py +++ b/main.py @@ -15,7 +15,9 @@ import logging import os import signal import subprocess +import time from contextlib import asynccontextmanager +from datetime import datetime from pathlib import Path from typing import Any @@ -271,6 +273,17 @@ async def lifespan(app: FastAPI): # 시작 logger.info(f"[{ENVIRONMENT}] 앱 시작 중...") + # 지금 도는 코드가 언제 것인지 못박아 둔다 — `/api/health` 가 `stale` 로 알린다(7-2). + global _STARTED_AT, _CODE_MTIME_AT_START + _STARTED_AT = time.time() + if DEBUG: + _CODE_MTIME_AT_START = _newest_source_mtime() + logger.info( + "[reload] 감시 폴더 %d개, 소스 최신 수정 %s", + len(_reload_dirs()), + datetime.fromtimestamp(_CODE_MTIME_AT_START).isoformat(timespec="seconds"), + ) + # 프론트엔드 빌드 (필수) build_frontend() @@ -440,10 +453,44 @@ async def root(): } +#: 이 프로세스가 뜬 시각과, 뜰 때 본 **가장 최근 소스 수정 시각**. 기동 때 채운다 +#: (`_reload_dirs` 가 아래에 정의돼 있어 모듈 로드 시점에는 못 부른다). +#: 자동 리로드가 조용히 죽는 일이 잦아(오늘만 세 번, 매번 틀린 결론을 냄) 「지금 도는 서버가 +#: 옛 코드인가」를 한 번에 가릴 수 있게 남긴다(2026-09-07, PLAN 7-2). +_STARTED_AT = 0.0 +_CODE_MTIME_AT_START = 0.0 + + +def _newest_source_mtime() -> float: + """감시 대상 폴더의 `.py` 중 가장 최근 수정 시각. 못 읽으면 0.""" + newest = 0.0 + for folder in _reload_dirs(): + for path in Path(folder).rglob("*.py"): + try: + newest = max(newest, path.stat().st_mtime) + except OSError: + continue + return newest + + @app.get("/api/health") async def health(): - """헬스 체크""" - return {"status": "ok", "environment": ENVIRONMENT} + """헬스 체크. + + 개발 모드에서는 **코드가 최신인지**도 함께 알린다 — `stale: true` 면 소스가 더 새로우니 + (자동 리로드가 안 붙은 것) 재시작 전에는 어떤 측정도 믿으면 안 된다. + """ + body: dict[str, Any] = {"status": "ok", "environment": ENVIRONMENT} + if DEBUG: + newest = _newest_source_mtime() + body["started_at"] = datetime.fromtimestamp(_STARTED_AT).isoformat(timespec="seconds") + body["code_mtime"] = datetime.fromtimestamp(_CODE_MTIME_AT_START).isoformat( + timespec="seconds" + ) + body["source_mtime"] = datetime.fromtimestamp(newest).isoformat(timespec="seconds") + # 1초 여유 — 저장 직후의 미세한 시각차로 헛경고가 뜨지 않게. + body["stale"] = newest > _CODE_MTIME_AT_START + 1.0 + return body # ─────────────────────────────────────────────────────────────────────────