Django 错误处理模式指南

Django 错误处理的最佳实践:区分错误类型、视图内优雅处理、自定义异常中间件、自定义异常类与表单校验错误的统一处理。

最佳实践
并发任务的多路径协同插画

直接回答:健壮的 Django 错误处理建立在正确分类之上:预期内的运营错误(404、校验失败)优雅降级,代码缺陷记录告警,外部服务故障熔断兜底;视图处理、中间件兜底、自定义异常三层配合,构成完整防线。

先分清三类错误

  1. 运营性错误(预期内):资源不存在(404)、表单校验失败(400)、权限不足(403)——外部因素导致,应优雅处理;
  2. 代码缺陷(Bug):空指针、类型错误——不该发生,发生了要告警并修复;
  3. 外部服务故障:数据库超时、第三方 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 自动附堆栈,是错误日志的标准姿势。

官方参考

本文基于官方文档整理,未进行运行时或性能测试。示例中的业务函数、数据模型和部署地址需结合项目补全;局部片段不等同于完整生产应用。

延伸阅读

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台