当前位置: 代码网 > it编程>前端脚本>Python > python多进程日志以及分布式日志的实现方式

python多进程日志以及分布式日志的实现方式

2024年07月05日 Python 我要评论
python日志在多进程环境下的问题python日志模块logging支持多线程,但是在多进程下写入日志文件容易出现下面的问题:permissionerror: [winerror 32] 另一个程序

python日志在多进程环境下的问题

python日志模块logging支持多线程,但是在多进程下写入日志文件容易出现下面的问题:

permissionerror: [winerror 32] 另一个程序正在使用此文件,进程无法访问。

也就是日志文件被占用的情况,原因是多个进程的文件handler对日志文件进行操作产生的。

这个问题经常在timedrotatingfilehandler、rotatingfilehandler中出现。

解决办法

题主在网上搜集了各种解决上面问题的办法,基本以下面三个方向为主:

  • 安装第三方库提供的handler
  • 重写filehandler加全局锁
  • 使用队列将消息传递

但是三种方法各有小缺陷:

  • 第三方库很久无人维护,且支持的功能比较单一,无法满足生产环境的需求。
  • 轮转日志的时候由于全局锁的存在,其他子进程无法记录日志,有丢失日志的风险。
  • 使用多进程消息队列的缺点在于使用困难,如果是多模块编程,需要将全局队列传来传去,在大型项目中显得很麻烦。

经过对官网的研究 1,题主无意中找到了一种非常方便且高效的方法,并且经过一定的修改使这种方法可用于分布式日志,且支持多语言日志的处理。

唯一的不足是需要新学习一个zmq通信协议,但是这并不是问题,如果只是想要一个解决方案并立即投入使用,只需要按照下面的方法编写,无需关注zmq的相关知识。

基于zmq的分布式日志

实现思路

  • 通过zmq的多对一通信,将多个地方的日志发送到一个地方集中处理,从而实现分布式日志。
  • 这个方法不仅可以解决python分布日志的问题,还可以很好的兼容其他语言,比如项目中还有c、java,那么可以将它们中的日志也发送过来,一并处理。2

看到这很多人可能明白了,这个方法类似官网提供的sockethandler,但本方法其实是基于queuehandler实现的,有利于发挥zmq易用性、可插拔、并发性能好的优点。

代码实现

首先是集中处理日志的程序,也就是上面所说"多对一"中的一

import zmq
import logging
from logging import handlers

class zeromqsocketlistener(handlers.queuelistener):
    def __init__(self, uri="tcp://127.0.0.1:5555", *handlers,**kwargs):
        self.respect_handler_level = true     # handler日志等级启用,允许对handler设置setlevel,false则忽视级别
        self.ctx = kwargs.get('ctx') or zmq.context()
        socket = self.ctx.socket(zmq.sub)
        socket.bind(uri)
        socket.setsockopt_string(zmq.subscribe, '')     # 订阅所有主题
        super().__init__(socket, *handlers, respect_handler_level=self.respect_handler_level)

    def dequeue(self,block):
        msg = self.queue.recv_json()
        # print('111',msg)    # 测试用
        return logging.makelogrecord(msg)


def main_logger():
    # 日志集中处理区,在主程序中调用一次
    
    # handlers配置区,filter可选
    formatter = logging.formatter("%(name)s - %(asctime)s - %(levelname)s - %(module)s - %(funcname)s - %(message)s")
    console = logging.streamhandler()
    console.setlevel(logging.error)
    ch = handlers.timedrotatingfilehandler(r'logs\face.log',when='m',
                                           # backupcount=180,
                                           encoding='utf-8')
    ch.setlevel(logging.info)
    ch.setformatter(formatter)  # add formatter to ch
    
    # 设置监听的端口,并传递handlers
    loggerlistener = zeromqsocketlistener("tcp://127.0.0.1:5555",*(ch,console))
    loggerlistener.start()   # 开启一个子线程处理记录器监听
    
# 主进程调用一次,非阻塞
main_logger()

自此,日志集中处理就结束了,是不是很简单,而且需要注意,我们这里不需要用到root logger,因为zeromqsocketlistener会自动调用各种handlers将日志内容进行处理,想当于替代了logger的工作,所以也就没必要声明一个logger出来了。

更新:

这里的main_logger()是非阻塞,也就是下面还可以写其他代码,但是如果什么代码都没有,那么主进程就会直接退出,日志就收不到了。

如果接下来不需要做其他工作,那么请在main_logger()下方使用while true:time.sleep(0.5) 将主进程阻塞。

  • 需要重点关注通信地址"tcp://127.0.0.1:5555",因为其他地方的日志都会发送到这里来。

接下来是子进程中或者是你想记录日志的任何地方,比如在其他同事的电脑里

  • subprocess.py
import logging,zmq
from logging import handlers

# 我们需要的handler
class zeromqsockethandler(handlers.queuehandler):
    def __init__(self, uri="tcp://127.0.0.1:5555", socktype=zmq.pub, ctx=none):
        self.ctx = ctx or zmq.context()
        socket = self.ctx.socket(socktype)
        socket.connect(uri)
        super().__init__(socket)
    def enqueue(self, record):
        self.queue.send_json(record.__dict__)
    def close(self):
        self.queue.close()
        
# 创建远端日志
rmtlogger = logging.getlogger('sub_root_name')    ##
rmtlogger.setlevel(logging.info)     # 建议设置一下,有时候默认是warning级别
rmtlogger.propagate=false    # 不允许传递,日志传递到这里就发送到主进程中

# 配置handler
zmqhandler = zeromqsockethandler()
zmqhandler.setlevel(logging.info)
rmtlogger.addhandler(zmqhandler)

# if you have submodule
# import submodule  

# 记录日志
rmtlogger.info("这是一条遥远的日志")
  • 如果是多进程环境下,您大可直接将上面的代码直接开启到多个子进程中,并不会出现网络问题。

logger可以通过python日志的name系统进行传递,也就是说如果子进程中还有其他模块,可以通过日志传递系统将其他模块产生的日志传递过来,最后一并发送给监听器,就像下面:

  • submodule.py
# subprocess.py的子模块,如需测试注意调用
import logging
submoldulelogger = logging.getlogger(f'sub_root_name.modulename')

submoldulelogger.info("这是一条子模块日志")
# 这部分内容需要logging基础知识
  • 上面这条日志会传递给rmtlogger,通过rmtlogger发送到主进程。

在主进程中,设置了logging.formatter对象,可以将产生日志的名字打印出来,用于区分日志产生的位置。

多语言支持

由于zmq本身就支持多语言,比如你使用c语言或其他语言,只需要在代码中使用zmq将日志通过json发送过来,

python日志可以通过dict方法重建logger对象,具体可以打印上面代码中zeromqsocketlistener.dequeue中的msg进行摸索,实现起来还是比较简单的。

总结

本篇所提供的多进程日志解决方法的目的是尽可能少做配置和修改,保留原有编程习惯的同时兼顾了代码的易用性。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

参考资料:

python日志zmq的使用 ↩︎

zmq资料 ↩︎

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com