Django捕获所有异常的处理

探讨Django中处理异常的三种策略:自定义异常处理、中间件异常处理及框架集成,重点介绍如何通过中间件实现异常埋点,提高应用稳定性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

诚然,每个人都会写bug,程序抛异常是一件很正常的事;既然异常总是会抛,那就想办法在抛出后,尽早解决才是王道。就拿Django来说,通常发生未知异常时,我们会将settings里的DEBUG=False改为True,然后盯着日志看。可谁没事老盯着日志看啊,未免也太浪费时间了;不能老是等待用户反馈异常和问题,万一用户懒得反馈了,岂不很尴尬。

需求:在异常发生时,进行异常埋点,接入开源平台Cat.

本篇是【Sentry部署+DingDing告警+Django接入】的兄弟篇,感兴趣可以了解下。

思路

  • Django自定义异常处理

  • Django中间件针对异常做处理

  • Django集成 

1、Django自定义异常处理

得赖于DRF对Django深度贴合,DRF中也有类似的处理过程。这里对官方文档做进一步总结,具体使用姿势可移步官网。

DRF异常处理的思路:dispatch 处理请求 -> handle_exception -> exception_handler -> raise_uncaught_exception -> raise

  • 在APIView类里有个方法:handle_exception,它是在APIView处理视图方法,发生异常时调用
def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch,
        but with extra hooks for startup, finalize, and exception handling.
        """
        self.args = args
        self.kwargs = kwargs
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed

            response = handler(request, *args, **kwargs)

        except Exception as exc:
            response = self.handle_exception(exc)

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
  • 这个handle_exception主要干了这么一件事,这里看看源码
def handle_exception(self, exc):
        """
        Handle any exception that occurs, by returning an appropriate response,
        or re-raising the error.
        """
        if isinstance(exc, (exceptions.NotAuthenticated,
                            exceptions.AuthenticationFailed)):
            # WWW-Authenticate header for 401 responses, else coerce to 403
            auth_header = self.get_authenticate_header(self.request)

            if auth_header:
                exc.auth_header = auth_header
            else:
                exc.status_code = status.HTTP_403_FORBIDDEN

        exception_handler = self.settings.EXCEPTION_HANDLER

        context = self.get_exception_handler_context()
        response = exception_handler(exc, context)

        if response is None:
            self.raise_uncaught_exception(exc)

        response.exception = True
        return response

可以看到,handle_exception拿到view层发生的异常后,将异常转换为特定状态码的response,并添加特定的说明信息,如果转换不了,就直接抛出异常。raise_uncaught_exception就是在settings.DEBUG为True时,打印异常相关的日志、堆栈。也就是说,如果我们要对未捕获的异常做埋点,就可以放到raise_uncaught_exception这里。但这里也仅限于view层的异常,其他处理过程中的异常,这里无法处理。综上:方案一(自定义异常处理)不是一种好办法。

2、Django中间件针对异常做处理

中间件是一个轻量级、底层的插件系统,可以介入 django 的请求和响应处理过程,修改 django 的输入和输出。其各个处理阶段,可参考这篇博文:Django中间件讲解 。文中同样提到某些场景下的异常,同样不能捕获,所以通过中间件也不是一种好办法。不过中间件+猴子补丁,或许可以达到想要的效果。例如针对django.core.handlers.exception文件里的response_for_exception方法做补丁替换,该方法本人已在生产中使用,效果颇佳。

  • 我们来看看,一个Django请求,在中间件里,是如何流转的:

  •  一个简单的中间件使用案例:
import cat
import time

from django.utils.deprecation import MiddlewareMixin
from django.http import HttpRequest, HttpResponse

from comm.utils import generate_request_id


class MonitorMiddleware(MiddlewareMixin):

    def __init__(self, get_response=None):
        super(MonitorMiddleware, self).__init__(get_response)

    @staticmethod
    def process_exception(request, exc):
        # type: (HttpRequest, Exception) -> None
        """
        处理所有的异常, 在views.py中出现异常时被调用,返回None或时HttpResponse对象, 注意:404错误属于url的异常,这里不能被捕捉到
        :param request: 进入的请求对象
        :param exc: 异常对象
        :return:
        """
        cat.init("my-application-name", debug=False, encoder=cat.ENCODER_TEXT, sampling=True, logview=False)
        with cat.Transaction(request.get_full_path(), request.method) as t:
            t.add_data(request.get_full_path(), request.META.get('HTTP_REFERER'))
            cat.metric(u'{0}_{1}'.format(request.method, request.get_full_path())).count()
            cat.log_exception(exc)
        return

    @staticmethod
    def process_request(request):
        # type: (HttpRequest) -> None
        """
        在urls.py之前调用,返回None或HttpResponse对象
        :param request:
        :return:
        """
        request.context = dict(
            start_time=int(time.time()),
            request_id=generate_request_id(request)
        )

    @staticmethod
    def process_response(request, response):
        # type: (HttpRequest, HttpResponse) -> None
        """
        在模板渲染完,返回浏览器前调用, 返回 HttpResponse 或 StreamingHttpResponse 对象
        :param request:
        :param response:
        :return:
        """
        context = getattr(request, 'context', {})
        end_time = int(time.time())
        
        pass

    @staticmethod
    def process_view(request, view_func, view_args, view_kwargs):
        """
        在views.py之前调用,返回None或时HttpResponse对象
        :param request:
        :param view_func:
        :param view_args:
        :param view_kwargs:
        :return:
        """
        pass

    @staticmethod
    def process_template_response(request, response):
        """
        views.py 之后,渲染模板之前执行  返回实现了render方法的响应对象
        :param self:
        :param request:
        :param response:
        :return:
        """
        pass

3、Django集成

灵感来源:sentry对Django异常的采集思路,但是我们需要将sentry的集成放到我们的项目里,以便完成对其扩展,加入我们的异常埋点代码,同时又不影响sentry的接入。这种方式难度较大,改sentry源码很可能改脏,不熟悉python的话,就不建议这种方式了。 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值