异步异常处理在使用 ensure_future 时有效,但在使用 run_until_complete 时无效
Async exception handling works when using ensure_future but not when using run_until_complete
我正在使用 aiologger 进行异步日志记录,并编写了两个函数来覆盖默认异常处理程序:
from aiologger import Logger as AioLogger
from aiologger.levels import LogLevel
import asyncio
logger = AioLogger.with_default_handlers(name='test', level=LogLevel.NOTSET,
loop=asyncio.get_event_loop())
def excepthook(exc_type, exc_value, exc_traceback):
print('excepthook called.')
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
logger.error(f'Uncaught Exception: {exc_type.__name__}')
def asyncio_exception_handler(loop, context):
print('asyncio_exception_handler called.')
exception = context.get('exception', None)
if exception:
exc_info = (type(exception), exception, exception.__traceback__)
if issubclass(exception.__class__, KeyboardInterrupt):
sys.__excepthook__(*exc_info)
return
logger.error(f'Uncaught Exception: {exc_info[0].__name__}')
else:
logger.error(context['message'])
然后,我用我提供的异常处理程序覆盖了异常处理程序:
import sys
sys.excepthook = excepthook
asyncio.get_event_loop().set_exception_handler(asyncio_exception_handler)
最后,我写了一个简单的代码来测试功能:
async def main():
raise RuntimeError('Uncaught Test')
loop = asyncio.get_event_loop()
asyncio.ensure_future(main())
loop.run_forever()
这按预期工作,输出为:
asyncio_exception_handler called.
Uncaught Exception: RuntimeError
^Cexcepthook called.
Traceback (most recent call last):
File "examples/sample.py", line 110, in <module>
loop.run_forever()
File "/usr/lib/python3.8/asyncio/base_events.py", line 570, in run_forever
self._run_once()
File "/usr/lib/python3.8/asyncio/base_events.py", line 1823, in _run_once
event_list = self._selector.select(timeout)
File "/usr/lib/python3.8/selectors.py", line 468, in select
fd_event_list = self._selector.poll(timeout, max_ev)
KeyboardInterrupt
(异常后进程保持打开状态,我必须发送KeyboardInterrupt
终止它。)
但是,如果我将 asyncio.ensure_future(main())
替换为 loop.run_until_complete(main())
,一切都会变得疯狂,应用程序会在没有任何日志的情况下退出:
$ python main.py
excepthook called.
$
令人困惑的部分是,在这种情况下,我的 excepthook
函数被执行而不是 asyncio_exception_handler
。我的看法是,以某种方式使用 loop.run_until_complete()
会将代码视为非异步,因此调用 logger.error()
创建异步任务没有任何效果。
当我的代码是 运行 loop.run_until_complete()
时,我如何设法使用我的异常处理程序来记录未捕获的异常?我提供的两个场景有什么区别?我对 asyncio
不是很好,我可能在这里遗漏了一些琐碎的笔记。
AioLogger
是一个异步日志记录框架,它依赖于 运行 的事件循环。当您从 ensure_future
引发时,您的事件循环仍然是 运行ning,直到您按下 Ctrl+C,这就是您看到日志的原因。另一方面,当您使用 run_until_complete(main())
时,run_until_complete
之后的事件循环不会 运行 引发任何事件,因此 excepthook
安排的日志消息将被删除。
要解决此问题,您可以在记录器调用 excepthook
后 运行 类似 asyncio.sleep(0.001)
的内容以确保日志消息通过:
def excepthook(exc_type, exc_value, exc_traceback):
print('excepthook called.')
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
logger.error(f'Uncaught Exception: {exc_type.__name__}')
# sleep a bit to give the log a chance to flush
loop.run_until_complete(asyncio.sleep(0.001))
一般来说,asyncio 异常处理程序是最后的错误报告手段,而不是您的业务逻辑应该依赖的东西。当您调用 run_until_complete()
时,没有理由将异常传递给 asyncio 异常处理程序,因为异常会传播到 run_until_complete()
的调用者,后者可以按通常的方式处理它。 (在你的例子中,调用 sys.excepthook
是因为错误传播到顶层。)当你调用 ensure_future
时,你会得到一个 Future
实例(或其 Task
子类的实例) 您可以 await
或调用 result()
或 exception()
来获取异常。如果你这样做 none 并且只允许未来被 GC 销毁,它会将异常传递给异步处理程序以记录它。
我正在使用 aiologger 进行异步日志记录,并编写了两个函数来覆盖默认异常处理程序:
from aiologger import Logger as AioLogger
from aiologger.levels import LogLevel
import asyncio
logger = AioLogger.with_default_handlers(name='test', level=LogLevel.NOTSET,
loop=asyncio.get_event_loop())
def excepthook(exc_type, exc_value, exc_traceback):
print('excepthook called.')
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
logger.error(f'Uncaught Exception: {exc_type.__name__}')
def asyncio_exception_handler(loop, context):
print('asyncio_exception_handler called.')
exception = context.get('exception', None)
if exception:
exc_info = (type(exception), exception, exception.__traceback__)
if issubclass(exception.__class__, KeyboardInterrupt):
sys.__excepthook__(*exc_info)
return
logger.error(f'Uncaught Exception: {exc_info[0].__name__}')
else:
logger.error(context['message'])
然后,我用我提供的异常处理程序覆盖了异常处理程序:
import sys
sys.excepthook = excepthook
asyncio.get_event_loop().set_exception_handler(asyncio_exception_handler)
最后,我写了一个简单的代码来测试功能:
async def main():
raise RuntimeError('Uncaught Test')
loop = asyncio.get_event_loop()
asyncio.ensure_future(main())
loop.run_forever()
这按预期工作,输出为:
asyncio_exception_handler called.
Uncaught Exception: RuntimeError
^Cexcepthook called.
Traceback (most recent call last):
File "examples/sample.py", line 110, in <module>
loop.run_forever()
File "/usr/lib/python3.8/asyncio/base_events.py", line 570, in run_forever
self._run_once()
File "/usr/lib/python3.8/asyncio/base_events.py", line 1823, in _run_once
event_list = self._selector.select(timeout)
File "/usr/lib/python3.8/selectors.py", line 468, in select
fd_event_list = self._selector.poll(timeout, max_ev)
KeyboardInterrupt
(异常后进程保持打开状态,我必须发送KeyboardInterrupt
终止它。)
但是,如果我将 asyncio.ensure_future(main())
替换为 loop.run_until_complete(main())
,一切都会变得疯狂,应用程序会在没有任何日志的情况下退出:
$ python main.py
excepthook called.
$
令人困惑的部分是,在这种情况下,我的 excepthook
函数被执行而不是 asyncio_exception_handler
。我的看法是,以某种方式使用 loop.run_until_complete()
会将代码视为非异步,因此调用 logger.error()
创建异步任务没有任何效果。
当我的代码是 运行 loop.run_until_complete()
时,我如何设法使用我的异常处理程序来记录未捕获的异常?我提供的两个场景有什么区别?我对 asyncio
不是很好,我可能在这里遗漏了一些琐碎的笔记。
AioLogger
是一个异步日志记录框架,它依赖于 运行 的事件循环。当您从 ensure_future
引发时,您的事件循环仍然是 运行ning,直到您按下 Ctrl+C,这就是您看到日志的原因。另一方面,当您使用 run_until_complete(main())
时,run_until_complete
之后的事件循环不会 运行 引发任何事件,因此 excepthook
安排的日志消息将被删除。
要解决此问题,您可以在记录器调用 excepthook
后 运行 类似 asyncio.sleep(0.001)
的内容以确保日志消息通过:
def excepthook(exc_type, exc_value, exc_traceback):
print('excepthook called.')
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
logger.error(f'Uncaught Exception: {exc_type.__name__}')
# sleep a bit to give the log a chance to flush
loop.run_until_complete(asyncio.sleep(0.001))
一般来说,asyncio 异常处理程序是最后的错误报告手段,而不是您的业务逻辑应该依赖的东西。当您调用 run_until_complete()
时,没有理由将异常传递给 asyncio 异常处理程序,因为异常会传播到 run_until_complete()
的调用者,后者可以按通常的方式处理它。 (在你的例子中,调用 sys.excepthook
是因为错误传播到顶层。)当你调用 ensure_future
时,你会得到一个 Future
实例(或其 Task
子类的实例) 您可以 await
或调用 result()
或 exception()
来获取异常。如果你这样做 none 并且只允许未来被 GC 销毁,它会将异常传递给异步处理程序以记录它。