python 中日志异步发送到远程服务器( 四 )

这时代码执行崩溃了
C:Python37liblogging__init__.py:894: RuntimeWarning: coroutine 'CustomHandler.emit' was never awaited  self.emit(record)RuntimeWarning: Enable tracemalloc to get the object allocation traceback服务端也没有收到发送日志的请求 。
究其原因是由于emit方法中使用async with session.post 函数,它需要在一个使用async 修饰的函数里执行,所以修改emit函数,使用async来修饰,这里emit函数变成了异步的函数, 返回的是一个coroutine 对象,要想执行coroutine对象,需要使用await, 但是脚本里却没有在哪里调用 await emit() ,所以崩溃信息中显示coroutine 'CustomHandler.emit' was never awaited.
既然emit方法返回的是一个coroutine对象,那么我们将它放一个loop中执行
async def main():    await logger.debug("今天天气不错")    await logger.debug("是风和日丽的")loop = asyncio.get_event_loop()loop.run_until_complete(main())执行依然报错
raise TypeError('An asyncio.Future, a coroutine or an awaitable is '意思是需要的是一个coroutine,但是传进来的对象不是 。
这似乎就没有办法了 , 想要使用异步库来发送 , 但是却没有可以调用await的地方.
解决办法是有的,我们使用 asyncio.get_event_loop() 获取一个事件循环对象, 我们可以在这个对象上注册很多协程对象,这样当执行事件循环的时候,就是去执行注册在该事件循环上的协程, 我们通过一个小例子来看一下
import asyncio async def test(n):    while n > 0:        await asyncio.sleep(1)        print("test {}".format(n))        n -= 1    return n    async def test2(n):    while n >0:        await asyncio.sleep(1)        print("test2 {}".format(n))        n -= 1def stoploop(task):    print("执行结束, task n is {}".format(task.result()))    loop.stop()loop = asyncio.get_event_loop()task = loop.create_task(test(5))task2 = loop.create_task(test2(3))task.add_done_callback(stoploop)task2 = loop.create_task(test2(3))loop.run_forever()我们使用loop = asyncio.get_event_loop() 创建了一个事件循环对象loop, 并且在loop上创建了两个task, 并且给task1添加了一个回调函数,在task1它执行结束以后,将loop停掉.
注意看上面的代码,我们并没有在某处使用await来执行协程,而是通过将协程注册到某个事件循环对象上,然后调用该循环的run_forever() 函数,从而使该循环上的协程对象得以正常的执行.
上面得到的输出为
test 5test2 3test 4test2 2test 3test2 1test 2test 1执行结束, task n is 0可以看到,使用事件循环对象创建的task,在该循环执行run_forever() 以后就可以执行了.
如果不执行loop.run_forever() 函数,则注册在它上面的协程也不会执行
loop = asyncio.get_event_loop()task = loop.create_task(test(5))task.add_done_callback(stoploop)task2 = loop.create_task(test2(3))time.sleep(5)# loop.run_forever()上面的代码将loop.run_forever() 注释掉,换成time.sleep(5) 停5秒, 这时脚本不会有任何输出,在停了5秒以后就中止了.
回到之前的日志发送远程服务器的代码,我们可以使用aiohttp封装一个发送数据的函数, 然后在emit中将这个函数注册到全局的事件循环对象loop中,最后再执行loop.run_forever() .
loop = asyncio.get_event_loop()class CustomHandler(logging.Handler):    def __init__(self, host, uri, method="POST"):        logging.Handler.__init__(self)        self.url = "%s/%s" % (host, uri)        method = method.upper()        if method not in ["GET", "POST"]:            raise ValueError("method must be GET or POST")        self.method = method    # 使用aiohttp封装发送数据函数    async def submit(self, data):        timeout = aiohttp.ClientTimeout(total=6)        if self.method == "GET":            if self.url.find("?") >= 0:                sep = '&'            else:                sep = '?'            url = self.url + "%c%s" % (sep, urllib.parse.urlencode({"log": data}))            async with aiohttp.ClientSession(timeout=timeout) as session:                async with session.get(url) as resp:                    print(await resp.text())        else:            headers = {                "Content-type": "application/x-www-form-urlencoded",            }            async with aiohttp.ClientSession(timeout=timeout, headers=headers) as session:                async with session.post(self.url, data=https://www.isolves.com/it/cxkf/yy/Python/2020-09-25/{'log': data}) as resp: print(await resp.text()) return True def emit(self, record): msg = self.format(record) loop.create_task(self.submit(msg))# 添加一个httphandlerhttp_handler = CustomHandler(r"http://127.0.0.1:1987", 'api/log/get')http_handler.setLevel(logging.DEBUG)http_handler.setFormatter(fmt)logger.addHandler(http_handler)logger.debug("今天天气不错")logger.debug("是风和日丽的")loop.run_forever()


推荐阅读