正确记录协程中的异常 Python3 + Tornado4.3 + 本机日志记录模块

Correct logging for exception in coroutine Python3 + Tornado4.3 + native logging module

在 Tornado 中,异常被封装在 Future 中,异步函数的调用者必须产生 Future 才能解压异常。如果我有很长的异步函数调用链,如何获取日志消息以正确打印出函数名称和发生异常的行?

例如,在下面的代码中:

FORMAT = '%(asctime)s - %(levelname)s - %(filename)s : %(lineno)s'\
         ' - %(funcName)20s() - %(message)s\n'

logging.basicConfig(format=FORMAT)

...

@gen.coroutine
def layer_2():
    return(yield async_func())

@gen.coroutine 
def layer_1():
    return(yield layer_2())

@gen.coroutine
def main():
    try:
        yield layer_1()
    except Exception as e:
        logging.warning('Error: {}'.format(str(e))

如果 async_func() 中出现异常,日志消息 linenofuncNamemain() 的日志消息,而不是 async_func() 的消息。

除了 trycatch 每个 yield 语句,还有什么解决方案吗?谢谢!

编辑 1:我意识到我的问题的答案可能与龙卷风无关,但我只是把它包括在这里,因为这是我的情况。

我看到您正在使用 Python 3,并且在 Python 3 中,Futures 的异常具有自动可用的回溯。使用 logging.exception 打印它:

@gen.coroutine
def main():
    try:
        yield layer_1()
    except Exception as e:
        logging.exception(e)

logging 没有什么神秘的事情发生。您可以自己打印回溯:

@gen.coroutine
def main():
    try:
        yield layer_1()
    except Exception as e:
        import traceback
        traceback.print_exc()

与来自常规函数调用堆栈的回溯相比,此回溯中有一些额外的内容:对 gen.Runner.run 的调用,这是 Tornado 协程实现的一部分。但它达到了它的目的:你可以看到 main 在堆栈的顶部,而 async_func 在底部。

这在 Python 3 中有效,因为每个异常都附加了一个回溯,并且 Tornado 在异常处理期间展开堆栈时正确地将协程的回溯附加到异常。 For some history, see the docs on Future.

最后,如果您将 gen.coroutine 替换为 async def,您将获得由 Python 3.5 解释器本身实现的清晰回溯。