|
|
|
|
@ -1,42 +1,90 @@
|
|
|
|
|
# 导入日志模块,用于记录中间件运行过程中的日志信息
|
|
|
|
|
import logging
|
|
|
|
|
# 导入时间模块,用于计算页面渲染耗时
|
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
# 从ipware工具导入获取客户端IP的函数
|
|
|
|
|
from ipware import get_client_ip
|
|
|
|
|
# 从user_agents工具导入解析用户代理的函数
|
|
|
|
|
from user_agents import parse
|
|
|
|
|
|
|
|
|
|
# 导入博客相关的ES配置和文档管理器(用于记录页面加载时间)
|
|
|
|
|
from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager
|
|
|
|
|
|
|
|
|
|
# 创建当前模块的日志记录器
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class OnlineMiddleware(object):
|
|
|
|
|
"""
|
|
|
|
|
自定义Django中间件,用于:
|
|
|
|
|
1. 计算页面渲染耗时
|
|
|
|
|
2. 收集客户端信息(IP、用户代理)
|
|
|
|
|
3. 在启用Elasticsearch时记录访问性能数据
|
|
|
|
|
4. 替换响应中的特定标记为实际加载时间
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, get_response=None):
|
|
|
|
|
"""
|
|
|
|
|
中间件初始化方法
|
|
|
|
|
:param get_response: Django框架传入的处理响应的函数,用于链式调用中间件
|
|
|
|
|
"""
|
|
|
|
|
self.get_response = get_response
|
|
|
|
|
# 调用父类初始化方法(Python 2兼容写法,在Python 3中可省略)
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
|
|
|
|
def __call__(self, request):
|
|
|
|
|
''' page render time '''
|
|
|
|
|
"""
|
|
|
|
|
中间件核心处理方法,在请求到达视图前和响应返回客户端前执行
|
|
|
|
|
:param request: Django的请求对象,包含客户端请求信息
|
|
|
|
|
:return: 处理后的响应对象
|
|
|
|
|
"""
|
|
|
|
|
# 记录请求处理开始时间(用于计算耗时)
|
|
|
|
|
start_time = time.time()
|
|
|
|
|
|
|
|
|
|
# 调用下一个中间件或视图函数,获取响应对象
|
|
|
|
|
response = self.get_response(request)
|
|
|
|
|
|
|
|
|
|
# 从请求头中获取用户代理字符串(如浏览器型号、系统等信息)
|
|
|
|
|
http_user_agent = request.META.get('HTTP_USER_AGENT', '')
|
|
|
|
|
# 获取客户端IP地址(第二个返回值为是否是公开IP,此处暂不使用)
|
|
|
|
|
ip, _ = get_client_ip(request)
|
|
|
|
|
# 解析用户代理字符串,转换为可操作的对象(方便提取浏览器、系统等信息)
|
|
|
|
|
user_agent = parse(http_user_agent)
|
|
|
|
|
|
|
|
|
|
# 判断响应是否为非流式响应(流式响应无法修改内容,如文件下载)
|
|
|
|
|
if not response.streaming:
|
|
|
|
|
try:
|
|
|
|
|
# 计算页面渲染总耗时(当前时间 - 开始时间)
|
|
|
|
|
cast_time = time.time() - start_time
|
|
|
|
|
|
|
|
|
|
# 如果启用了Elasticsearch,记录性能数据
|
|
|
|
|
if ELASTICSEARCH_ENABLED:
|
|
|
|
|
# 将耗时转换为毫秒并保留2位小数
|
|
|
|
|
time_taken = round((cast_time) * 1000, 2)
|
|
|
|
|
# 获取当前请求的URL路径
|
|
|
|
|
url = request.path
|
|
|
|
|
# 导入Django的时区工具,用于记录当前时间
|
|
|
|
|
from django.utils import timezone
|
|
|
|
|
# 通过文档管理器向Elasticsearch插入一条性能记录
|
|
|
|
|
ElaspedTimeDocumentManager.create(
|
|
|
|
|
url=url,
|
|
|
|
|
time_taken=time_taken,
|
|
|
|
|
log_datetime=timezone.now(),
|
|
|
|
|
useragent=user_agent,
|
|
|
|
|
ip=ip)
|
|
|
|
|
url=url, # 访问的URL
|
|
|
|
|
time_taken=time_taken, # 页面加载耗时(毫秒)
|
|
|
|
|
log_datetime=timezone.now(), # 记录时间(当前时区)
|
|
|
|
|
useragent=user_agent, # 解析后的用户代理信息
|
|
|
|
|
ip=ip # 客户端IP地址
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 将响应内容中的<!!LOAD_TIMES!!>标记替换为实际耗时(保留前5位字符)
|
|
|
|
|
# 注:需确保响应内容为bytes类型,因此使用str.encode转换
|
|
|
|
|
response.content = response.content.replace(
|
|
|
|
|
b'<!!LOAD_TIMES!!>', str.encode(str(cast_time)[:5]))
|
|
|
|
|
b'<!!LOAD_TIMES!!>', str.encode(str(cast_time)[:5])
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 捕获所有异常,避免中间件错误导致请求失败
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error("Error OnlineMiddleware: %s" % e)
|
|
|
|
|
# 记录异常信息到日志
|
|
|
|
|
logger.error("Error in OnlineMiddleware: %s" % e)
|
|
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
# 返回处理后的响应对象
|
|
|
|
|
return response
|