自动化Python异常显示 - better-exceptions

by LauCyun Sep 12,2017 22:09:55 29,116 views

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

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

使用方法:

  • 通过pip安装better_exceptions
    $ pip install better_exceptions
  • 将它导入某处:
    import better_exceptions

栗子1:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# import better_exceptions

def division(a, b):
    return a/b

print(division(1, 0))

运行结果如下:

$ python test.py
Traceback (most recent call last):
  File "test.py", line 11, in <module>
    print(division(1, 0))
  File "test.py", line 8, in division
    return a/b
ZeroDivisionError: integer division or modulo by zero

加上better_exceptions后,其运行结果如下:

$ python test.py
Traceback (most recent call last):
  File "test.py", line 11, in <module>
    print(division(1, 0))
          └ <function division at 0x00000000036566D8>
  File "test.py", line 8, in division
    return a/b
           │ └ 0
           └ 1
ZeroDivisionError: integer division or modulo by zero

从上面的应用场景描述,以及最终呈现的结果来看,似乎是有用的。那么better_exceptions是怎么做到的呢?

看一下better_exceptions的源码:

# better_exceptions/__init__.py

...

def write_stream(data):
    if SHOULD_ENCODE:
        data = _byte(data)

        if PY3:
            STREAM.buffer.write(data)
        else:
            STREAM.write(data)
    else:
        STREAM.write(data)


def format_exception(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())

    return full_trace


def excepthook(exc, value, tb):
    formatted = format_exception(exc, value, tb)
    write_stream(formatted)


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对象

这意味着,你可以通过重写这个方法来处理系统未捕获的异常处理。但是对于DjangoTornado这样的Web框架,没啥用。为什么呢?

栗子2:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from django.http import HttpResponse
import better_exceptions


def test(request):
    print(request.GET['q'])
    if 'q' in request.GET:
        message = u'你搜索的内容为: ' + request.GET['q']
    else:
        message = u'你提交了空表单'
    return HttpResponse(message)

这是一个view方法,运行python manage.py runserver 0.0.0.0:8000后,我在浏览器中输入http://127.0.0.1:8000/test?a=111时,并没显示像栗子1中那样的exception提示。

这是因为框架中(DjangoTornado等)会自己处理异常,所以这种hook的方式不会被触发。

Tags