You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Django/doc/blog/middleware.py

79 lines
5.1 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 导入Python内置logging模块用于记录中间件运行过程中的日志如错误信息
import logging
# 导入Python内置time模块用于计算请求处理耗时页面加载时间
import time
# 导入ipware库的get_client_ip函数用于获取请求客户端的真实IP地址兼容多种部署场景
from ipware import get_client_ip
# 导入user_agents库的parse函数用于解析用户代理UA字符串提取浏览器、设备、系统信息
from user_agents import parse
# 从当前应用的documents模块导入
# 1. ELASTICSEARCH_ENABLED判断项目是否启用Elasticsearch之前定义的全局变量
# 2. ElaspedTimeDocumentManager性能监控文档管理类用于将耗时数据存入Elasticsearch
from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager
# 创建日志记录器日志名称与当前模块一致__name__便于定位日志来源区分其他模块日志
logger = logging.getLogger(__name__)
# 定义自定义中间件类OnlineMiddleware遵循Django中间件接口规范
# 作用1. 计算请求处理耗时页面加载时间2. 记录访问IP、设备信息到Elasticsearch3. 替换页面中的加载时间占位符
class OnlineMiddleware(object):
# 中间件初始化方法接收get_response参数Django 1.10+中间件必需参数,代表后续中间件/视图的响应流程)
def __init__(self, get_response=None):
# 保存get_response到实例属性后续在__call__方法中调用确保请求流程继续向下执行
self.get_response = get_response
# 调用父类object的初始化方法确保基础类功能正常Python 2/3兼容写法
super().__init__()
# 中间件核心执行方法,处理每个请求的入口和出口(请求到达时执行前半部分,响应返回时执行后半部分)
def __call__(self, request):
''' page render time ''' # 注释:该方法用于计算页面渲染耗时
# 记录请求开始时间(时间戳,单位:秒),作为耗时计算的起始点
start_time = time.time()
# 调用后续中间件/视图函数获取响应对象response此时请求已完成业务处理
response = self.get_response(request)
# 从请求的META信息中获取用户代理UA字符串
# HTTP_USER_AGENT是请求头中的字段包含浏览器、设备、系统等信息默认值为空字符串
http_user_agent = request.META.get('HTTP_USER_AGENT', '')
# 调用get_client_ip函数获取客户端真实IP
# 返回值为元组ip地址, 是否为代理IP此处仅取IP地址_忽略代理标记
ip, _ = get_client_ip(request)
# 解析UA字符串调用parse函数将原始UA字符串转换为结构化对象可通过属性获取浏览器/设备/系统信息)
user_agent = parse(http_user_agent)
# 判断响应是否为非流式响应(流式响应如文件下载,无需处理加载时间和替换占位符)
if not response.streaming:
try:
# 计算请求处理总耗时:当前时间 - 开始时间(单位:秒)
cast_time = time.time() - start_time
# 如果启用了Elasticsearch将性能数据存入Elasticsearch
if ELASTICSEARCH_ENABLED:
# 耗时转换为毫秒保留2位小数更符合性能监控的常用单位
time_taken = round((cast_time) * 1000, 2)
# 获取请求的路径(如"/article/1/"作为性能记录的URL标识
url = request.path
# 导入Django的timezone模块延迟导入避免循环导入问题用于获取当前时间
from django.utils import timezone
# 调用ElaspedTimeDocumentManager的create方法插入性能记录到Elasticsearch
# 包含URL、耗时、记录时间、用户代理信息、IP地址
ElaspedTimeDocumentManager.create(
url=url,
time_taken=time_taken,
log_datetime=timezone.now(),
useragent=user_agent,
ip=ip)
# 替换响应内容中的占位符:
# 将页面中的'<!!LOAD_TIMES!!>'字符串替换为实际耗时保留前5个字符如"0.321"
# 注意response.content是字节类型需用str.encode将字符串耗时转换为字节
response.content = response.content.replace(
b'<!!LOAD_TIMES!!>', str.encode(str(cast_time)[:5]))
# 捕获所有异常,避免中间件报错导致响应失败
except Exception as e:
# 记录异常日志将错误信息写入日志便于后续排查问题如Elasticsearch连接失败、占位符替换失败
logger.error("Error OnlineMiddleware: %s" % e)
# 返回处理后的响应对象,最终返回给客户端
return response