Django中间件须继承BaseMiddleware或实现__call__方法,推荐函数式或__call__方式以兼容同步/异步;注意执行时机、用户获取安全、日志异步写入及敏感信息过滤。

中间件类必须继承 BaseMiddleware 或实现 __call__ 方法
Django 4.1+ 推荐用函数式中间件或继承 BaseMiddleware,旧版的 process_request 等方法在 ASGI 模式下可能不触发。直接定义类并实现 __call__ 最可靠,且兼容同步/异步请求。
常见错误是照搬老教程写 process_view,结果在 DEBUG=False 或部署到 Gunicorn 后日志完全不记录——因为该方法只在 Django 内置开发服务器的特定路径中调用,且不覆盖所有请求生命周期。
- 必须确保中间件类在
MIDDLEWARE配置中靠前(比如放在SessionMiddleware之后、CommonMiddleware之前),否则可能拿不到request.user - 不要在
__init__中做耗时操作(如连接数据库),它在服务启动时执行一次,不是每次请求都调用 - 若需记录 POST 数据,注意
request.body只能读取一次,后续视图再读会为空;应先request.body.decode()并缓存到request._logged_body这类自定义属性
如何安全获取用户身份和请求元数据
request.user 在未登录时是 AnonymousUser 实例,直接取 request.user.id 会报 AttributeError;而 request.META.get('HTTP_X_FORWARDED_FOR') 可能为空或被伪造,不能直接当真实 IP 用。
推荐组合判断:
立即学习“Python免费学习笔记(深入)”;
- 用
getattr(request.user, 'pk', None)安全取用户 ID,避免 AttributeError - IP 地址优先取
request.META.get('REMOTE_ADDR'),仅当部署了可信反向代理(如 Nginx)时才 fallback 到HTTP_X_FORWARDED_FOR的第一个非私有地址 -
request.path和request.method是稳定可用的,但request.get_full_path()包含查询参数,敏感操作(如密码重置链接)要过滤掉token、code等字段
日志写入必须异步或使用线程安全方式
在中间件里直接调用 logging.info() 看似简单,但高并发下可能阻塞主线程,尤其当日志后端是文件或网络服务(如 Logstash)时。Django 默认日志配置是同步的,RotatingFileHandler 在写入大日志时会锁文件。
仅使用 Python 将 Claw Code 虚拟化框架与 OpenClaw 集成,提供 184 种工具和 200 多条命令,用于安全分析和代码质量...
实操建议:
- 用 Python 标准库的
concurrent.futures.ThreadPoolExecutor提交日志任务,避免阻塞响应 - 更轻量的做法:把日志结构化为字典,用
queue.Queue缓存,另起一个后台线程消费(适合中小流量) - 绝对不要在中间件里做数据库
.save()—— 事务未提交时写 DB 日志,可能因视图异常回滚导致日志丢失
示例关键片段:
import logging
from concurrent.futures import ThreadPoolExecutor
<p>logger = logging.getLogger('user_action')</p><p>class LoggingMiddleware:
def <strong>init</strong>(self, get_response):
self.get_response = get_response
self.executor = ThreadPoolExecutor(max_workers=2)</p><pre class='brush:python;toolbar:false;'>def __call__(self, request):
# 记录前准备数据
user_id = getattr(request.user, 'pk', None)
ip = self._get_client_ip(request)
# 异步写日志,不阻塞
self.executor.submit(
logger.info,
{'user_id': user_id, 'ip': ip, 'path': request.path, 'method': request.method}
)
return self.get_response(request)避免循环记录和敏感信息泄露
如果日志文件本身被 Django 静态服务或媒体路由暴露(比如放在 STATIC_ROOT 下),或日志内容包含 password、api_key 字段,就构成严重风险。另一个隐蔽问题是中间件自身触发日志记录——例如日志写入失败抛出异常,又触发错误中间件,形成递归。
必须加防护:
- 在中间件开头检查
request.path.startswith('/admin/log/')或匹配日志相关路径,直接跳过记录 - 对
request.POST做关键词过滤:{k: '***' if 'pass' in k.lower() else v for k, v in request.POST.items()} - 用
try/except包裹整个日志提交逻辑,捕获Exception而非仅IOError,防止BrokenPipeError等底层异常中断请求流 - 日志文件权限设为
640,属组仅限运行 Django 的用户和日志轮转进程
最常被忽略的一点:Django 的 LOGGING 配置中若启用了 django.security 或 django.db.backends 日志器,它们默认输出 SQL 和认证失败详情——这些和你的行为日志混在一起,排查时反而难定位。建议单独配一个 user_action 日志器,指向独立文件。

















