我希望上下文管理器捕获异常,打印堆栈跟踪,然后允许执行继续。
我想知道我是否可以使用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行的先前电话是丢失的信息。如何让上下文管理器为我提供实际的 完整调用堆栈,而不是它的截断版本?总结一下,我想满足两个要求:
如果装饰器无法做到这一点,那么我将使用其他风格的上下文管理器。如果上下文管理器不能做到这一点,那么我想知道什么是好的pythonic替代方案。
答案 0 :(得分:0)
我已经在这里更新了我的解决方案:
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中执行logging.exception
时所期望的信息相同 - 除了您在上下文管理器中包装的相同代码。