rtc_backend/utils/middleware.py
repair-agent 7a6d7814e0
Some checks failed
Build and Deploy Backend / build-and-deploy (push) Failing after 57s
fix store bug
2026-02-12 14:05:51 +08:00

124 lines
3.8 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.

"""
全局异常捕获中间件
两层防线确保异常上报到 Log Center
1. got_request_exception 信号 —— 捕获被 Django convert_exception_to_response 吞掉的异常
(如 CommonMiddleware 的 APPEND_SLASH RuntimeError
2. ExceptionReportMiddleware 的 try/except —— 兜底捕获穿透所有内层包裹的异常
"""
import os
import sys
import traceback
import threading
import requests
from django.http import JsonResponse
from django.core.signals import got_request_exception
LOG_CENTER_URL = os.environ.get('LOG_CENTER_URL', 'https://qiyuan-log-center-api.airlabs.art')
LOG_CENTER_ENABLED = os.environ.get('LOG_CENTER_ENABLED', 'true').lower() == 'true'
def _send_to_log_center(payload):
"""异步发送日志到 Log Center"""
def send_async():
try:
requests.post(
f"{LOG_CENTER_URL}/api/v1/logs/report",
json=payload,
timeout=3,
)
except Exception:
pass
thread = threading.Thread(target=send_async)
thread.daemon = True
thread.start()
def _report_exception(exc, request):
"""构造 payload 并上报异常"""
if not LOG_CENTER_ENABLED:
return
try:
tb = traceback.extract_tb(exc.__traceback__) if exc.__traceback__ else []
last_frame = tb[-1] if tb else None
payload = {
"project_id": "rtc_backend",
"environment": os.environ.get('ENVIRONMENT', 'production'),
"level": "ERROR",
"error": {
"type": type(exc).__name__,
"message": str(exc),
"file_path": last_frame.filename if last_frame else "unknown",
"line_number": last_frame.lineno if last_frame else 0,
"stack_trace": traceback.format_exception(exc) if exc.__traceback__ else [str(exc)],
},
"context": {
"url": request.path,
"method": request.method,
"view": "middleware",
},
}
_send_to_log_center(payload)
except Exception:
pass
def _on_request_exception(sender, request, **kwargs):
"""
Django 信号回调convert_exception_to_response 内部触发。
此时 sys.exc_info() 仍持有完整的异常上下文。
"""
exc_info = sys.exc_info()
exc = exc_info[1]
if exc:
_report_exception(exc, request)
# 模块加载时注册信号,全局生效
got_request_exception.connect(_on_request_exception)
class TrailingSlashMiddleware:
"""
为缺少尾部斜杠的 API 请求补全 '/',直接修改 request.path_info
不做 HTTP 重定向,因此 POST/PUT/PATCH 请求体完好保留。
配合 APPEND_SLASH = False 使用,替代 CommonMiddleware 的重定向逻辑。
必须放在 CommonMiddleware 之前。
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if not request.path_info.endswith('/'):
request.path_info = request.path_info + '/'
return self.get_response(request)
class ExceptionReportMiddleware:
"""
全局异常捕获中间件,必须放在 MIDDLEWARE 列表的第一个位置。
作为第二道防线:如果异常穿过了所有内层中间件的
convert_exception_to_response 包裹,这里的 try/except 仍会兜底。
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
try:
response = self.get_response(request)
return response
except Exception as exc:
_report_exception(exc, request)
return JsonResponse(
{"code": 1, "message": str(exc), "data": None},
status=500,
)