我创建了一个名为ParallelTimedRotatingFileHandler的类,主要旨在允许多个进程并行写入日志文件。
这个类解决的与并行进程有关的问题包括:
- 所有进程在同时尝试复制或重命名同一文件时的滚动瞬间会出现错误。
- 解决此问题的方法正是您建议的命名约定。因此,对于您在处理程序中提供的文件名
Service
,日志记录不会进入例如Service.log
,而是今天进入Service.2014-08-18.log
,明天进入Service.2014-08-19.log
。
- 另一种解决方案是以
a
(追加)模式打开文件,而不是w
,以允许并行写入。
- 删除备份文件也需要小心,因为多个并行进程正在同时删除同一文件。
- 此实现不考虑闰秒(这对Unix不是问题)。在其他操作系统中,可能仍然是2018年6月30日23:59:60,在滚动瞬间,日期未更改,因此我们采用昨天相同的文件名。
- 我知道标准Python建议不使用
logging
模块进行并行处理,而应使用SocketHandler,但在我的环境中,这个方法可行。
代码只是标准Python handlers.py模块中代码的微小变化。当然,版权属于版权持有人。
以下是代码:
import logging
import logging.handlers
import os
import time
import re
class ParallelTimedRotatingFileHandler(logging.handlers.TimedRotatingFileHandler):
def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, postfix = ".log"):
self.origFileName = filename
self.when = when.upper()
self.interval = interval
self.backupCount = backupCount
self.utc = utc
self.postfix = postfix
if self.when == 'S':
self.interval = 1
self.suffix = "%Y-%m-%d_%H-%M-%S"
self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}$"
elif self.when == 'M':
self.interval = 60
self.suffix = "%Y-%m-%d_%H-%M"
self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}$"
elif self.when == 'H':
self.interval = 60 * 60
self.suffix = "%Y-%m-%d_%H"
self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}$"
elif self.when == 'D' or self.when == 'MIDNIGHT':
self.interval = 60 * 60 * 24
self.suffix = "%Y-%m-%d"
self.extMatch = r"^\d{4}-\d{2}-\d{2}$"
elif self.when.startswith('W'):
self.interval = 60 * 60 * 24 * 7
if len(self.when) != 2:
raise ValueError("You must specify a day for weekly rollover from 0 to 6 (0 is Monday): %s" % self.when)
if self.when[1] < '0' or self.when[1] > '6':
raise ValueError("Invalid day specified for weekly rollover: %s" % self.when)
self.dayOfWeek = int(self.when[1])
self.suffix = "%Y-%m-%d"
self.extMatch = r"^\d{4}-\d{2}-\d{2}$"
else:
raise ValueError("Invalid rollover interval specified: %s" % self.when)
currenttime = int(time.time())
logging.handlers.BaseRotatingHandler.__init__(self, self.calculateFileName(currenttime), 'a', encoding, delay)
self.extMatch = re.compile(self.extMatch)
self.interval = self.interval * interval
self.rolloverAt = self.computeRollover(currenttime)
def calculateFileName(self, currenttime):
if self.utc:
timeTuple = time.gmtime(currenttime)
else:
timeTuple = time.localtime(currenttime)
return self.origFileName + "." + time.strftime(self.suffix, timeTuple) + self.postfix
def getFilesToDelete(self, newFileName):
dirName, fName = os.path.split(self.origFileName)
dName, newFileName = os.path.split(newFileName)
fileNames = os.listdir(dirName)
result = []
prefix = fName + "."
postfix = self.postfix
prelen = len(prefix)
postlen = len(postfix)
for fileName in fileNames:
if fileName[:prelen] == prefix and fileName[-postlen:] == postfix and len(fileName)-postlen > prelen and fileName != newFileName:
suffix = fileName[prelen:len(fileName)-postlen]
if self.extMatch.match(suffix):
result.append(os.path.join(dirName, fileName))
result.sort()
if len(result) < self.backupCount:
result = []
else:
result = result[:len(result) - self.backupCount]
return result
def doRollover(self):
if self.stream:
self.stream.close()
self.stream = None
currentTime = self.rolloverAt
newFileName = self.calculateFileName(currentTime)
newBaseFileName = os.path.abspath(newFileName)
self.baseFilename = newBaseFileName
self.mode = 'a'
self.stream = self._open()
if self.backupCount > 0:
for s in self.getFilesToDelete(newFileName):
try:
os.remove(s)
except:
pass
newRolloverAt = self.computeRollover(currentTime)
while newRolloverAt <= currentTime:
newRolloverAt = newRolloverAt + self.interval
if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc:
dstNow = time.localtime(currentTime)[-1]
dstAtRollover = time.localtime(newRolloverAt)[-1]
if dstNow != dstAtRollover:
if not dstNow:
newRolloverAt = newRolloverAt - 3600
else:
newRolloverAt = newRolloverAt + 3600
self.rolloverAt = newRolloverAt
__init__
的第一行添加对基类构造函数的调用,则您的类无法正常工作:super().__init__(filename=filename, when=when, interval=interval, backupCount=backupCount, encoding=encoding, delay=delay, utc=utc)
。 - Erwin Mayer