【问题标题】:Ignore and log error with contextlib contextmanager使用 contextlib contextmanager 忽略并记录错误
【发布时间】:2018-10-18 10:11:16
【问题描述】:

我希望上下文管理器捕获异常,打印堆栈跟踪,然后允许继续执行。

我想知道我是否可以使用 contextlib contextmanager 装饰器来做到这一点。如果没有,我该怎么办?

文档建议如下:

在生成器产生的地方,嵌套在 with 语句中的块被执行。然后在退出块后恢复生成器。如果块中发生未处理的异常,它会在生成器中在产生产生的点重新引发。因此,您可以使用 try...except...finally 语句来捕获错误(如果有),或确保进行一些清理。如果捕获异常只是为了记录它或执行某些操作(而不是完全抑制它),则生成器必须重新引发该异常。

所以我尝试了文档引导我采用的明显方法:

import contextlib
import logging


@contextlib.contextmanager
def log_error():
    try:
        yield
    except Exception as e:
        logging.exception('hit exception')
    finally:
        print 'done with contextmanager'


def something_inside_django_app():
    with log_error():
        raise Exception('alan!')


something_inside_django_app()


print 'next block of code'

这会产生输出

ERROR:root:hit exception
Traceback (most recent call last):
  File "exception_test.py", line 8, in log_error
    yield
  File "exception_test.py", line 17, in something_inside_django_app
    raise Exception('alan!')
Exception: alan!
done with contextmanager
next block of code

这会丢失有关异常从何处引发的关键信息。考虑将上下文管理器调整为抑制异常时得到的结果:

Traceback (most recent call last):
  File "exception_test.py", line 20, in <module>
    something_inside_django_app()
  File "exception_test.py", line 17, in something_inside_django_app
    raise Exception('alan!')
Exception: alan!

是的,它能够告诉我异常是从第 17 行引发的,非常感谢,但是第 20 行的先前调用丢失了信息。如何让上下文管理器给我 actual full 调用堆栈,而不是它的截断版本?回顾一下,我想满足两个要求:

  • 有一个 python 上下文管理器抑制它包装的代码中引发的异常
  • 如果我没有使用上下文管理器,打印该代码将生成的堆栈跟踪

如果使用装饰器无法做到这一点,那么我将使用其他样式的上下文管理器来代替。如果这不能通过上下文管理器完成,我想知道一个好的 Python 替代方案是什么。

【问题讨论】:

标签: python exception-handling contextmanager


【解决方案1】:

我在这里更新了我的解决方案:

https://gist.github.com/AlanCoding/288ee96b60e24c1f2cca47326e2c0af1

问题遗漏了更多上下文。为了在异常点获得完整的堆栈,我们既需要返回到上下文管理器的回溯,也需要当前上下文。然后我们可以将栈顶和栈底粘在一起。

为了更好地说明用例,请考虑以下内容:

def err_method1():
    print [1, 2][4]


def err_method2():
    err_method1()


def outside_method1():
    with log_error():
        err_method2()


def outside_method2():
    outside_method1()

outside_method2()

要真正完成这个问题所要寻找的,我们希望在调用堆栈中同时查看外部方法和内部方法。

这是一个似乎确实适用的解决方案:

class log_error(object):

    def __enter__(self):
        return

    def __exit__(self, exc_type, exc_value, exc_traceback):
        if exc_value:
            # We want the _full_ traceback with the context, so first we
            # get context for the current stack, and delete the last 2
            # layers of context, saying that we're in the __exit__ method...
            top_stack = StringIO.StringIO()
            tb.print_stack(file=top_stack)
            top_lines = top_stack.getvalue().strip('\n').split('\n')[:-4]
            top_stack.close()
            # Now, we glue that stack to the stack from the local error
            # that happened within the context manager
            full_stack = StringIO.StringIO()
            full_stack.write('Traceback (most recent call last):\n')
            full_stack.write('\n'.join(top_lines))
            full_stack.write('\n')
            tb.print_tb(exc_traceback, file=full_stack)
            full_stack.write('{}: {}'.format(exc_type.__name__, str(exc_value)))
            sinfo = full_stack.getvalue()
            full_stack.close()
            # Log the combined stack
            logging.error('Log message\n{}'.format(sinfo))
        return True

回溯看起来像:

ERROR:root:Log message
Traceback (most recent call last):
  File "exception_test.py", line 71, in <module>
    outside_method2()
  File "exception_test.py", line 69, in outside_method2
    outside_method1()
  File "exception_test.py", line 65, in outside_method1
    err_method2()
  File "exception_test.py", line 60, in err_method2
    err_method1()
  File "exception_test.py", line 56, in err_method1
    print [1, 2][4]
IndexError: list index out of range

这与您在 try-except 中对包装在上下文管理器中的相同代码执行 logging.exception 所期望的信息相同。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-09-22
    • 1970-01-01
    • 2013-05-21
    • 1970-01-01
    • 2017-12-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多