python - Async exception handling works when using ensure_future but not when using run_until_complete
问题描述
I am using aiologger for async logging, and have written two functions to override default exception handlers:
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'])
Then, I overrode the exception handlers with the ones I have provided:
import sys
sys.excepthook = excepthook
asyncio.get_event_loop().set_exception_handler(asyncio_exception_handler)
Finally, I wrote a simple code to test the functionality:
async def main():
raise RuntimeError('Uncaught Test')
loop = asyncio.get_event_loop()
asyncio.ensure_future(main())
loop.run_forever()
This works as expected, and the output is:
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
(The process remains open after the exception, and I have to send KeyboardInterrupt
to terminate it.)
However, if I replace asyncio.ensure_future(main())
with loop.run_until_complete(main())
, everything goes crazy and the application exits without any logs:
$ python main.py
excepthook called.
$
The confusing part is, in this case, my excepthook
function is executed instead of asyncio_exception_handler
. My perception is that somehow using loop.run_until_complete()
will treat the code to be non-async, hence calling logger.error()
which creates an async task, has no effects.
How can I manage to use my exception handlers to log uncaught exceptions when my code is running with loop.run_until_complete()
? What is the difference between the two scenarios I have provided? I am not so good with asyncio
, and I may have missed some trivial notes here.
解决方案
AioLogger
是一个依赖事件循环运行的异步日志框架。当您从 引发时ensure_future
,您的事件循环仍在运行,直到您按下Ctrl+C,这就是您看到日志的原因。另一方面,当你使用 时run_until_complete(main())
,在 raises 之后什么都不会运行事件循环run_until_complete
,因此调度的日志消息excepthook
将被丢弃。
要解决此问题,您可以asyncio.sleep(0.001)
在记录器调用后运行类似的操作,excepthook
以确保日志消息通过:
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()
获取异常。如果你什么都不做,只是让未来被 GC 销毁,它会将异常传递给 asyncio 处理程序以记录它。
推荐阅读
- javascript - 使用 ASP Classic/Javascript 导出到 Excel
- fiware - httpCustom 有效负载中没有替换
- django - 将 django.rest_framework.serializers.ModelSerializer 扩展到模型属性字段?
- database - 为什么 RDS 突发平衡看似随机,从不低于 99%,并且与系统流量无关?
- forms - 引导程序中有用的焦点覆盖
- react-native - 如何在 react-native 的 Image 组件中显示本地 SVG 图像
- python - 从 Pandas DataFrame 创建 NetowrkX Graph
- html - 标签属性内的字符数限制
- reactjs - 错误处理后如何刷新反应页面
- python - Django:将 URL 参数传递给模板标签中反向 URL 查找的每个实例