# 导入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) # 替换响应内容中的占位符: # 将页面中的''字符串替换为实际耗时(保留前5个字符,如"0.321") # 注意:response.content是字节类型,需用str.encode将字符串耗时转换为字节 response.content = response.content.replace( b'', str.encode(str(cast_time)[:5])) # 捕获所有异常,避免中间件报错导致响应失败 except Exception as e: # 记录异常日志:将错误信息写入日志,便于后续排查问题(如Elasticsearch连接失败、占位符替换失败) logger.error("Error OnlineMiddleware: %s" % e) # 返回处理后的响应对象,最终返回给客户端 return response