Python 多进程日志输出到同一个文件并实现日志回滚

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Python 多进程想要实现将日志输出到同一个文件中,使用同一个日志句柄,且日志需要按照日期,大小回滚。


Python 多进程想要实现将日志输出到同一个文件中,使用同一个日志句柄,且日志需要按照日期,大小回滚。

出现问题

使用 Python 自带的有回滚功能的日志库 TimedRotatingFileHandler 日志并没有正常打印:在日志回滚时多个进程抢占句柄,最终只有一个进程能够正常打印日志;在多进程中回滚时会互相将日志文件删除,并且多进程同时启动时有小概率将多进程卡死。

注:使用该库的日志滚动只会发生在有新日志输出时,会出现像这样的情况:
假设日志按天回滚,第一天日志正常输出,第二天和第三天没有日志输出,第四天有日志输出,那么在第四天只会回滚第一天的日志,而不会生成第二天和第三天的日志文件。

TimedRotatingFileHandler 回滚 doRollover() 源码(省略初始化和其他函数部分):

class TimedRotatingFileHandler(BaseRotatingHandler):
    def doRollover(self):
        """
        do a rollover; in this case, a date/time stamp is appended to the filename
        when the rollover happens.  However, you want the file to be named for the
        start of the interval, not the current time.  If there is a backup count,
        then we have to get a list of matching filenames, sort them and remove
        the one with the oldest suffix.
        """
        # 关闭向日志中打印的被回滚掉的流,将其流置为空
        if self.stream:
            self.stream.close()
            self.stream = None

        # 获得队列的起始时间,将值赋给TimeTuple
        currentTime = int(time.time())
        dstNow = time.localtime(currentTime)[-1]
        t = self.rolloverAt - self.interval
        if self.utc:
            timeTuple = time.gmtime(t)
        else:
            timeTuple = time.localtime(t)
            dstThen = timeTuple[-1]
            if dstNow != dstThen:
                if dstNow:
                    addend = 3600
                else:
                    addend = -3600
                timeTuple = time.localtime(t + addend)

        # 获得被回滚掉的日志名
        dfn = self.rotation_filename(self.baseFilename + "." +
                                     time.strftime(self.suffix, timeTuple))

        # TODO 在该处多进程会互相删除各自的日志文件
        if os.path.exists(dfn):
            os.remove(dfn)
        self.rotate(self.baseFilename, dfn)

        # 回滚计数器大于0
        if self.backupCount > 0:
            for s in self.getFilesToDelete():
                os.remove(s)
        if not self.delay:
            self.stream = self._open()
        newRolloverAt = self.computeRollover(currentTime)
        while newRolloverAt <= currentTime:
            newRolloverAt = newRolloverAt + self.interval

        # 执行回滚
        if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc:
           # ~~~ 省略 ~~~~

问题原因

问题出在上述代码的 36 ~ 38 行 ,此处并没有加锁,并不是进程安全的。

在日志需要回滚时,多进程的每个进程都调用了 doRollover() 方法,导致多进程抢占日志句柄,最终在日志回滚后只有一个进程能够正常将日志打印到日志文件中。

问题解决

对日志文件的文件描述符加独占锁(fcntl.LOCK_EX),防止多个进程同时同时获取到日志文件描述符。

具体修改代码如下: 该类中的 doRollover() 方法中文件覆盖的部分(上述代码 36 ~ 38 行)重写

class MultiCompatibleTimedRotatingFileHandler(TimedRotatingFileHandler):
    def __init__(self, log_path, interval, when, backupCount, encoding):
        super().__init__(filename=log_path, interval=interval, when=when, backupCount=backupCount, encoding=encoding)

    def doRollover(self):
        # ~~~ 省略,与上面代码保持一致 ~~~

        # if os.path.exists(dfn):
        #     os.remove(dfn)
        # self.rotate(self.baseFilename, dfn)

        # 多进程检测到旧句柄存在则不覆盖,继续向文件续写,避免多进程日志互相覆盖
        if not os.path.exists(dfn) and os.path.exists(self.baseFilename):
            f = open(self.baseFilename, 'a')
            fcntl.lockf(f.fileno(), fcntl.LOCK_EX)
            if not os.path.exists(dfn) and os.path.exists(self.baseFilename):
                os.rename(self.baseFilename, dfn)
            f.close()  # 释放锁 释放老 log 句柄

        # ~~~ 省略,与上面代码保持一致 ~~~
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
1天前
|
开发者 Python
基于Python的日志管理与最佳实践
日志是开发和调试过程中的重要工具,然而,如何高效地管理和利用日志常常被忽略。本文通过Python中的logging模块,探讨如何使用日志来进行调试、分析与问题排查,并提出了一些实际应用中的优化建议和最佳实践。
|
3天前
|
消息中间件 网络协议 Python
工具人逆袭!掌握Python IPC,让你的进程从此告别单打独斗
【9月更文挑战第9天】你是否曾遇到多个Python程序像孤岛般无法通信,导致数据孤立、任务难协同的问题?掌握进程间通信(IPC)技术,可助你打破这一僵局。IPC是不同进程间传递数据或信号的机制,在Python中常用的方法有管道、消息队列、共享内存及套接字等。其中,管道适用于父子或兄弟进程间简单数据传递;套接字则不仅限于本地,还能在网络间实现复杂的数据交换。通过学习IPC,你将能设计更健壮灵活的系统架构,成为真正的编程高手。
10 3
|
4天前
|
安全 开发者 Python
揭秘Python IPC:进程间的秘密对话,让你的系统编程更上一层楼
【9月更文挑战第8天】在系统编程中,进程间通信(IPC)是实现多进程协作的关键技术。IPC机制如管道、队列、共享内存和套接字,使进程能在独立内存空间中共享信息,提升系统并发性和灵活性。Python提供了丰富的IPC工具,如`multiprocessing.Pipe()`和`multiprocessing.Queue()`,简化了进程间通信的实现。本文将从理论到实践,详细介绍各种IPC机制的特点和应用场景,帮助开发者构建高效、可靠的多进程应用。掌握Python IPC,让系统编程更加得心应手。
12 4
|
3天前
|
消息中间件 数据库 Python
深度剖析!Python IPC的奥秘,带你走进进程间通信的微观世界
【9月更文挑战第8天】在编程世界中,进程间通信(IPC)是连接不同程序或进程的关键技术,使数据在独立进程间自由流动,构建复杂软件系统。本文将深入探讨Python中的IPC机制,包括管道、消息队列、套接字等,并通过具体示例展示如何使用Socket实现网络IPC。Python的`multiprocessing`模块还提供了队列、管道和共享内存等多种高效IPC方式。通过本文,你将全面了解Python IPC的核心概念与应用技巧,助力开发高效协同的软件系统。
14 2
|
5天前
|
消息中间件 数据采集 数据库
庆祝吧!Python IPC让进程间的合作,比团队游戏还默契
【9月更文挑战第7天】在这个数字化时代,软件系统日益复杂,单进程已难以高效处理海量数据。Python IPC(进程间通信)技术应运而生,使多进程协作如同训练有素的电竞战队般默契。通过`multiprocessing`模块中的Pipe等功能,进程间可以直接传递数据,无需依赖低效的文件共享或数据库读写。此外,Python IPC还提供了消息队列、共享内存和套接字等多种机制,适用于不同场景,使进程间的合作更加高效、精准。这一技术革新让开发者能轻松应对复杂挑战,构建更健壮的软件系统。
15 1
|
13天前
|
消息中间件 安全 Python
Python日志管理之Loguru
Python日志管理之Loguru
|
2天前
|
消息中间件 安全 数据库
动手实操!Python IPC机制,打造高效协同的进程军团
【9月更文挑战第10天】在软件开发领域,进程间的高效协作对应用性能与稳定性至关重要。Python提供了多种进程间通信(IPC)机制,如管道、消息队列、套接字、共享内存等,帮助开发者构建高效协同的系统。本文将通过动手实践,使用`multiprocessing`模块演示如何利用队列实现进程间通信。示例代码展示了如何创建一个工作进程从队列接收并处理数据,从而实现安全高效的进程交互。通过实际操作,读者可以深入了解Python IPC的强大功能,提升系统的并发处理能力。
9 0
|
11天前
|
运维 监控 数据可视化
自动化运维:使用Python脚本进行日志分析
【8月更文挑战第31天】当系统出现问题时,我们通常会查看日志寻找线索。然而,手动阅读大量日志既费时又易出错。本文将介绍如何使用Python脚本自动分析日志,快速定位问题,提高运维效率。我们将从简单的日志读取开始,逐步深入到复杂的正则表达式匹配和错误统计,最后实现一个自动化的日志监控系统。无论你是新手还是老手,这篇文章都将为你提供有价值的参考。让我们一起探索如何用代码解放双手,让运维工作变得更加轻松吧!
|
12天前
|
SQL 关系型数据库 Shell
【一文搞懂PGSQL】3.进程和关键文件介绍
PostgreSQL采用C/S模型,拥有多种关键进程,如PM(连接管理)、SP(会话)、SysLogger(系统日志)、BgWriter(后台写)、WALWriter(预写式日志)、PgArch(归档)、AutoVacuum(自动清理)、PgStat(统计收集)和CheckPoint(检查点)。其中,PM负责连接管理,SP处理用户会话,SysLogger记录系统日志(需在`postgresql.conf`中启用),BgWriter负责脏页写盘,WALWriter处理预写式日志,PgArch负责WAL日志归
|
14天前
|
存储 搜索推荐 Python
Python|日志记录详解(2)
Python|日志记录详解(2)
12 0