63 lines
2.1 KiB
Python
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")
|