klg-asutk-app/backend/app/api/routes/metrics.py

63 lines
2.1 KiB
Python

"""
Prometheus-compatible metrics endpoint.
Exposes request counts, latencies, and business metrics.
"""
import time
from collections import defaultdict
from fastapi import APIRouter, Request, Response
from starlette.middleware.base import BaseHTTPMiddleware
router = APIRouter(tags=["monitoring"])
# In-memory counters (production: use prometheus_client library)
_request_count: dict[str, int] = defaultdict(int)
_request_latency_sum: dict[str, float] = defaultdict(float)
_error_count: dict[str, int] = defaultdict(int)
class MetricsMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
start = time.monotonic()
response = await call_next(request)
elapsed = time.monotonic() - start
path = request.url.path
method = request.method
key = f"{method} {path}"
_request_count[key] += 1
_request_latency_sum[key] += elapsed
if response.status_code >= 400:
_error_count[f"{response.status_code}"] += 1
return response
@router.get("/metrics")
def prometheus_metrics():
"""Prometheus-compatible text metrics."""
lines = [
"# HELP klg_http_requests_total Total HTTP requests",
"# TYPE klg_http_requests_total counter",
]
for key, count in sorted(_request_count.items()):
method, path = key.split(" ", 1)
lines.append(f'klg_http_requests_total{{method="{method}",path="{path}"}} {count}')
lines += [
"# HELP klg_http_request_duration_seconds Total request duration",
"# TYPE klg_http_request_duration_seconds counter",
]
for key, total in sorted(_request_latency_sum.items()):
method, path = key.split(" ", 1)
lines.append(f'klg_http_request_duration_seconds{{method="{method}",path="{path}"}} {total:.4f}')
lines += [
"# HELP klg_http_errors_total Total HTTP errors by status code",
"# TYPE klg_http_errors_total counter",
]
for code, count in sorted(_error_count.items()):
lines.append(f'klg_http_errors_total{{status="{code}"}} {count}')
return Response(content="\n".join(lines) + "\n", media_type="text/plain")