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