"""Prometheus /metrics endpoint. Mounted at ``GET /metrics`` (NOT under ``/api/v1``) because that's the convention Prometheus scrapers expect by default. Auth model: * ``APP_ENV='development'`` + empty token → endpoint open (local dev, Docker-bridge). * Non-development APP_ENV + empty token → endpoint REFUSES requests with 503 Service Unavailable. Helix prod serves user APIs and ``/metrics`` on the same ALB-fronted FastAPI port; leaving the token empty would leak cron topology / SLA buckets / compliance incident counts to the internet. The 503 path keeps the security contract while letting the app boot when the operator forgets to pre-stage the secret — they see startup CRITICAL logs AND a failing /metrics scrape, both of which fix-forward via setting PLATFORM_METRICS_TOKEN in .env. * Any APP_ENV + non-empty token → requests must carry ``Authorization: Bearer ``; anything else → 401. """ from __future__ import annotations import secrets from fastapi import APIRouter, Depends, Header, Request from fastapi.responses import PlainTextResponse from sqlalchemy.ext.asyncio import AsyncSession from app.core.config import settings from app.core.database import get_db from app.core.exceptions import AppException, UnauthorizedError from app.observability.metrics import render_metrics router = APIRouter() class MetricsNotConfiguredError(AppException): """503 — /metrics requested but token not configured in non-dev env.""" def __init__(self) -> None: super().__init__( code="metrics.not_configured", message=( "Metrics endpoint is not configured " "(set PLATFORM_METRICS_TOKEN)." ), status_code=503, ) def _require_metrics_token(authorization: str | None = Header(None)) -> None: token = settings.PLATFORM_METRICS_TOKEN if not token: # Refuse in non-development environments — see module docstring. if settings.APP_ENV != "development": raise MetricsNotConfiguredError() # Endpoint is open in development. return expected = f"Bearer {token}" if not authorization or not secrets.compare_digest(authorization, expected): raise UnauthorizedError("Invalid or missing metrics token") @router.get( "/metrics", summary="Prometheus exposition (text/plain)", response_class=PlainTextResponse, include_in_schema=False, ) async def metrics( request: Request, db: AsyncSession = Depends(get_db), _: None = Depends(_require_metrics_token), ): body = await render_metrics(db) return PlainTextResponse( content=body, media_type="text/plain; version=0.0.4; charset=utf-8", )