记录异步调用的正确/常用方法

Posted

技术标签:

【中文标题】记录异步调用的正确/常用方法【英文标题】:A correct/common way to log async calls 【发布时间】:2017-04-13 14:08:08 【问题描述】:

假设我们有一个 IO 操作的异步调用,我们想要记录它。 最简单的方法如下所示:

async def f():
   logger.log('io was called')
   await call_some_io()

但是当我们运行 log() 函数时,我们显然会遇到这种情况,该函数会切换上下文并记录其他内容,并且仅在执行 call_some_io() 之后。

下一个方法看起来更健壮:

async def f():
   await call_some_io()
   logger.log('io was called')

我们正在等待 call_some_io() 并在它之后记录它。看起来在这种情况下我们有一致的调用。

但是还有第三种使用上下文管理器的方法:

async def f():
   with LoggingContext:
     await call_some_io()

这里的LoggingContext 是一些ContextManager,其中__exit__ 方法有一些日志调用。

所以问题是:哪种记录异步调用的方法最常见且最可靠?

【问题讨论】:

【参考方案1】:

所有方法都是稳健的。上下文管理器可能不那么常见,但仍然很强大。

但是,示例 #1 和 #2 具有不同的语义。第一条日志消息应该是about to call io 而不是io was called,因为还没有发出调用。

LoggingContext 示例对开发人员来说相当方便,但在执行顺序方面与示例 #2 相同。

【讨论】:

我只能为自己说话,除非该操作需要很长时间,否则我会在执行操作后记录消息。【参考方案2】:

在启动另一个协程的协程中使用日志记录是相对稳健的,因为正如您所注意到的,它可能会命中上下文切换。实际上只有两种情况:等待之前和之后以及几种可能的方法:登录协程、日志装饰器和日志上下文管理器。我相信这不是一份详尽的清单。

在 io 启动前登录协程以进行日志记录

async def coro():
   logger.log('Before io start')
   await call_some_io()

在 io 启动之前记录的装饰器

def logger(f):
    def warp(*arguments, **kwarguments):
        logging.log("Before io start")
        f(*arguments. **kwarguments)
    return wrap

@logger
async def coro():
    # Async logic...

在 io 启动之前记录的日志上下文管理器

class Logger(object):
     def __enter__(self):
        logging.log("Before io start")

     # Empty __exit__ ...

with Logger():
     await coro()

一个更棘手的装饰器示例,它在 io 调用后记录。

def logger(f):          
    async def wrapper():
        await f()       
        logging.log("After io start") 
    return wrapper

装饰器的包装器也必须是协程才能在 io 启动后记录异步调用。使用异步记录器记录异步调用绝对不可靠;)

in-coro 和上下文管理器方法很容易在调用后转换为日志记录,因此我将跳过示例。

所有这些示例都同样常见,因为 Python 非常具有艺术性,并且没有像 Java 那样的编程模式。所以所有的方法对我来说都像是 Pythonic 的方式。

【讨论】:

以上是关于记录异步调用的正确/常用方法的主要内容,如果未能解决你的问题,请参考以下文章

在异步调用之前更新状态的正确方法

如何正确调用 Parallel.ForEach 循环中的调用异步方法[重复]

调用两个异步操作的正确方法

从同步代码调用异步方法并阻塞直到任务完成的正确方法是啥? [复制]

生产环境项目问题记录系列:同步方法调用异步方法

调用异步的方法,单独得到异步的数据的解决办法