VWED_server/middlewares/response_alert_middleware.py

174 lines
5.5 KiB
Python
Raw Normal View History

2025-09-20 16:50:45 +08:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
响应异常告警中间件模块
拦截非正常响应并推送异常日志到主系统
"""
import json
import logging
from typing import Any, Dict
from fastapi import Request, Response
from starlette.types import Message
from utils.logger import get_logger
from utils.alert_sync import get_alert_sync_service
# 设置日志
logger = get_logger("middleware.response_alert")
class ResponseAlertMiddleware:
"""
响应异常告警中间件
监控所有API响应当出现非正常响应时推送告警
"""
def __init__(self, app):
self.app = app
self.alert_service = get_alert_sync_service()
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
await self.app(scope, receive, send)
return
# 创建请求对象获取路径信息
request = Request(scope, receive)
# 捕获响应数据
response_body = bytearray()
async def send_wrapper(message: Message):
if message["type"] == "http.response.body":
body = message.get("body", b"")
response_body.extend(body)
await send(message)
# 处理请求
await self.app(scope, receive, send_wrapper)
# 检查响应并发送告警
await self._check_and_alert(request, response_body)
async def _check_and_alert(self, request: Request, response_body: bytearray):
"""
检查响应并发送告警
Args:
request: 请求对象
response_body: 响应体
"""
try:
# 解析响应体
body_str = response_body.decode('utf-8')
if not body_str:
return
# 尝试解析JSON响应
try:
response_data = json.loads(body_str)
except json.JSONDecodeError:
# 非JSON响应跳过检查
return
# 检查是否为标准格式响应
if not isinstance(response_data, dict) or 'code' not in response_data:
return
response_code = response_data.get('code')
message = response_data.get('message', '未知错误')
# 只处理非成功响应 (code != 200)
if response_code == 200:
return
# 构造告警信息
alert_message = self._format_alert_message(request, response_code, message, response_data)
# 创建日志记录用于告警推送
log_record = self._create_log_record(alert_message, response_code)
# 推送告警
success = self.alert_service.sync_alert(log_record)
if success:
logger.debug(f"异常响应告警已推送: {request.method} {request.url.path} - {response_code}")
else:
logger.warning(f"异常响应告警推送失败: {request.method} {request.url.path} - {response_code}")
except Exception as e:
# 避免告警中间件本身的错误影响正常流程
logger.error(f"响应告警中间件处理错误: {str(e)}")
def _format_alert_message(self, request: Request, code: int, message: str, response_data: Dict[str, Any]) -> str:
"""
格式化告警消息
Args:
request: 请求对象
code: 响应码
message: 响应消息
response_data: 完整响应数据
Returns:
格式化的告警消息
"""
method = request.method
path = request.url.path
client_ip = request.client.host if request.client else "unknown"
# 构建基本告警信息
alert_msg = f"API异常响应: {method} {path} 返回错误码 {code}"
alert_msg += f" | 错误信息: {message}"
alert_msg += f" | 客户端IP: {client_ip}"
# 添加查询参数(如果有)
if request.query_params:
alert_msg += f" | 查询参数: {dict(request.query_params)}"
# 添加响应数据(如果有额外信息)
if 'data' in response_data and response_data['data'] is not None:
alert_msg += f" | 响应数据: {str(response_data['data'])[:200]}" # 限制长度
return alert_msg
def _create_log_record(self, message: str, response_code: int) -> logging.LogRecord:
"""
创建用于告警的日志记录
Args:
message: 告警消息
response_code: 响应码
Returns:
日志记录对象
"""
# 根据响应码确定日志级别
if response_code >= 500:
level = logging.ERROR
elif response_code >= 400:
level = logging.WARNING
else:
level = logging.WARNING
# 创建日志记录
record = logging.LogRecord(
name="api.response_alert",
level=level,
pathname=__file__,
lineno=0,
msg=message,
args=(),
exc_info=None
)
return record
def register_middleware(app):
"""
注册响应告警中间件到FastAPI应用
Args:
app: FastAPI应用实例
"""
app.add_middleware(ResponseAlertMiddleware)