87 lines
2.5 KiB
Python
87 lines
2.5 KiB
Python
"""监控中间件"""
|
||
import time
|
||
from typing import Callable
|
||
|
||
from fastapi import Request, Response
|
||
from starlette.middleware.base import BaseHTTPMiddleware
|
||
|
||
from app.monitoring.metrics import (
|
||
API_REQUESTS_TOTAL,
|
||
API_REQUEST_DURATION_SECONDS,
|
||
API_REQUESTS_IN_PROGRESS,
|
||
)
|
||
|
||
# 需要排除的路径(不记录指标)
|
||
EXCLUDED_PATHS = {"/health", "/ready", "/metrics", "/docs", "/openapi.json"}
|
||
|
||
|
||
class MonitoringMiddleware(BaseHTTPMiddleware):
|
||
"""API监控中间件"""
|
||
|
||
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
||
# 跳过排除路径
|
||
if request.url.path in EXCLUDED_PATHS:
|
||
return await call_next(request)
|
||
|
||
# 提取端点标识(用于指标标签)
|
||
endpoint = self._get_endpoint_label(request)
|
||
|
||
# 增加活跃请求计数
|
||
API_REQUESTS_IN_PROGRESS.labels(
|
||
method=request.method,
|
||
endpoint=endpoint
|
||
).inc()
|
||
|
||
# 记录开始时间
|
||
start_time = time.perf_counter()
|
||
|
||
try:
|
||
# 执行请求
|
||
response = await call_next(request)
|
||
status_code = response.status_code
|
||
except Exception as e:
|
||
status_code = 500
|
||
raise
|
||
finally:
|
||
# 计算耗时
|
||
duration = time.perf_counter() - start_time
|
||
|
||
# 记录指标
|
||
API_REQUESTS_TOTAL.labels(
|
||
method=request.method,
|
||
endpoint=endpoint,
|
||
status=str(status_code)
|
||
).inc()
|
||
|
||
API_REQUEST_DURATION_SECONDS.labels(
|
||
method=request.method,
|
||
endpoint=endpoint
|
||
).observe(duration)
|
||
|
||
# 减少活跃请求计数
|
||
API_REQUESTS_IN_PROGRESS.labels(
|
||
method=request.method,
|
||
endpoint=endpoint
|
||
).dec()
|
||
|
||
return response
|
||
|
||
def _get_endpoint_label(self, request: Request) -> str:
|
||
"""提取端点标签"""
|
||
path = request.url.path
|
||
|
||
# 规范化路径(替换ID等参数)
|
||
parts = path.strip("/").split("/")
|
||
|
||
# 处理常见模式:/api/v1/resources/{id}
|
||
if len(parts) >= 4 and parts[0] == "api":
|
||
resource = parts[2] if len(parts) > 2 else "unknown"
|
||
action = parts[3] if len(parts) > 3 else "list"
|
||
|
||
# 映射到规范标签
|
||
if action.isdigit():
|
||
return f"{resource}_detail"
|
||
return f"{resource}_{action}"
|
||
|
||
return "other"
|