Python 多进程下日志打印

Python 多进程下日志打印

问题分析

使用 gunicorn 启动 Flask 时,如果直接使用 logging 的 RotatingFileHandler 模块会出现日志混乱,甚至日志丢失的情况。
在日志翻转时,可能出现一个进程将 log 文件翻转,而后又有进程也将 log 文件翻转,导致 log.1 文件并未达到设置的最大的文件大小,而第一个进程也继续再往 log.1 中输出日志。

改进方式

根据网上很多文档,使用 portalocker 创建文件锁,实现了一个简单的 handler 方便使用,暂时没有发现问题。
需要安装依赖:pip install portalocker

import logging
import logging.handlers
import os
import time
from logging.handlers import RotatingFileHandler

import portalocker


def get_lock_filename(log_file_name) -> str:
    """
    定义日志文件锁名称,类似于 `.__file.lock`,其中file与日志文件baseFilename一致
    :return:
    """
    if log_file_name.endswith('.log'):
        lock_file = log_file_name[:-4]
    else:
        lock_file = log_file_name
    lock_file += '.lock'
    lock_path, lock_name = os.path.split(lock_file)
    # hide the file on Unix and generally from file completion
    lock_name = '.__' + lock_name
    return str(os.path.join(lock_path, lock_name))


class ConcurrentRotatingFileHandler(RotatingFileHandler):
    def __init__(self, filename, *args, **kwargs):
        super().__init__(filename, *args, **kwargs)

        file_dir = os.path.split(filename)[0]
        if not os.path.exists(file_dir):
            os.makedirs(file_dir)

        lock_filename = get_lock_filename(filename)
        self.concurrent_lock = portalocker.Lock(lock_filename, flags=portalocker.LOCK_EX)

    def _do_rollover(self, record):
        # 检查日志文件是否需要翻转
        base_stream = self._open()
        base_stream.seek(0, 2)
        msg = "%s\n" % self.format(record)
        base_stream_length = base_stream.tell() + len(msg)
        base_stream.close()
        if base_stream_length >= self.maxBytes:
            # print('current pid: %d, doRollover' % os.getpid())
            super().doRollover()
        else:
            if self.stream:
                self.stream.close()
                self.stream = None
            if not self.delay:
                self.stream = self._open()

    def emit(self, record):
        """
        Emit a record.

        Output the record to the file, catering for rollover as described
        in doRollover().
        """
        with self.concurrent_lock:
            try:
                if self.shouldRollover(record):
                    # print('current pid: %d, shouldRollover: %d' % (os.getpid(), self.shouldRollover(record)))
                    self._do_rollover(record)
                logging.FileHandler.emit(self, record)
            except Exception:
                self.handleError(record)

posted @ 2024-07-25 21:40  守望人间  阅读(187)  评论(0)    收藏  举报