赞
踩
诚然,每个人都会写bug,程序抛异常是一件很正常的事;既然异常总是会抛,那就想办法在抛出后,尽早解决才是王道。就拿Django来说,通常发生未知异常时,我们会将settings里的DEBUG=False改为True,然后盯着日志看。可谁没事老盯着日志看啊,未免也太浪费时间了;不能老是等待用户反馈异常和问题,万一用户懒得反馈了,岂不很尴尬。
需求:在异常发生时,进行异常埋点,接入开源平台Cat.
本篇是【Sentry部署+DingDing告警+Django接入】的兄弟篇,感兴趣可以了解下。
Django自定义异常处理
Django中间件针对异常做处理
Django集成
得赖于DRF对Django深度贴合,DRF中也有类似的处理过程。这里对官方文档做进一步总结,具体使用姿势可移步官网。
DRF异常处理的思路:dispatch 处理请求 -> handle_exception -> exception_handler -> raise_uncaught_exception -> raise
- 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
'运行
- 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层的异常,其他处理过程中的异常,这里无法处理。综上:方案一(自定义异常处理)不是一种好办法。
中间件是一个轻量级、底层的插件系统,可以介入 django 的请求和响应处理过程,修改 django 的输入和输出。其各个处理阶段,可参考这篇博文:Django中间件讲解 。文中同样提到某些场景下的异常,同样不能捕获,所以通过中间件也不是一种好办法。不过中间件+猴子补丁,或许可以达到想要的效果。例如针对django.core.handlers.exception文件里的response_for_exception方法做补丁替换,该方法本人已在生产中使用,效果颇佳。
- 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

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