87 lines
3.0 KiB
Python
87 lines
3.0 KiB
Python
"""Metrics registry and helpers for Prometheus and JSON fallbacks."""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import time
|
|
from typing import Any, Dict, Tuple
|
|
|
|
try:
|
|
from prometheus_client import CollectorRegistry, Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST
|
|
except Exception:
|
|
CollectorRegistry = None # type: ignore
|
|
Counter = None # type: ignore
|
|
Histogram = None # type: ignore
|
|
generate_latest = None # type: ignore
|
|
CONTENT_TYPE_LATEST = "text/plain; version=0.0.4; charset=utf-8"
|
|
|
|
_start_time = time.time()
|
|
_total_submissions = 0
|
|
|
|
_prom_registry = CollectorRegistry() if CollectorRegistry is not None else None
|
|
_prom_total_submissions = (
|
|
Counter("contact_total_submissions", "Total contact submissions", registry=_prom_registry)
|
|
if Counter is not None
|
|
else None
|
|
)
|
|
_prom_request_counter = None
|
|
_prom_request_latency = None
|
|
|
|
if Counter is not None and _prom_registry is not None:
|
|
try:
|
|
_prom_request_counter = Counter(
|
|
"http_requests_total",
|
|
"Total HTTP requests",
|
|
["method", "endpoint"],
|
|
registry=_prom_registry,
|
|
)
|
|
except Exception:
|
|
_prom_request_counter = None
|
|
|
|
if Histogram is not None and _prom_registry is not None:
|
|
try:
|
|
_prom_request_latency = Histogram(
|
|
"http_request_duration_seconds",
|
|
"Request duration",
|
|
["method", "endpoint"],
|
|
registry=_prom_registry,
|
|
)
|
|
except Exception:
|
|
_prom_request_latency = None
|
|
|
|
|
|
def record_submission() -> None:
|
|
"""Register a completed contact submission."""
|
|
global _total_submissions
|
|
_total_submissions += 1
|
|
if _prom_total_submissions is not None:
|
|
try:
|
|
_prom_total_submissions.inc()
|
|
except Exception:
|
|
logging.debug("Failed to increment Prometheus submission counter", exc_info=True)
|
|
|
|
|
|
def observe_request(method: str, endpoint: str, start_time: float | None, status: int | None = None) -> None:
|
|
"""Update request counters and latency histograms."""
|
|
if _prom_request_counter is not None:
|
|
try:
|
|
_prom_request_counter.labels(method=method, endpoint=endpoint).inc()
|
|
except Exception:
|
|
logging.debug("Failed to increment request counter", exc_info=True)
|
|
|
|
if _prom_request_latency is not None and start_time:
|
|
try:
|
|
_prom_request_latency.labels(method=method, endpoint=endpoint).observe(time.time() - start_time)
|
|
except Exception:
|
|
logging.debug("Failed to observe request latency", exc_info=True)
|
|
|
|
|
|
def export_metrics() -> Tuple[Any, int, Dict[str, str]]:
|
|
"""Return a Flask-style response tuple for the metrics endpoint."""
|
|
uptime = int(time.time() - _start_time)
|
|
if generate_latest is not None and _prom_registry is not None:
|
|
payload = generate_latest(_prom_registry)
|
|
headers = {"Content-Type": CONTENT_TYPE_LATEST}
|
|
return payload, 200, headers
|
|
body = {"uptime_seconds": uptime, "total_submissions": _total_submissions}
|
|
return body, 200, {"Content-Type": "application/json"}
|