重写TimedRotatingFileHandler 以兼容多进程

本文探讨了`TimedRotatingFileHandler`在多进程环境下可能丢失日志数据的问题,并提供了两种解决方案:一是直接写入完整文件名,二是使用锁机制避免重复切换。通过实例代码展示了如何改进日志切换,确保数据一致性。

TimedRotatingFileHandler有一个缺点就是没有办法支持多进程的日志切换,多进程进行日志切换的时候可能会因为重命名而丢失日志数据

原因在于它的日志并不是直接写在 baseFilename.2022-02-02.log 上,而是先写到 baseFilename上,到了凌晨零点时,把baseFilename 改名为 baseFilename.2022-02-02.log 。然后新建一个baseFilename文件 。

(如果改名时baseFilename.2022-02-02.log已存在,则把原baseFilename.2022-02-02.log删除,再把baseFilename 改名为 baseFilename.2022-02-02.log)

dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
if os.path.exists(dfn):
    os.remove(dfn)
if os.path.exists(self.baseFilename):
    os.rename(self.baseFilename, dfn)

如果要解决多进程重复删除baseFilename.2022-02-02.log的问题,比较容易想到的两个思路:

方案一:日志一开始就直接写在baseFilename.2022-02-02.log上

方案二:改名时如果baseFilename.2022-02-02.log已存在,则认为其他进程已做了切换操作,当前进程不需重复操作。 

其他方案参考:https://blog.youkuaiyun.com/ling620/article/details/103862183

​
# 代码转自:https://blog.youkuaiyun.com/dustless927/article/details/122061953
# 另有简易版(无删除过期日志功能) https://my.oschina.net/lionets/blog/796438
​

import codecs
import os
import re
import sys
import logging
import time
from pathlib import Path
from logging.handlers import BaseRotatingHandler


class DailyRotatingFileHandler(BaseRotatingHandler):
    """
    同`logging.TimedRotatingFileHandler`类似,不过这个handler:
    - 可以支持多进程
    - 只支持自然日分割
    - 暂不支持UTC
    """

    def __init__(self, filename
重写`logging.handlers.TimedRotatingFileHandler`类可以让你自定义日志文件的轮转策略。在多进程环境中,如使用Gunicorn运行Flask应用时,你可能需要处理多个工作进程写入同一个日志文件的问题。默认情况下,`TimedRotatingFileHandler`不支持多进程写入,因此你需要确保日志系统能够在多进程环境下正确地轮转日志文件。 以下是一个简单的例子,展示了如何在Flask项目中重写`TimedRotatingFileHandler`来实现多进程日期分割的日志轮转: ```python import logging.handlers import os class MultiProcessTimedRotatingFileHandler(logging.handlers.TimedRotatingFileHandler): def __init__(self, filename, when='H', interval=1, backupCount=0, encoding=None, delay=False): super().__init__(filename, when, interval, backupCount, encoding, delay) # 确保父进程打开文件时,子进程能够正确复制文件描述符 selfFH = None def getFilesToDelete(self): # 获取需要删除的旧日志文件列表 dirName, baseName = os.path.split(self.baseFilename) fileNames = os.listdir(dirName) result = [os.path.join(dirName, fileName) for fileName in fileNames if fileName.startswith(baseName + ".")] result.sort() return result[-self.backupCount:] def doRollover(self): # 多进程环境下,确保日志文件的轮转 if self.stream: self.stream.close() self.stream = None if self.backupCount > 0: for s in self.getFilesToDelete(): os.remove(s) # 这里不需要重新打开文件,因为我们将通过 reopen() 方法来处理它 self.reopen() def reopen(self): # 重新打开文件,确保可以写入新的日志文件 if self.encoding is None: mode = 'a' else: mode = 'a' self.stream = self._open() if self.delay: self.stream.close() self.stream = None class MyRotatingFileHandler(MultiProcessTimedRotatingFileHandler): def __init__(self, filename, when='H', interval=1, backupCount=0, encoding=None, delay=False): super().__init__(filename, when, interval, backupCount, encoding, delay) # 可以在这里添加更多自定义的逻辑 ``` 在Flask应用中使用自定义的日志处理器: ```python from flask import Flask import logging from myrotatingfilehandler import MyRotatingFileHandler app = Flask(__name__) # 创建logger对象 logger = logging.getLogger() logger.setLevel(logging.DEBUG) # 设置日志级别 # 创建自定义的多进程日志处理器 handler = MyRotatingFileHandler('app.log', when='D', interval=1, backupCount=7, encoding='utf-8', delay=False) # 设置处理器的日志级别和格式 handler.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) # 添加处理器到logger对象 logger.addHandler(handler) # 使用logger @app.route('/') def index(): logger.debug('debug message') logger.info('info message') logger.warning('warning message') logger.error('error message') logger.critical('critical message') return 'Hello, World!' if __name__ == '__main__': app.run(host='0.0.0.0', port=5000) ``` 使用这个自定义的`MyRotatingFileHandler`,你可以确保在多进程的Flask应用中,日志文件能够按照指定的策略进行日期分割轮转,同时处理多进程的写入问题。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值