VWED_server/middlewares/response_alert_middleware.py

174 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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)