"""监控中间件""" 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"