当前位置: 首页 > news >正文

Python多进程Logging

多个进程的 logging 向同一个 .log 文件写入是一套Python程序被多次启动时(多进程启动)无法回避的问题。

一个进程的程序正在向.log文件写入的同时,另一个进行启动的程序也需要向同一个.log文件写入,会产生异常吗?答案是:会的!

直接写入存在的问题

如果多个进程直接使用 Python 的 logging 模块向同一个文件写入日志,可能会出现日志内容混乱数据丢失等问题。这是因为多个进程同时访问和修改文件时,可能会导致文件指针冲突、写入内容覆盖等情况。

当然,多进程同时写入某一个.log是可以实现的,但需要考虑一些问题并采用合适的方法,以下为你详细介绍不同的实现方式及各自的优缺点。


解决方法

1. 使用 FileHandler 加锁(不推荐)

Python 的 logging.FileHandler 本身并不支持多进程安全写入。虽然可以手动加锁来保证同一时间只有一个进程可以写入文件,但实现起来比较复杂,且性能可能会受到影响。 

import logging
import threading

# 创建一个锁对象
lock = threading.Lock()

class MultiProcessFileHandler(logging.FileHandler):
    def emit(self, record):
        try:
            with lock:
                super().emit(record)
        except Exception:
            self.handleError(record)

# 配置日志
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
handler = MultiProcessFileHandler('example.log')
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)

# 模拟多进程写入日志
def log_message():
    for i in range(5):
        logger.info(f"Message {i} from process")

if __name__ == "__main__":
    import multiprocessing
    processes = []
    for _ in range(3):
        p = multiprocessing.Process(target=log_message)
        processes.append(p)
        p.start()

    for p in processes:
        p.join()

缺点:这种方法是笔者最不推荐的方法!它使用线程锁,对于多进程并不适用,因为每个进程都有自己独立的内存空间,线程锁无法在进程间共享 !

2. 使用 ConcurrentLogHandler(推荐)

ConcurrentLogHandler 是一个第三方库 concurrent-log-handler 提供的处理程序,它专门用于处理多进程并发写入日志文件的问题。

安装

pip install concurrent-log-handler

示例代码

import logging
from concurrent_log_handler import ConcurrentRotatingFileHandler

# 配置日志
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
handler = ConcurrentRotatingFileHandler('example.log', maxBytes=1024*1024, backupCount=5)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)

# 模拟多进程写入日志
def log_message():
    for i in range(5):
        logger.info(f"Message {i} from process")

if __name__ == "__main__":
    import multiprocessing
    processes = []
    for _ in range(3):
        p = multiprocessing.Process(target=log_message)
        processes.append(p)
        p.start()

    for p in processes:
        p.join()

 ConcurrentRotatingFileHandler 与 RotatingFileHandler的使用方法一致。注:在应用后会产生._XXX.lock(其中XXX位置是您的log日志的name),这实属正常,说明ConcurrentRotatingFileHandler 为了解决多进程的访问,不可避免的引入了“锁”的机制,只是此锁非彼锁。

优点ConcurrentRotatingFileHandler 可以自动处理多进程并发写入的问题,保证日志内容不会混乱,同时还支持日志文件的轮转功能。

3. 使用消息队列(如 Redis、RabbitMQ)

如果您在实际应用中,因为同时爆发的超巨量日志,而导致应用变慢,那么可以考虑建立一套真正的大型日志系统了。方法如下:

可以让每个进程将日志消息发送到消息队列中,然后由一个单独的进程从消息队列中读取消息并写入日志文件。

示例代码(使用 Redis 作为消息队列)

import logging
import redis
import multiprocessing

# 生产者进程:将日志消息发送到 Redis 队列
def producer():
    r = redis.Redis()
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    for i in range(5):
        message = f"Message {i} from process"
        r.rpush('log_queue', message)
        logger.info(message)

# 消费者进程:从 Redis 队列中读取消息并写入日志文件
def consumer():
    r = redis.Redis()
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    handler = logging.FileHandler('example.log')
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    while True:
        message = r.lpop('log_queue')
        if message is None:
            break
        logger.info(message.decode())

if __name__ == "__main__":
    producers = []
    for _ in range(3):
        p = multiprocessing.Process(target=producer)
        producers.append(p)
        p.start()

    for p in producers:
        p.join()

    c = multiprocessing.Process(target=consumer)
    c.start()
    c.join()

优点:这种方法将日志写入操作集中到一个进程中,避免了多进程直接写入文件的冲突问题,同时可以实现日志的异步处理,提高性能。缺点是需要额外的依赖(如 Redis),增加了系统的复杂度。


总结:

如果您的Python程序可能存在多次被同时运行的情况,也就是多进程同时运行您的程序的情况,为保证程序日志输出的正确性和稳定性,应该使用 ConcurrentLogHandler代替普通的FileHandler。

相关文章:

  • 金融风控项目-业务基础
  • carbon 加入 GitCode:Golang 时间处理的 “瑞士军刀”
  • C语言基础学习之基本语法
  • C++智能指针的使用
  • 服务器绑定 127.0.0.1 和 0.0.0.0 的区别
  • 深入理解 CSS 层叠上下文
  • 只需三步!5分钟本地部署deep seek——MAC环境
  • linux ollama deepseek等大语言模型的model文件的存储目录
  • 【Mastering Vim 2_01】开篇词:在 AI 时代持续深耕底层技术,做长期主义的坚定捍卫者
  • 【每日关注】科技圈重要动态
  • ArrayList、LinkedList、HashMap、HashTable、HashSet、TreeSet
  • 得物端智能视频封面推荐
  • WebSocket与Socket.io的区别
  • 将Docker容器打包成镜像提交
  • 评估多智能体协作网络(MACNET)的性能:COT和AUTOGPT基线方法
  • 今日学习总结
  • 【面试集锦】如何设计SSO方案?和OAuth有什么区别?
  • Open FPV VTX开源之OSD使用分类
  • 腿足机器人之二- 运动控制概览
  • Java NIO ByteBuffer 详解
  • 电商平台设计电商网站建设/产品营销软文
  • 怎么找做企业网站的/南京百度seo代理
  • 巴音郭楞库尔勒网站建设/seo收费低
  • 吴忠建设网站/快速刷排名seo软件
  • wordpress swf插件/九江seo
  • 网站权重收录/html家乡网站设计