Browse Source

feat(middleware): 添加慢请求监控中间件

添加 SlowRequestMiddleware 用于监控并记录超过阈值的慢请求
pull/15387/head
Lgf 3 months ago
parent
commit
bab4c6d5e8
  1. 4
      fastapi/applications.py
  2. 2
      fastapi/middleware/__init__.py
  3. 51
      fastapi/middleware/slow_request.py
  4. 53
      tests/test_slow_request_middleware.py

4
fastapi/applications.py

@ -13,6 +13,7 @@ from fastapi.exception_handlers import (
from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError
from fastapi.logger import logger
from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware
from fastapi.middleware.slow_request import SlowRequestMiddleware
from fastapi.openapi.docs import (
get_redoc_html,
get_swagger_ui_html,
@ -1029,7 +1030,8 @@ class FastAPI(Starlette):
exception_handlers[key] = value
middleware = (
[Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)] # ty: ignore[invalid-argument-type]
[Middleware(SlowRequestMiddleware)]
+ [Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)] # ty: ignore[invalid-argument-type]
+ self.user_middleware
+ [
Middleware(

2
fastapi/middleware/__init__.py

@ -1 +1,3 @@
from starlette.middleware import Middleware as Middleware
from fastapi.middleware.slow_request import SlowRequestMiddleware

51
fastapi/middleware/slow_request.py

@ -0,0 +1,51 @@
import logging
import time
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response
from starlette.types import ASGIApp
logger = logging.getLogger("fastapi")
class SlowRequestMiddleware(BaseHTTPMiddleware):
"""
Middleware to monitor slow requests and log warnings when they exceed the threshold.
"""
def __init__(self, app: ASGIApp, threshold: float = 0.5) -> None:
"""
Initialize the SlowRequestMiddleware.
Args:
app: The ASGI application.
threshold: The threshold in seconds for considering a request as slow.
Defaults to 0.5 seconds (500ms).
"""
super().__init__(app)
self.threshold = threshold
async def dispatch(self, request: Request, call_next) -> Response:
"""
Dispatch the request, measuring the time taken.
Args:
request: The incoming request.
call_next: The next middleware or endpoint handler.
Returns:
The response from the next middleware or endpoint handler.
"""
start_time = time.perf_counter()
try:
response = await call_next(request)
return response
finally:
duration = time.perf_counter() - start_time
if duration > self.threshold:
method = request.method
url = str(request.url)
logger.warning(
f"Slow request: {method} {url} took {duration:.2f}s (threshold: {self.threshold:.2f}s)"
)

53
tests/test_slow_request_middleware.py

@ -0,0 +1,53 @@
import asyncio
import logging
import time
from fastapi import FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
@app.get("/fast")
async def fast_endpoint():
return {"message": "Fast response"}
@app.get("/slow")
async def slow_endpoint():
await asyncio.sleep(0.6)
return {"message": "Slow response"}
def test_slow_request_logging(caplog):
caplog.set_level(logging.WARNING)
client = TestClient(app)
# 测试快速请求,不应记录警告
response = client.get("/fast")
assert response.status_code == 200
assert "Slow request" not in caplog.text
# 测试慢速请求,应记录警告
response = client.get("/slow")
assert response.status_code == 200
# 检查日志是否包含预期的警告信息
slow_request_logs = [record for record in caplog.records if "Slow request" in record.message]
assert len(slow_request_logs) == 1
log_message = slow_request_logs[0].message
assert "GET" in log_message
assert "/slow" in log_message
# 检查是否包含两位小数的时间
import re
match = re.search(r"took (\d+\.\d{2})s", log_message)
assert match is not None, f"Log message should contain time with two decimal places: {log_message}"
print("✅ 慢请求监控中间件测试通过!")
print(f" 日志消息: {log_message}")
if __name__ == "__main__":
import pytest
pytest.main([__file__, "-v", "-s"])
Loading…
Cancel
Save