自定义python多进程下可用的TimedRotatingFileHandler,解决使用logging模块写日志冲突问题

5 篇文章 0 订阅
4 篇文章 0 订阅

如题,解决多进程写日志冲突的问题,用法和logging模块原生的TimedRotatingFileHandler一样,但是不支持按星期保留日志,也不支持utc参数,需要的老铁可以自己定制。

需要注意的是这里没有使用进程锁,打开文件必须使用"a+"模式,改的时候要注意别改错了。

# -*- coding:utf-8 -*-
import os
import time
import logging
import logging.config
from datetime import datetime
from multiprocessing import Process
try:
    import codecs
except ImportError:
    codecs = None


class TimedRotatingFileHandler(logging.FileHandler):
    def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False):
        # 日志文件日期后缀
        when_map = {'S': '%Y-%m-%d_%H-%M-%S', 'M': '%Y-%m-%d_%H-%M', 'H': '%Y-%m-%d_%H', 'D': '%Y-%m-%d'}
        time_suffix = when_map.get(when.upper())
        if not time_suffix:
            raise ValueError('Error params when: %s' % when)

        self.backupCount = backupCount
        interval_map = {'S': 1, 'M': 60, 'H': 3600, 'D': 3600 * 24}
        self.interval = interval_map[when.upper()] * interval
        self.file_formater = '%s.%s' % (filename, time_suffix)  # 拼接日志文件路径格式化字符串
        self.filename = datetime.now().strftime(self.file_formater)  # 使用当前时间生成日志文件路径
        self.file_time = datetime.strptime(self.filename, self.file_formater)  # 生成当前日志文件对应的时间戳
        self.log_dir = os.path.dirname(os.path.abspath(self.filename))  # 获得日志文件夹路径
        if not os.path.exists(self.log_dir):  # 如果日志文件夹不存在,则创建文件夹
            os.makedirs(self.log_dir)

        if codecs is None:
            encoding = None
        super(TimedRotatingFileHandler, self).__init__(self.filename, 'a+', encoding, delay)  # 没有加锁,必须使用a+模式打开
        self.delete_expire_files()  # 刷新日志文件

    def should_change_file2_write(self, record):
        record_time = datetime.fromtimestamp(record.created)
        if (record_time - self.file_time).total_seconds() >= self.interval:  # 如果当前时间超过指定步长
            self.filename = record_time.strftime(self.file_formater)  # 更新到最新的日志文件名
            self.file_time = datetime.strptime(self.filename, self.file_formater)
            return True
        return False

    def do_change_file(self):
        self.baseFilename = os.path.abspath(self.filename)  # 日志文件的绝对路径
        if self.stream:  # stream is not None 表示OutStream中还有未输出完的缓存数据
            self.stream.close()  # 将缓存写入文件并关闭文件
            self.stream = None  # 关闭stream后必须重新设置stream为None,否则会造成对已关闭文件进行IO操作
        if not self.delay:
            self.stream = self._open()  # 打开新的日志文件文件流
        self.delete_expire_files()  # 删除多于保留个数的所有日志文件

    def is_log_file(self, filename, file_match):
        try:
            if datetime.strptime(filename, file_match):
                return True
        except:
            pass

    def delete_expire_files(self):
        if self.backupCount <= 0:  # backupCount不大于0时表示持续保留日志
            return
        file_match = os.path.basename(self.file_formater)
        result = [filename for filename in os.listdir(self.log_dir) if self.is_log_file(filename, file_match)]  # 提取出日志文件名
        for s in sorted(result)[:-self.backupCount]:  # 删除多于保留文件个数的日志文件
            try:
                os.remove(os.path.join(self.log_dir, s))
            except:
                pass

    def emit(self, record):
        try:
            if self.should_change_file2_write(record):  # 判断是否需要删除旧文件增加新文件
                self.do_change_file()
            logging.FileHandler.emit(self, record)  # 写入日志内容
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)


def init_logger(logger_name, log_save_count=3):
    logging.config.dictConfig({
        'version': 1,
        'disable_existing_loggers': False,
        'formatters': {
            'default_fmt': {
                'format': '%(asctime)s - %(name)s - %(filename)s - %(funcName)s[line:%(lineno)d] - %(levelname)s - %(process)d - %(message)s',
                # 'datefmt': '[%Y-%m-%d %H:%M:%S]'
            }
        },
        'handlers': {
            'root': {
                'class': 'logging.StreamHandler',
                'level': 'DEBUG',
                'formatter': 'default_fmt',
                'stream': 'ext://sys.stderr'
            },
            'count': {
                'class': 'main.TimedRotatingFileHandler',
                'level': 'DEBUG',
                'formatter': 'default_fmt',
                'filename': 'logs/count.log',
                'when': 'S',
                'interval': 1,
                'backupCount': log_save_count,
                'encoding': 'utf8'
            },
            'range': {
                'class': 'main.TimedRotatingFileHandler',
                'level': 'DEBUG',
                'formatter': 'default_fmt',
                'filename': 'logs/range.log',
                'when': 'S',
                'interval': 1,
                'backupCount': log_save_count,
                'encoding': 'utf8'
            }
        },
        'loggers': {
            'count': {
                'level': 'DEBUG',
                'handlers': ['count'],
                'propagate': 'no'
            },
            'range': {
                'level': 'DEBUG',
                'handlers': ['range'],
                'propagate': 'no'
            }
        },
        'root': {
            'level': 'DEBUG',
            'handlers': ['root'],
            'propagate': 'no'
        }
    })
    return logging.getLogger(logger_name)


def do_something(process_tag):
    for range_tag in range(400):  # 4个进程共1600行,1秒结束循环
        count_logger.info('process_tag: %s, range_tag:%s.' % (process_tag, range_tag))
        time.sleep(0.001)
    for range_tag in range(5000):  # 存在3个日志文件,5秒结束循环
        range_logger.info('process_tag: %s, range_tag:%s.' % (process_tag, range_tag))
        time.sleep(0.001)


if __name__ == '__main__':
    # 生成全局logger,不要作为参数传到进程内部使用
    count_logger = init_logger('range', log_save_count=3)  # 测试日志行数是否有错漏
    range_logger = init_logger('count', log_save_count=3)  # 测试文件按时间循环处理逻辑
    processes = [Process(target=do_something, args=(process_tag,)) for process_tag in range(4)]
    for process in processes:
        process.daemon = False  # 设为非守护进程
        process.start()  # 启动子进程
    for process in processes:
        process.join()  # 子进程阻塞父进程

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python Web 项目中,多进程日志需要考虑多个进程同时入同一个文件的问题。为了避免不同进程之间的入操作互相干扰,可以使用 Python 标准库中的 `logging` 模块,并将日志输出到不同的文件中。 下面是一个示例代码: ```python import logging import multiprocessing import os def process_task(): # 设置日志输出格式 log_format = '%(asctime)s %(levelname)s %(processName)s %(message)s' logging.basicConfig(level=logging.INFO, format=log_format) # 获取当前进程 ID process_id = os.getpid() # 创建一个 FileHandler 对象,并指定日志输出文件 file_handler = logging.FileHandler(f'log_{process_id}.txt') file_handler.setLevel(logging.INFO) file_handler.setFormatter(logging.Formatter(log_format)) # 将 FileHandler 添加到 logger 对象中 logger = logging.getLogger() logger.addHandler(file_handler) # 日志信息 logger.info('This is a log message from process %d.', process_id) if __name__ == '__main__': # 创建多个进程 processes = [] for i in range(5): p = multiprocessing.Process(target=process_task) processes.append(p) p.start() # 等待所有进程完成 for p in processes: p.join() ``` 在上面的代码中,我们首先定义了一个 `process_task` 函数,该函数会在每个进程中被调用。在 `process_task` 函数中,我们首先设置了日志输出的格式,然后获取当前进程 ID,创建了一个 `FileHandler` 对象,并将其添加到 `logger` 对象中。最后,我们使用 `logger` 对象入了一条日志信息。 在 `__main__` 函数中,我们创建了 5 个进程,并等待所有进程完成。在每个进程中,都会执行 `process_task` 函数,并将日志输出到不同的文件中。 这样,我们就可以在多进程Python Web 项目中,实现日志的并发入了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值