fastapi: 中间件记录请求参数和响应内容,并接受参数
一,相关的说明:
1,当使用 app.add_middleware() 注册中间件时,
如果你想向中间件传递自定义参数,直接将参数作为关键字参数(Keyword Arguments)传给 add_middleware 即可。
FastAPI 底层基于 Starlette,add_middleware 会自动把这些额外的参数传递给中间件类的 __init__ 构造函数。
2, 要注意的是:中间件参数是在“启动时”确定,而不是“请求时”
传给 add_middleware 的参数是单例配置。也就是说,这些参数只会在 FastAPI 项目启动(初始化中间件)时被赋值一次。
正确用法:传递数据库连接池、环境配置(如 debug=True)、超时时间、密钥等固定配置。
错误用法:试图传递当前请求的用户 ID、当前请求的 Token 等动态数据。如果需要处理请求级别的动态数据,应该在 dispatch 方法内部通过 request 对象去获取。
3,请求体(Request Body)和响应体(Response Body)在数据流中只能被读取一次。
如果不做特殊处理,中间件读取完数据后,后续的路由或前端就会拿不到数据(报错或悬挂)。
二,代码:
middleware:
# app/middleware/log_time.py
import json
from fastapi import FastAPI, Request
import time
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import StreamingResponse
# from app.core import logger
from app.core.logger import logger
# 1. 定义你的自定义中间件
# x_header_name: str,
class LogRequestResponseMiddleware(BaseHTTPMiddleware):
def __init__(self, app, enable_timer: bool = True):
# 必须先调用父类的 __init__,把 app 传进去
super().__init__(app)
# 接收并保存你传递的自定义参数
# self.x_header_name = x_header_name
self.enable_timer = enable_timer
async def dispatch(self, request: Request, call_next):
process_time = 0
if self.enable_timer:
start_time = time.time()
# ==================== 1. 捕获请求信息 ====================
path = request.url.path
method = request.method
# 获取 GET 参数 (Query Params)
query_params = dict(request.query_params)
# 安全地获取 POST/PUT 等请求体 (Body)
body_json = None
if method in ["POST", "PUT", "PATCH"]:
# receive() 会消耗数据流,所以必须通过缓存重新构建 request 保证后续路由可读
request_body = await request.body()
if request_body:
try:
body_json = json.loads(request_body.decode("utf-8"))
except Exception:
# 如果不是 JSON 格式(比如文件或表单),转为字符串记录
body_json = request_body.decode("utf-8", errors="ignore")
# 关键步骤:重写请求体数据流,防止后续路由拿不到 Body
async def receive():
return {"type": "http.request", "body": request_body, "more_body": False}
request._receive = receive
# 正常处理请求
response = await call_next(request)
response_body = b""
# 优先判断是否存在 body_iterator(这是流式响应的核心特征)
if hasattr(response, "body_iterator"):
# 逐块读取响应体内容
res_body_chunks = [chunk async for chunk in response.body_iterator]
response_body = b"".join(res_body_chunks)
# 关键步骤:重新填充响应迭代器,否则前端收到的会是空数据
async def stream_iterator():
for chunk in res_body_chunks:
yield chunk
response.body_iterator = stream_iterator()
# 如果没有 body_iterator,但有 body 属性(普通响应)
elif hasattr(response, "body"):
response_body = response.body
# 极少数特殊响应(例如某些自定义的 Response 类),如果两边都拿不到,尝试通过异步迭代器兜底
else:
try:
response_body = getattr(response, "content", b"")
except Exception:
response_body = b"[Unreadable Response Body]"
# 尝试解析响应体为 JSON 格式以方便阅读日志
try:
res_json = json.loads(response_body.decode("utf-8"))
except Exception:
res_json = response_body.decode("utf-8", errors="ignore")
# 使用传递进来的参数逻辑
if self.enable_timer:
process_time = time.time() - start_time
# 记录请求和响应
# ==================== 4. 统一写入日志 ====================
log_data = {
"method": method,
"path": path,
"query_params": query_params,
"request_body": body_json,
"status_code": response.status_code,
"response_body": res_json
}
if process_time>0:
log_data["process_time"] = process_time
logger.bind(log_type="alipay").info(f"API Log: {json.dumps(log_data, ensure_ascii=False)}")
return response
注册中间件:
# 创建FastAPI应用,并传入 lifespan
api_app = FastAPI(title="我的API项目",
# docs_url=None,
docs_url=None,
redoc_url=None,
openapi_url=None)
# 注册全局异常捕获
register_exception_handlers(api_app)
# 注册中间件
api_app.add_middleware(LogRequestResponseMiddleware,enable_timer=True)
三,测试效果:
查看日志:
2026-07-25 23:10:18 | API Log: {"method": "GET", "path": "/api/users/all2", "query_params": {"ctype": "0"}, "request_body": null,
"status_code": 200,
"response_body": {"code": 200, "message": "success",
"data": {"total": 6, "items": [
{"id": 1, "username": "13866669999", "nickname": "cccbbb老刘他人不错", "avatar_id": 12, "head_url": "http://127.0.0.1:8008/20260721/1540/7da829d0-a760-4696-968a-f8d506ee65b7.png", "avatar_url": "https://cdn.example.com/avatars/1.png"},
{"id": 2, "username": "123", "nickname": "测试1", "avatar_id": 0, "head_url": "http://127.0.0.1:8008/logo_300.png", "avatar_url": "https://cdn.example.com/avatars/2.png"},
{"id": 3, "username": "321", "nickname": "测试2", "avatar_id": 0, "head_url": "http://127.0.0.1:8008/logo_300.png", "avatar_url": "https://cdn.example.com/avatars/3.png"}]}},
"process_time": 0.022850751876831055}
浙公网安备 33010602011771号