用 Django 中间件检测慢请求需在 process_request 存时间戳、process_response 计算耗时并记录,推荐 __call__ 新式写法;须设合理阈值、避免日志泄露、使用结构化日志。

如何用 Django 中间件检测并记录耗时超过阈值的请求
直接结论:用 process_request 和 process_response 配合 time.time() 记录耗时,再按需写入日志或数据库;Django 3.1+ 推荐用 __call__ 方式实现新式中间件,更清晰可控。
关键不是“能不能记录”,而是“在哪记、记什么、怎么避免干扰正常逻辑”。慢请求本身不报错,但容易掩盖性能瓶颈,比如某次数据库查询卡住 8 秒,却没留下任何痕迹。
-
process_request在视图执行前触发,适合打起点时间戳 -
process_response在响应生成后触发,适合算耗时、加日志、甚至动态修改响应头(如加X-Response-Time) - 别在
process_exception里记耗时——异常发生时请求可能根本没走到视图,时间不准 - 避免在中间件里做耗时操作(如同步写数据库),否则会把“慢请求”变成“更慢请求”
为什么不能只靠 logging.getLogger().info() 简单打点
单纯在开头结尾打日志,看起来能算时间差,但实际会漏掉三类关键场景:
- 视图抛出未捕获异常 →
process_response不执行,耗时丢失 - 流式响应(如
StreamingHttpResponse)→ 响应体可能在返回后才真正生成,process_response时还没结束 - 中间件顺序错乱 → 如果你的日志中间件排在认证中间件之后,而用户因 token 过期被
401拦截,那它根本不会运行
正确做法是:在 process_request 存时间戳到 request 对象上(如 request._start_time = time.time()),再统一在 process_response 或 process_exception 中读取并记录。这样即使出错也能留痕。
如何设置合理阈值并区分真实慢请求和网络延迟
阈值设成 500ms?1s?要看业务场景。API 接口通常 200ms 内应返回,后台管理页可放宽到 2s。更重要的是排除客户端网络抖动影响:
- 不要依赖
request.META.get('HTTP_X_FORWARDED_FOR')或类似头判断来源——这些可伪造,且 CDN 可能清空 - 优先看
request.path+request.method组合,比如/api/v1/orders/的POST请求连续超时,比首页GET超时更值得查 - 记录
request.GET和request.POST的键名(非值!),避免日志泄露敏感数据,也方便后续聚合分析(例如发现所有慢请求都带export=true参数) - 用
settings.SLOW_REQUEST_THRESHOLD_MS = 1000统一配置,别硬编码在中间件里
一个轻量但可用的中间件示例
import time
import logging
from django.conf import settings
<p>logger = logging.getLogger(<strong>name</strong>)</p><p>class SlowRequestMiddleware:
def <strong>init</strong>(self, get_response):
self.get_response = get_response</p><pre class="brush:php;toolbar:false;">def __call__(self, request):
start_time = time.time()
response = self.get_response(request)
duration = time.time() - start_time
if duration * 1000 > getattr(settings, 'SLOW_REQUEST_THRESHOLD_MS', 1000):
logger.warning(
'Slow request: %s %s %.0fms',
request.method,
request.path,
duration * 1000,
extra={
'status_code': response.status_code,
'path': request.path,
'method': request.method,
'duration_ms': round(duration * 1000, 0),
'params_keys': list(request.GET.keys()) + list(request.POST.keys()),
}
)
return response
把它加进 MIDDLEWARE 设置靠前位置(比如在 SecurityMiddleware 之后、SessionMiddleware 之前),确保覆盖所有请求。注意:如果用了异步视图(async def),这个中间件要改用 AsyncMiddleware 形式,否则会阻塞事件循环。
最常被忽略的一点:日志 handler 必须支持结构化输出(如用 JsonFormatter),否则后续用 ELK 或 Grafana 查慢请求时,得靠正则硬扒文本,效率极低。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











