python 多进程日志打印方法

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

posted @ 2025-04-14 15:56  吴丹阳-V  阅读(66)  评论(0)    收藏  举报