geo/backend/app/middleware/metrics.py

61 lines
2.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""请求指标收集中间件:计时、慢请求告警、响应时间响应头。"""
import time
import logging
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response
logger = logging.getLogger("geo.metrics")
# 慢请求阈值(秒)
SLOW_REQUEST_THRESHOLD = 1.0
# 跳过指标收集的路径前缀(健康检查等高频低价值路径)
_SKIP_PATHS = {"/health", "/ready", "/docs", "/openapi.json", "/favicon.ico"}
class MetricsMiddleware(BaseHTTPMiddleware):
"""记录每个 HTTP 请求的耗时,并:
- 在响应头写入 X-Response-Time
- 对超过阈值的慢请求输出 WARNING 日志(携带结构化字段)
- 预留 Sentry / Prometheus 集成点TODO 注释标注)
"""
async def dispatch(self, request: Request, call_next) -> Response:
# 跳过健康检查等低价值路径,避免日志噪音
if request.url.path in _SKIP_PATHS:
return await call_next(request)
start_time = time.perf_counter()
response = await call_next(request)
duration = time.perf_counter() - start_time
duration_ms = round(duration * 1000, 2)
# 写回响应时间响应头
response.headers["X-Response-Time"] = f"{duration:.3f}s"
# 从 request.state 获取 request_id由 RequestIdMiddleware 注入)
request_id = getattr(request.state, "request_id", None)
log_extra: dict = {
"path": request.url.path,
"method": request.method,
"duration_ms": duration_ms,
"status_code": response.status_code,
}
if request_id:
log_extra["request_id"] = request_id
if duration >= SLOW_REQUEST_THRESHOLD:
logger.warning("Slow request detected", extra=log_extra)
else:
logger.debug("Request completed", extra=log_extra)
# TODO: 集成 Prometheus Counter/Histogram
# metrics_registry.http_request_duration.observe(duration, labels={...})
# TODO: 集成 Sentry 性能监控
# if sentry_sdk: sentry_sdk.set_measurement("response_time_ms", duration_ms)
return response