[Python第三方库系列] - Better Exceptions

2019-03-01 14:05:04 浏览数 (1)

[Python第三方库系列] - Better Exceptions

Better Exceptions是最近一期的Python Weekly 和Pycoders Weekly上都推荐的一个库,用处是展示更友好的异常信息。

无论在什么语言里遇到异常是很正常的,遇到线上异常也是常有的。本地异常的话通过pdb调试/print输出关键信息是可行的,但是对于线上异常的话,只能从日志里查看,但日志里的信息可能只是提示你:ValueError: need more than 3 values to unpack 或者是 KeyError: 'a',遇到这种问题,一般的做法是本地启动项目,尝试重现,这样你才能知道上下文是什么。但,往往很难复现,因为从日志里你看不到用户的输入是什么?如果你没有手动捕获异常,并把造成异常的数据写入log。(当然,最好的做法其实是使用Sentry)

上面的错误可以简化为这种形式:

代码语言:javascript复制
# coding:utf-8
request = "test test test"  # 从request中获取到数据
a, b, c, d = request.split()  # 处理数据

# 运行后:
Traceback (most recent call last):
  File "exc.py", line 8, in <module>
      a, b, c, d = request.split()
ValueError: need more than 3 values to unpack

或者这种

代码语言:javascript复制
# coding:utf-8
# 前端ajax传过来数据,后端处理
request_dict = {}
user_name = request_dict['user_name']

# 运行后:
Traceback (most recent call last):
  File "exc.py", line 5, in <module>
      user_name = request_dict['user_name']
KeyError: 'user_name'

后一种情况一个简单的场景就是,按照之前的逻辑,代码没问题,但是上线后,一直会报这个错误,为啥呢?谁也搞不清楚,只能去看用户到底发了什么数据,怎么看呢。把用户端发过来的数据打印出来?在生产环境这么做吗?(当然,最好的方法还是使用Sentry)

好了,这就是Better Exceptions出现的场景了。同样是上面的代码,增加 import better_exceptions之后就是这样:

代码语言:javascript复制
# coding:utf-8
import better_exceptions

request = "test test test"  # 从request中获取到数据
a, b, c, d = request.split()  # 处理数据

# 运行后:
Traceback (most recent call last):
  File "exc.py", line 8, in <module>
      a, b, c, d = request.split()
                   └ 'test test test'    # 增加了内容
ValueError: need more than 3 values to unpack

或者这种

代码语言:javascript复制
# coding:utf-8
import better_exceptions

# 前端ajax传过来数据,后端处理
request_dict = {}
user_name = request_dict['user_name']

# 运行后:
Traceback (most recent call last):
  File "exc.py", line 5, in <module>
      user_name = request_dict['user_name']
                  └ {}
KeyError: 'user_name'

Better Exceptions是怎么做到的呢

从上面的应用场景描述,以及最终呈现的结果来看,似乎是有用的。但是,对于Django和Tornado这样的Web框架,没啥用。为什么呢?

来看Better Exceptions的实现:

代码语言:javascript复制
better_exceptions/__init__.py  最后几行代码

def excepthook(exc, value, tb):
    formatted, colored_source = format_traceback(tb)

    if not str(value) and exc is AssertionError:
        value.args = (colored_source,)
    title = traceback.format_exception_only(exc, value)

    full_trace = u'Traceback (most recent call last):n{}{}n'.format(formatted, title[0].strip())
    write_stream(full_trace)


sys.excepthook = excepthook  # 关键是这个

主要是用了sys模块的excepthook方法,这个方法的描述如下:

ref: https://docs.python.org/2/library/sys.html#sys.excepthook

This function prints out a given traceback and exception to sys.stderr. When an exception is raised and uncaught, the interpreter calls sys.excepthook with three arguments, the exception class, exception instance, and a traceback object. In an interactive session this happens just before control is returned to the prompt; in a Python program this happens just before the program exits. The handling of such top-level exceptions can be customized by assigning another three-argument function to sys.excepthook.

大概意思就是,如果系统抛出一个未捕获的异常,那么解释器就会调用sys.excepthook方法,同时传递三个参数:异常类(ValueError或者KeyError之类的),异常实例,和traceback对象。

这意味着,你可以通过重写这个方法来处理系统未捕获的异常处理。但,在框架中(我只看了Django代码,测试了Tornado)会自己处理异常,所以这种hook的方式不会被触发。

看到这里,可能会觉得,那这玩意有啥用呢?我能想到的场景是你自己写的爬虫,定时执行可以用的上,另外介绍这个库的另一个作用就是可以用来学习下Python中的几个模块,比如sys.excepthook, inspect, ast, TraceBack等模块的使用。

另外我上面也提到了两次Sentry,通过这个也可以看下,Sentry的Python客户端(Raven-Python)是怎么处理异常的。

这个库的安装直接pip就行:

代码语言:javascript复制
pip install better-exceptions

0 人点赞