from __future__ import annotations import hmac from fastapi import APIRouter, Header, HTTPException from fastapi.responses import Response from prometheus_client import CONTENT_TYPE_LATEST, generate_latest from sqlalchemy import func, select from app.core.config import get_settings from app.core.db import SessionLocal from app.core.observability import OUTBOX_EVENTS from app.models.outbox import DELIVERY_STATUSES, DEMO_SCENARIO_ERROR_CODE, OutboxEvent router = APIRouter(tags=["observability"]) settings = get_settings() def _refresh_database_metrics() -> None: with SessionLocal() as db: rows = db.execute( select( OutboxEvent.delivery_status, (OutboxEvent.last_error_code == DEMO_SCENARIO_ERROR_CODE).label("demo"), func.count(), ).group_by(OutboxEvent.delivery_status, "demo") ).all() OUTBOX_EVENTS.clear() # Keep every time series present even when a state currently contains no rows. # Stable zero-valued series make dashboards and alerts deterministic after resets, # restores and fresh installations instead of turning "zero" into "no data". for scenario in ("synthetic", "operational"): for status in DELIVERY_STATUSES: OUTBOX_EVENTS.labels(scenario, status).set(0) for status, is_demo, count in rows: OUTBOX_EVENTS.labels("synthetic" if is_demo else "operational", str(status)).set(count) @router.get("/metrics", include_in_schema=False) def metrics(authorization: str | None = Header(default=None)) -> Response: if settings.metrics_bearer_token: supplied = authorization.removeprefix("Bearer ") if authorization else "" if not hmac.compare_digest(supplied, settings.metrics_bearer_token): raise HTTPException(status_code=401, detail="Metrics token required") _refresh_database_metrics() return Response(content=generate_latest(), media_type=CONTENT_TYPE_LATEST)