当前位置:   article > 正文

Django捕获所有异常的处理_django异常处理

django异常处理

诚然,每个人都会写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处理视图方法,发生异常时调用
  1. def dispatch(self, request, *args, **kwargs):
  2. """
  3. `.dispatch()` is pretty much the same as Django's regular dispatch,
  4. but with extra hooks for startup, finalize, and exception handling.
  5. """
  6. self.args = args
  7. self.kwargs = kwargs
  8. request = self.initialize_request(request, *args, **kwargs)
  9. self.request = request
  10. self.headers = self.default_response_headers # deprecate?
  11. try:
  12. self.initial(request, *args, **kwargs)
  13. # Get the appropriate handler method
  14. if request.method.lower() in self.http_method_names:
  15. handler = getattr(self, request.method.lower(),
  16. self.http_method_not_allowed)
  17. else:
  18. handler = self.http_method_not_allowed
  19. response = handler(request, *args, **kwargs)
  20. except Exception as exc:
  21. response = self.handle_exception(exc)
  22. self.response = self.finalize_response(request, response, *args, **kwargs)
  23. return self.response
'
运行
  • 这个handle_exception主要干了这么一件事,这里看看源码
  1. def handle_exception(self, exc):
  2. """
  3. Handle any exception that occurs, by returning an appropriate response,
  4. or re-raising the error.
  5. """
  6. if isinstance(exc, (exceptions.NotAuthenticated,
  7. exceptions.AuthenticationFailed)):
  8. # WWW-Authenticate header for 401 responses, else coerce to 403
  9. auth_header = self.get_authenticate_header(self.request)
  10. if auth_header:
  11. exc.auth_header = auth_header
  12. else:
  13. exc.status_code = status.HTTP_403_FORBIDDEN
  14. exception_handler = self.settings.EXCEPTION_HANDLER
  15. context = self.get_exception_handler_context()
  16. response = exception_handler(exc, context)
  17. if response is None:
  18. self.raise_uncaught_exception(exc)
  19. response.exception = True
  20. 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请求,在中间件里,是如何流转的:

  •  一个简单的中间件使用案例:
  1. import cat
  2. import time
  3. from django.utils.deprecation import MiddlewareMixin
  4. from django.http import HttpRequest, HttpResponse
  5. from comm.utils import generate_request_id
  6. class MonitorMiddleware(MiddlewareMixin):
  7. def __init__(self, get_response=None):
  8. super(MonitorMiddleware, self).__init__(get_response)
  9. @staticmethod
  10. def process_exception(request, exc):
  11. # type: (HttpRequest, Exception) -> None
  12. """
  13. 处理所有的异常, 在views.py中出现异常时被调用,返回None或时HttpResponse对象, 注意:404错误属于url的异常,这里不能被捕捉到
  14. :param request: 进入的请求对象
  15. :param exc: 异常对象
  16. :return:
  17. """
  18. cat.init("my-application-name", debug=False, encoder=cat.ENCODER_TEXT, sampling=True, logview=False)
  19. with cat.Transaction(request.get_full_path(), request.method) as t:
  20. t.add_data(request.get_full_path(), request.META.get('HTTP_REFERER'))
  21. cat.metric(u'{0}_{1}'.format(request.method, request.get_full_path())).count()
  22. cat.log_exception(exc)
  23. return
  24. @staticmethod
  25. def process_request(request):
  26. # type: (HttpRequest) -> None
  27. """
  28. 在urls.py之前调用,返回None或HttpResponse对象
  29. :param request:
  30. :return:
  31. """
  32. request.context = dict(
  33. start_time=int(time.time()),
  34. request_id=generate_request_id(request)
  35. )
  36. @staticmethod
  37. def process_response(request, response):
  38. # type: (HttpRequest, HttpResponse) -> None
  39. """
  40. 在模板渲染完,返回浏览器前调用, 返回 HttpResponse 或 StreamingHttpResponse 对象
  41. :param request:
  42. :param response:
  43. :return:
  44. """
  45. context = getattr(request, 'context', {})
  46. end_time = int(time.time())
  47. pass
  48. @staticmethod
  49. def process_view(request, view_func, view_args, view_kwargs):
  50. """
  51. 在views.py之前调用,返回None或时HttpResponse对象
  52. :param request:
  53. :param view_func:
  54. :param view_args:
  55. :param view_kwargs:
  56. :return:
  57. """
  58. pass
  59. @staticmethod
  60. def process_template_response(request, response):
  61. """
  62. views.py 之后,渲染模板之前执行 返回实现了render方法的响应对象
  63. :param self:
  64. :param request:
  65. :param response:
  66. :return:
  67. """
  68. pass

3、Django集成

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

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号