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

简介: 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 句柄

        # ~~~ 省略,与上面代码保持一致 ~~~
相关实践学习
通过日志服务实现云资源OSS的安全审计
本实验介绍如何通过日志服务实现云资源OSS的安全审计。
相关文章
|
5月前
|
监控 安全 程序员
Python日志模块配置:从print到logging的优雅升级指南
从 `print` 到 `logging` 是 Python 开发的必经之路。`print` 调试简单却难维护,日志混乱、无法分级、缺乏上下文;而 `logging` 支持级别控制、多输出、结构化记录,助力项目可维护性升级。本文详解痛点、优势、迁移方案与最佳实践,助你构建专业日志系统,让程序“有记忆”。
441 0
|
6月前
|
数据可视化 Linux iOS开发
Python脚本转EXE文件实战指南:从原理到操作全解析
本教程详解如何将Python脚本打包为EXE文件,涵盖PyInstaller、auto-py-to-exe和cx_Freeze三种工具,包含实战案例与常见问题解决方案,助你轻松发布独立运行的Python程序。
1606 2
|
5月前
|
监控 机器人 编译器
如何将python代码打包成exe文件---PyInstaller打包之神
PyInstaller可将Python程序打包为独立可执行文件,无需用户安装Python环境。它自动分析代码依赖,整合解释器、库及资源,支持一键生成exe,方便分发。使用pip安装后,通过简单命令即可完成打包,适合各类项目部署。
1041 68
|
7月前
|
缓存 数据可视化 Linux
Python文件/目录比较实战:排除特定类型的实用技巧
本文通过四个实战案例,详解如何使用Python比较目录差异并灵活排除特定文件,涵盖基础比较、大文件处理、跨平台适配与可视化报告生成,助力开发者高效完成目录同步与数据校验任务。
251 0
|
8月前
|
编译器 Python
如何利用Python批量重命名PDF文件
本文介绍了如何使用Python提取PDF内容并用于文件重命名。通过安装Python环境、PyCharm编译器及Jupyter Notebook,结合tabula库实现PDF数据读取与处理,并提供代码示例与参考文献。
|
8月前
|
编译器 Python
如何利用Python批量重命名文件
本文介绍了如何使用Python和PyCharm对文件进行批量重命名,包括文件名前后互换、按特定字符调整顺序等实用技巧,并提供了完整代码示例。同时推荐了第三方工具Bulk Rename Utility,便于无需编程实现高效重命名。适用于需要处理大量文件命名的场景,提升工作效率。
|
9月前
|
编解码 Prometheus Java
当Python同时操作1000个文件时,为什么你的CPU只用了10%?
本文介绍如何构建一个高效的文件处理系统,解决单线程效率低、多线程易崩溃的矛盾。通过异步队列与多线程池结合,实现任务调度优化,提升I/O密集型操作的性能。
219 4
|
8月前
|
安全 Linux 网络安全
Python极速搭建局域网文件共享服务器:一行命令实现HTTPS安全传输
本文介绍如何利用Python的http.server模块,通过一行命令快速搭建支持HTTPS的安全文件下载服务器,无需第三方工具,3分钟部署,保障局域网文件共享的隐私与安全。
2041 0
|
8月前
|
数据管理 开发工具 索引
在Python中借助Everything工具实现高效文件搜索的方法
使用上述方法,你就能在Python中利用Everything的强大搜索能力实现快速的文件搜索,这对于需要在大量文件中进行快速查找的场景尤其有用。此外,利用Python脚本可以灵活地将这一功能集成到更复杂的应用程序中,增强了自动化处理和数据管理的能力。
668 0
|
8月前
|
数据采集 监控 算法
Python文件与目录比较全攻略:从基础操作到性能优化
文件比较的核心在于数据指纹校验,通过逐字节比对生成唯一标识,确保内容一致性。从标准库的os与filecmp到高性能第三方库如pydiffx,再到分布式与量子加密技术的未来趋势,文件比较广泛应用于数据备份、代码审查与系统监控等领域,是保障数据完整性的关键技术手段。
182 0

推荐镜像

更多