Files
kami_spider_monorepo/middleware/error_handler.py
danial 0e41e7acce feat(core): 初始化核心配置和部署文件
- 添加 .env.example 环境变量配置示例
- 添加 .gitignore 忽略文件配置
- 添加 core/config.py 配置管理模块
- 添加 deployments/k8s/configmap.yaml Kubernetes 配置
- 添加 core/database.py 数据库连接管理模块
- 添加 core/dependencies.py 全局依赖模块
- 添加 DEPENDENCIES_UPDATED.md 依赖更新记录
- 添加 deployments/k8s/deployment.yaml Kubernetes 部署配置- 添加 deployments/swarm/docker-compose.swarm.yml Docker Swarm 部署配置
- 添加 deployments/docker/docker-compose.yml Docker 部署配置
- 添加 deployments/docker/Dockerfile 应用镜像构建文件
- 添加 middleware/error_handler.py 全局异常处理中间件
2025-11-01 14:32:29 +08:00

220 lines
5.6 KiB
Python

"""
Global exception handler middleware.
Catches all exceptions and returns standardized error responses.
"""
import traceback
from typing import Union
from fastapi import Request, status
from fastapi.responses import ORJSONResponse
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException
from pydantic import ValidationError
from core.exceptions import BaseAppException
from core.responses import error, BusinessCode, ErrorMessage
from observability.logging import get_logger
logger = get_logger(__name__)
async def app_exception_handler(
request: Request,
exc: BaseAppException
) -> ORJSONResponse:
"""
Handle application-specific exceptions.
Args:
request: FastAPI request
exc: Application exception
Returns:
JSONResponse: Standardized error response
"""
# Get trace ID from request
trace_id = getattr(request.state, "trace_id", "")
# Log exception
logger.error(
f"Application error: {exc.message}",
extra={
"trace_id": trace_id,
"error_code": exc.code,
"details": exc.details,
},
exc_info=True
)
# Return error response
error_response = error(
code=exc.code,
message=exc.message,
trace_id=trace_id
)
return ORJSONResponse(
status_code=exc.status_code,
content=error_response.model_dump(mode='json')
)
async def validation_exception_handler(
request: Request,
exc: Union[RequestValidationError, ValidationError]
) -> ORJSONResponse:
"""
Handle Pydantic validation errors.
Args:
request: FastAPI request
exc: Validation error
Returns:
JSONResponse: Standardized error response
"""
# Get trace ID from request
trace_id = getattr(request.state, "trace_id", "")
# Extract validation errors
errors = exc.errors() if hasattr(exc, "errors") else []
# Format error message
if errors:
first_error = errors[0]
field = ".".join(str(loc) for loc in first_error.get("loc", []))
message = f"Validation error: {field} - {first_error.get('msg', 'Invalid value')}"
else:
message = "Validation error"
# Log validation error
logger.warning(
f"Validation error: {message}",
extra={
"trace_id": trace_id,
"validation_errors": errors,
}
)
# Return error response
error_response = error(
code=BusinessCode.INVALID_INPUT,
message=message,
trace_id=trace_id
)
return ORJSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content=error_response.model_dump(mode='json')
)
async def http_exception_handler(
request: Request,
exc: StarletteHTTPException
) -> ORJSONResponse:
"""
Handle HTTP exceptions.
Args:
request: FastAPI request
exc: HTTP exception
Returns:
JSONResponse: Standardized error response
"""
# Get trace ID from request
trace_id = getattr(request.state, "trace_id", "")
# Map HTTP status to business code
code_mapping = {
404: BusinessCode.RESOURCE_NOT_FOUND,
401: BusinessCode.LOGIN_FAILED,
403: BusinessCode.INSUFFICIENT_PERMISSIONS,
409: BusinessCode.RESOURCE_CONFLICT,
}
business_code = code_mapping.get(exc.status_code, BusinessCode.UNKNOWN_ERROR)
# Log HTTP error
logger.warning(
f"HTTP error {exc.status_code}: {exc.detail}",
extra={
"trace_id": trace_id,
"status_code": exc.status_code,
}
)
# Return error response
error_response = error(
code=business_code,
message=str(exc.detail),
trace_id=trace_id
)
return ORJSONResponse(
status_code=exc.status_code,
content=error_response.model_dump(mode='json')
)
async def generic_exception_handler(
request: Request,
exc: Exception
) -> ORJSONResponse:
"""
Handle all other unhandled exceptions.
Args:
request: FastAPI request
exc: Any exception
Returns:
JSONResponse: Standardized error response
"""
# Get trace ID from request
trace_id = getattr(request.state, "trace_id", "")
# Log full exception with traceback
logger.error(
f"Unhandled exception: {str(exc)}",
extra={
"trace_id": trace_id,
"exception_type": type(exc).__name__,
"traceback": traceback.format_exc(),
},
exc_info=True
)
# Return generic error response
error_response = error(
code=BusinessCode.UNKNOWN_ERROR,
message=ErrorMessage.UNKNOWN_ERROR,
trace_id=trace_id
)
return ORJSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content=error_response.model_dump(mode='json')
)
def register_exception_handlers(app) -> None:
"""
Register all exception handlers with FastAPI application.
Args:
app: FastAPI application instance
"""
# Application exceptions
app.add_exception_handler(BaseAppException, app_exception_handler)
# Validation errors
app.add_exception_handler(RequestValidationError, validation_exception_handler)
app.add_exception_handler(ValidationError, validation_exception_handler)
# HTTP exceptions
app.add_exception_handler(StarletteHTTPException, http_exception_handler)
# Generic exception handler (catch-all)
app.add_exception_handler(Exception, generic_exception_handler)