""" 自定义异常处理 """ import os import traceback import threading import requests from rest_framework.views import exception_handler from rest_framework import status from utils.response import APIResponse # Log Center 配置 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 report_to_log_center(exc, context): """异步上报错误到 Log Center""" 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 # 获取请求信息 request = context.get('request') request_path = request.path if request else 'unknown' request_method = request.method if request else 'unknown' 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": str(context.get('view', '')), } } # 异步发送,不阻塞响应 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() except Exception: pass # 上报失败不影响主业务 class BusinessException(Exception): """业务异常""" def __init__(self, code=1, message='业务错误', status_code=status.HTTP_400_BAD_REQUEST): self.code = code self.message = message self.status_code = status_code super().__init__(message) class ErrorCode: """错误码定义""" # 通用错误 1-99 SUCCESS = 0 UNKNOWN_ERROR = 1 PARAM_ERROR = 2 NOT_FOUND = 3 PERMISSION_DENIED = 4 # 用户模块 100-199 USER_NOT_FOUND = 100 USER_DISABLED = 101 PHONE_INVALID = 102 TOKEN_EXPIRED = 103 SMS_CODE_INVALID = 110 SMS_CODE_EXPIRED = 111 SMS_CODE_SEND_LIMIT = 112 # 设备模块 200-299 DEVICE_NOT_FOUND = 200 DEVICE_ALREADY_BOUND = 201 DEVICE_MAC_EXISTS = 202 DEVICE_SN_INVALID = 203 # 智能体模块 300-399 SPIRIT_NOT_FOUND = 300 SPIRIT_LIMIT_EXCEEDED = 301 # 批次模块 400-499 BATCH_NOT_FOUND = 400 BATCH_SN_EXISTS = 401 DEVICE_TYPE_NOT_FOUND = 402 # 管理员模块 500-599 ADMIN_NOT_FOUND = 500 ADMIN_PASSWORD_ERROR = 501 # 故事模块 600-699 STORY_NOT_FOUND = 600 SHELF_NOT_FOUND = 601 SHELF_LOCKED = 602 POINTS_NOT_ENOUGH = 603 SHELF_FULL = 604 # 音乐模块 700-799 TRACK_NOT_FOUND = 700 MUSIC_GENERATE_FAILED = 701 MUSIC_GENERATING = 702 MUSIC_DEFAULT_UNDELETABLE = 703 # 通知模块 800-899 NOTIFICATION_NOT_FOUND = 800 # 系统模块 900-999 FEEDBACK_SUBMIT_FAILED = 900 def custom_exception_handler(exc, context): """自定义异常处理器""" # 上报到 Log Center (仅上报非业务异常) if not isinstance(exc, BusinessException): report_to_log_center(exc, context) # 处理业务异常 if isinstance(exc, BusinessException): return APIResponse( code=exc.code, message=exc.message, status_code=exc.status_code ) # 调用DRF默认异常处理 response = exception_handler(exc, context) if response is not None: # 统一格式化DRF异常 error_message = '' if isinstance(response.data, dict): if 'detail' in response.data: error_message = str(response.data['detail']) else: # 处理字段验证错误 errors = [] for field, messages in response.data.items(): if isinstance(messages, list): errors.append(f"{field}: {', '.join(str(m) for m in messages)}") else: errors.append(f"{field}: {messages}") error_message = '; '.join(errors) else: error_message = str(response.data) return APIResponse( code=ErrorCode.UNKNOWN_ERROR, message=error_message, status_code=response.status_code ) # 处理未知异常 return APIResponse( code=ErrorCode.UNKNOWN_ERROR, message=str(exc), status_code=status.HTTP_500_INTERNAL_SERVER_ERROR )