|
|
# 导入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、设备信息到Elasticsearch;3. 替换页面中的加载时间占位符
|
|
|
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 |