V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
510908220
V2EX  ›  Python

加速 Python 异常处理

  •  2
     
  •   510908220 ·
    510908220 · 2018-04-19 12:37:40 +08:00 · 3964 次点击
    这是一个创建于 2417 天前的主题,其中的信息可能已经有所发展或是发生改变。

    better-exceptions使用简介

    介绍这个库前,我们先看一个简单的例子(example.py),代码如下:

    # -*- encoding: utf-8 -*-
    
    def get_student_infos(logs):
        student_infos = []
        for log in logs:
            name, catgory, grade = log.split(' ')
            student_infos.append({
                'name': name,
                'catgory': catgory,
                'grade': grade,
    
            })
        return student_infos
    
    if __name__ == '__main__':
        exam_logs = [
            'zhangsan math 60',
            'lisi english 80',
            'wangwu chinese 90',
            'qianliu music'
        ]
        get_student_infos(exam_logs)
    

    运行输出:

    Traceback (most recent call last):
      File "example.py", line 24, in <module>
        get_student_infos(exam_logs)
      File "example.py", line 7, in get_student_infos
        name, catgory, grade = log.split(' ')
    ValueError: not enough values to unpack (expected 3, got 2)
    

    如果我们给example.py增加一行代码import better_exceptions,使用前需要执行:

    • pip install better_exceptions

    • export BETTER_EXCEPTIONS=1  # Linux / OSX
      setx BETTER_EXCEPTIONS 1    # Windows
      最好是加入到系统环境变量里
      或者
      import better_exceptions; 
      better_exceptions.hook()
      

    再执行,输入如下:

    Traceback (most recent call last):
      File "example_with_better_exceptions.py", line 25, in <module>
        get_student_infos(exam_logs)
        │                 └ ['zhangsan math 60', 'lisi english 80', 'wangwu chinese 90', 'qianliu music']
        └ <function get_student_infos at 0x7fa594d1fe18>
      File "example_with_better_exceptions.py", line 8, in get_student_infos
        name, catgory, grade = log.split(' ')
        │     │        │       └ 'qianliu music'
        │     │        └ '90'
        │     └ 'chinese'
        └ 'wangwu'
    ValueError: not enough values to unpack (expected 3, got 2)
    
    

    看到了吗,加上import better_exceptions后, 异常时会将调用栈每一层用的变量值打印出来, 和普通异常时输出有比有什么好处呢,然我们来回忆一下,

    • 没有better_exceptions时是这样对待异常的:
      • 看到一个异常, 能看到异常的类型, 但是无法直接给出错误原因
      • 追踪异常时, 往往得修改代码,将关键变量打印出来. 如果是线上环境, 貌似没什么好办法,只能上线上改一改.
    • better_exceptions时:
      • 从异常时打印出来的信息可以清晰的看到异常发生时变量的值,可很容易定位问题.

    是不是有小伙伴会想, 要是某些变量特别大(比如有几万个元素的列表),这样会造成日志很大的.确实存在这样的问题,不过这个库的作者已经够给解决方案了: 加上这句better_exceptions.MAX_LENGTH = 字符数控制字符的个数, 对于上面的例子加上better_exceptions.MAX_LENGTH = 20这句输出如下:

    Traceback (most recent call last):
      File "example_with_better_exceptions_limit_length.py", line 25, in <module>
        get_student_infos(exam_logs)
        │                 └ ['zha...
        └ <func...
      File "example_with_better_exceptions_limit_length.py", line 8, in get_student_infos
        name, catgory, grade = log.split(' ')
        │     │        │       └ 'qian...
        │     │        └ '90'
        │     └ 'chin...
        └ 'wang...
    ValueError: not enough values to unpack (expected 3, got 2)
    
    

    看到了吗,只是简单的再代码上加上一句import better_exceptions就有如此神奇的效果. 但是, 这个库目前只会在控制台打印出这样的错误信息, 下面说一下怎么与 logging、django 集成.

    logging 接入

    先简单说一下,:

    • logging 模块在输出一个异常的时候,会调用handlerformaterformatException函数来格式化异常.
    • better_exceptions有个format_exception方法会将异常调用栈变量值输出,就是 上面例子的那种输出.

    看代码:

    # -*- encoding: utf-8 -*-
    import logging
    from better_exceptions import format_exception
    
    logger = logging.getLogger()
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    formatter.formatException = lambda exc_info: format_exception(*exc_info)
    
    file_handler = logging.FileHandler("example.log")
    file_handler.setLevel(logging.DEBUG)
    file_handler.setFormatter(formatter)
    logger.addHandler(file_handler)
    def get_student_infos(logs):
        student_infos = []
        for log in logs:
            name, catgory, grade = log.split(' ')
            student_infos.append({
                'name': name,
                'catgory': catgory,
                'grade': grade,
    
            })
        return student_infos
    
    if __name__ == '__main__':
        exam_logs = [
            'zhangsan math 60',
            'lisi english 80',
            'wangwu chinese 90',
            'qianliu music'
        ]
        try:
            get_student_infos(exam_logs)
        except Exception as e:
            logger.exception(e)
    

    查看example.log文件输出:

    2018-04-18 14:12:17,751 - root - ERROR - not enough values to unpack (expected 3, got 2)
    Traceback (most recent call last):
      File "better_exceptions_with_logging.py", line 36, in <module>
        get_student_infos(exam_logs)
        │                 └ ['zhangsan math 60', 'lisi english 80', 'wangwu chinese 90', 'qianliu music']
        └ <function get_student_infos at 0x7f5c28d088c8>
      File "better_exceptions_with_logging.py", line 18, in get_student_infos
        name, catgory, grade = log.split(' ')
        │     │        │       └ 'qianliu music'
        │     │        └ '90'
        │     └ 'chinese'
        └ 'wangwu'
    ValueError: not enough values to unpack (expected 3, got 2)
    
    

    django 接入

    思路和logging 接入一样的. 例如有如下django项目:

    ☁  test_better_exceptions_django  tree
    .
    ├── db.sqlite3
    ├── manage.py
    └── test_better_exceptions_django
        ├── fm.py
        ├── __init__.py
        ├── settings.py
        ├── urls.py
        ├── views.py
        └── wsgi.py
    

    两处修改:

    • 增加一个fm.py文件:

      import logging
      from better_exceptions import format_exception
      class ExceptionFormatter(logging.Formatter):
          def formatException(self, ei):
              return format_exception(*ei)
      
    • 修改settings.py的 LOGGING 配置:

      LOGGING = {
          'version': 1,
          'disable_existing_loggers': True,
          'formatters': {
              'simple': {
                  '()': 'test_better_exceptions_django.fm.ExceptionFormatter',
                  'format': '%(levelname)s %(message)s'
              },
          },
          'handlers': {
              'console': {
                  'level': 'INFO',
                  'class': 'logging.StreamHandler',
                  'formatter': 'simple'
              },
              'file': {
                  'level': 'ERROR',
                  'class': 'logging.FileHandler',
                  'filename': os.path.join(BASE_DIR, "error.log"),
                  'formatter': 'simple'
      
              }
          },
          'loggers': {
              'django': {
                  'handlers': ['console', 'file'],
      
              }
          }
      }
      

      这里主要是自定义了一个formatters.

    我是在请求里故意出发了一个异常,代码如下:

    • urls.py:

      from django.conf.urls import url
      from django.contrib import admin
      from . import views
      
      urlpatterns = [
          url(r'^$', views.index, name='index'),
      ]
      
      
    • views.py:

      from django.http import HttpResponse
      import logging
      logger = logging.getLogger('django')
      def index(request):
          a, b = 2, 0
          try:
              a / b
          except Exception as e:
              logger.exception(e)
          return HttpResponse("You have an excepion, see console and log")
      

    接着打开浏览器,可以看到根目录下error.log文件内容:

    Traceback (most recent call last):
      File "/opt/github/better-exceptions-example/test_better_exceptions_django/test_better_exceptions_django/views.py", line 7, in index
        a / b
        │   └ 0
        └ 2
    ZeroDivisionError: division by zero
    

    说明

    所有的例子都在examples目录下. 可以在这里查看 https://github.com/510908220/better-exceptions-examples

    17 条回复    2018-05-14 23:17:25 +08:00
    kevindu
        1
    kevindu  
       2018-04-19 12:54:16 +08:00
    好玩
    ant2017
        2
    ant2017  
       2018-04-19 12:57:48 +08:00 via Android
    nice
    junnplus
        3
    junnplus  
       2018-04-19 13:08:35 +08:00
    道理我都懂,为什么标题叫 加速 Python 异常处理
    510908220
        4
    510908220  
    OP
       2018-04-19 13:18:07 +08:00
    @junnplus 用普通方式,当出现一个异常,缺少上下文(各个变量值等)去排查不是很费时吗. 如果集成了类似这样的方式,遇到异常根据上下文基本上可以直接定位问题. 不是节约了很多时间吗
    Mistwave
        5
    Mistwave  
       2018-04-19 13:19:56 +08:00 via iPhone
    不错
    nooper
        6
    nooper  
       2018-04-19 13:26:49 +08:00
    我还以为作者写了 better exception 这个库
    doubleflower
        7
    doubleflower  
       2018-04-19 14:13:28 +08:00
    啥,不是作者本人?
    seerhut
        8
    seerhut  
       2018-04-19 14:25:49 +08:00
    看标题我还以为是加速抛异常。。原来是加速看异常
    Leigg
        9
    Leigg  
       2018-04-19 14:54:52 +08:00 via iPhone
    可以的,不过标题党了
    est
        10
    est  
       2018-04-19 18:06:45 +08:00
    不错不错。

    不过我是推荐 python -m pdb 一把梭
    Dillion
        11
    Dillion  
       2018-04-19 18:09:32 +08:00
    哈哈 我看标题也以为是加速异常处理速度
    Anybfans
        12
    Anybfans  
       2018-04-20 09:07:17 +08:00 via iPhone
    @est 你这方法适合脚步。有些 web 框架并不能这样运行吧?
    Yycreater
        13
    Yycreater  
       2018-04-24 17:43:07 +08:00
    难道,我的 django 是假的?还是,ta 自己生产错误?我是不添加环境变量的方式复制进去的啊
    ValueError: Unable to configure formatter 'simple': Cannot resolve 'test_better_exceptions_django.fm.ExceptionFormatter': No module named 'test_better_exceptions_django'
    510908220
        14
    510908220  
    OP
       2018-04-24 18:16:49 +08:00
    @Yycreater 你这是模块导入的有问题. 你可以把你的目录结构贴出来看看.
    zqguo
        15
    zqguo  
       2018-05-14 13:49:35 +08:00
    flask 里面怎么用?
    510908220
        16
    510908220  
    OP
       2018-05-14 22:14:57 +08:00 via iPhone
    @zqguo 一样的。日志 format 使用自定义类。这个只 logging 相关,实际和具 web 框架没多大关系
    zqguo
        17
    zqguo  
       2018-05-14 23:17:25 +08:00
    @510908220 好的,我试试看
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2846 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 09:04 · PVG 17:04 · LAX 01:04 · JFK 04:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.