47 lines
1.9 KiB
Python
47 lines
1.9 KiB
Python
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)
|