惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - Franky
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
美团技术团队
The Cloudflare Blog
量子位
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园_首页
F
Fortinet All Blogs
J
Java Code Geeks
人人都是产品经理
人人都是产品经理
N
Netflix TechBlog - Medium
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
爱范儿
爱范儿
Apple Machine Learning Research
Apple Machine Learning Research
B
Blog RSS Feed
博客园 - 聂微东
Hugging Face - Blog
Hugging Face - Blog
WordPress大学
WordPress大学
小众软件
小众软件
Y
Y Combinator Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Vercel News
Vercel News
S
SegmentFault 最新的问题
有赞技术团队
有赞技术团队

博客园 - 吴丹阳-V

换源神器 chsrc:一键为你的开发环境切换到最快镜像 python 什么是偏函数 CDN 带宽控制的几种方法 obsidian-things3-today插件 凡人修仙传动漫截图 Things 工作流 创建一个chrome独立APP窗口 202405081331 mac 鼠须管 - rime 如何使用命令行执行同步操作 linux-command-substitution(命令替换) 系统调用的具体过程以及注意点【包含AIGC】 使用 shell 脚本启动固定数量的进程 为什么 echo -n 后面会带着一个百分号 网络-最多有多少个a类、b类和c类网络号 unicode 与 utf-8 与 utf-16【包含AIGC】 《你的第一本哲学书》 我喜欢的音乐歌手标签 nginx 进程模型-整体架构 linux - trap 命令 丰田五问法 webp 格式图片压缩效果
python 多进程日志打印方法
吴丹阳-V · 2025-04-14 · via 博客园 - 吴丹阳-V

python 多进程日志轮转

具体代码

继承 logging.handlers.BaseRotatingHandler

轮转时,通过_open函数创建一个新的文件,文件名称总是真实写入的文件,没有文件的移动删除等操作

实际看到的基础文件,通过软链创建,用于日志收集、分析使用

轮转时间根据 suffix 决定,在日志整点时自动写入新的文件中

写入的mode必须是 append => 'a' , 保证每次写入到尾部是原子操作

class AlignedTimedRotatingFileHandler(logging.handlers.BaseRotatingHandler):
    """
    自定义的定时轮转处理器,确保在每小时整点进行轮转
    多进程安全
    see: https://my.oschina.net/lionets/blog/796438
    """

    def __init__(
        self,
        filename,
        backupCount=0,
        encoding=None,
        delay=False,
    ):
        # 设置文件名后缀
        self.suffix = "%Y%m%d%H"
        self.extMatch = re.compile(r"^\d{4}\d{2}\d{2}\d{2}$")
        self.baseFilename = filename
        self.currentFileName = self._compute_fn()
        self.backupCount = backupCount
        # 调用父类初始化方法 追加写入文件
        super().__init__(filename, "a", encoding, delay)

    def shouldRollover(self, record):
        """
        根据文件名计算是否需要轮转
        """
        if self.currentFileName != self._compute_fn():
            return True
        return False

    def doRollover(self):
        """
        执行轮转操作,仅将文件名修改,真正的轮转在 _open 函数中
        """
        if self.stream:
            self.stream.close()
            self.stream = None
        self.currentFileName = self._compute_fn()
        # 如果需要删除文件,则删除
        if self.backupCount > 0:
            for s in self.getFilesToDelete():
                try:
                    os.remove(s)
                except Exception:
                    # 文件删除失败,忽略,可能是文件已经被删除或者没有权限
                    pass

    def getFilesToDelete(self):
        """
        获取需要删除的文件
        """
        # 获取所有与当前文件名+后缀匹配的文件
        dirName, baseName = os.path.split(self.baseFilename)
        fileNames = os.listdir(dirName)
        result = []

        # 构建文件名前缀
        prefix = baseName + "."
        plen = len(prefix)

        # 遍历目录下的所有文件
        for fileName in fileNames:
            if fileName[:plen] == prefix:
                suffix = fileName[plen:]
                if self.extMatch.match(suffix):
                    result.append(os.path.join(dirName, fileName))

        # 如果文件数量小于等于backupCount,不需要删除任何文件
        if len(result) <= self.backupCount:
            return []

        # 按文件名排序
        result.sort()

        # 返回需要删除的文件列表
        return result[: len(result) - self.backupCount]

    def _compute_fn(self):
        """
        计算当前时间的文件名
        """
        return self.baseFilename + "." + time.strftime(self.suffix, time.localtime())

    def _open(self):
        """
        重写 _open 方法,确保在轮转时多进程写入同一个文件
        """
        if self.encoding is None:
            stream = open(self.currentFileName, self.mode)
        else:
            stream = codecs.open(self.currentFileName, self.mode, self.encoding)
        # 如果文件存在,则删除
        if os.path.exists(self.baseFilename):
            try:
                os.remove(self.baseFilename)
            except OSError:
                pass
        try:
            os.symlink(self.currentFileName, self.baseFilename)
        except OSError:
            pass
        return stream

参考

https://my.oschina.net/lionets/blog/796438