Django 错误处理模式指南
Django 错误处理的最佳实践:区分错误类型、视图内优雅处理、自定义异常中间件、自定义异常类与表单校验错误的统一处理。
直接回答:健壮的 Django 错误处理建立在正确分类之上:预期内的运营错误(404、校验失败)优雅降级,代码缺陷记录告警,外部服务故障熔断兜底;视图处理、中间件兜底、自定义异常三层配合,构成完整防线。
先分清三类错误
- 运营性错误(预期内):资源不存在(404)、表单校验失败(400)、权限不足(403)——外部因素导致,应优雅处理;
- 代码缺陷(Bug):空指针、类型错误——不该发生,发生了要告警并修复;
- 外部服务故障:数据库超时、第三方 API 挂了——需要重试、熔断与降级。
三者处理策略完全不同:第一类给友好提示,第二类必须 noisy(告警叫醒人),第三类要兜底。
视图内的错误处理
from django.shortcuts import get_object_or_404
from django.http import JsonResponse
def article_detail(request, pk):
article = get_object_or_404(Article, pk=pk) # 404 交给框架
return JsonResponse({"title": article.title})
get_object_or_404 是"不存在即 404"的标准姿势。API 视图里对预期错误返回结构化响应:
def transfer(request):
try:
result = do_transfer(request.user, amount)
except InsufficientBalance:
return JsonResponse({"error": "余额不足"}, status=400)
except PaymentGatewayTimeout:
return JsonResponse({"error": "支付通道繁忙,请稍后再试"}, status=503)
return JsonResponse({"result": result}) # result 须是业务层定义的可序列化结果
自定义异常中间件:全局兜底
散落各处的 try/except 既重复又易漏。中间件统一兜底:
# middleware.py
import logging
from django.http import JsonResponse
logger = logging.getLogger(__name__)
class ExceptionMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_exception(self, request, exc):
from django.http import Http404
from django.core.exceptions import PermissionDenied, SuspiciousOperation
if isinstance(exc, (Http404, PermissionDenied, SuspiciousOperation)):
return None # 保留 Django 的 404/403/400 语义
if isinstance(exc, AppError): # AppError 须定义或导入到此模块
return JsonResponse({"error": exc.code}, status=exc.status)
logger.exception("未捕获异常: %s %s", request.method, request.path)
return JsonResponse({"error": "服务器内部错误"}, status=500)
process_exception 处理视图或模板渲染抛出的异常,不涵盖所有中间件异常。保留标准 HTTP 异常语义,生产关闭 DEBUG;更早的异常响应或流式响应已经发出后也不能由它统一改写。
把异常日志写到文件并由观测云 DataKit 采集时,要为文本堆栈配置多行合并。主动触发一次测试异常,确认请求路径和完整堆栈落在同一条记录中,且响应不向用户暴露内部信息。
自定义异常类
业务异常建一套体系:
class AppError(Exception):
status = 400
code = "app_error"
class InsufficientBalance(AppError):
code = "insufficient_balance"
class RateLimitExceeded(AppError):
status = 429
code = "rate_limited"
视图里 raise InsufficientBalance(),中间件识别 AppError 家族返回对应状态码——业务错误从此有了统一出口。
表单校验错误
from django.shortcuts import render, redirect
from django.views.decorators.http import require_POST
from .forms import ArticleForm
@require_POST
def create_article(request):
form = ArticleForm(request.POST)
if form.is_valid():
form.save()
return redirect("article-list")
return render(request, "form.html", {"form": form}, status=400)
此为视图结构示意;项目需定义 article-list 路由,并保留 CSRF 中间件、登录与写入权限校验。金额转账片段中的 amount、异常和 do_transfer 也需业务层定义,不能直接作为支付接口上线。
API 场景(DRF)序列化器的 ValidationError 自动转为 400 响应,错误信息按字段组织——前端的表单回显就靠它。
常见问题(FAQ)
Q:DEBUG=False 时 500 页面怎么定制?
A:建 templates/500.html(同理 404.html/403.html),视图层还可配 handler500 自定义。静态页别依赖数据库——出问题时数据库可能也不可用。
Q:中间件和视图里 try/except 的分工?
A:能预期的业务异常尽量在视图内就地处理(语义清晰);中间件只兜"没想到的"。全靠中间件会让错误响应千篇一律、丢失上下文。
Q:异常日志要记多详细?
A:带请求路径、用户、关键参数(脱敏后)与完整堆栈。logger.exception 自动附堆栈,是错误日志的标准姿势。
官方参考
本文基于官方文档整理,未进行运行时或性能测试。示例中的业务函数、数据模型和部署地址需结合项目补全;局部片段不等同于完整生产应用。