How to force a rotating name with python's TimedRotatingFileHandler?
我尝试使用TimeDrotatingFileHandler将每日日志保存在单独的日志文件中。旋转完全按预期工作,但我不喜欢它的工作方式是命名文件。
如果我将一个日志文件设置为"我的日志"file.log,这将是"今天的"日志文件,当它在午夜更改日期时,它将被重命名为
我想得到的是旧文件被重命名为EDCOX1,2,甚至EDCOX1,3,主要是.log在最后,而不是在中间。另外,我想让"今天的"日志文件已经用今天的日期命名,就像以前的一样。
有什么办法吗?
我发现我可以对后缀进行个性化设置:
handler.suffix ="%Y-%m-%d"
但我不知道如何删除内部的.log部分并强制当前日志文件添加后缀。
我创建了一个类ParallelTimedRotatingFileHandler,主要目的是允许多个进程并行地写入日志文件。此类解决的并行进程问题有:
- 当所有进程都试图同时复制或重命名同一文件时,滚动会出现错误。
- 这个问题的解决方案正是您建议的命名约定。因此,对于您在处理程序中提供的文件名
Service ,日志记录不会转到例如Service.log ,而是今天转到Service.2014-08-18.log ,明天转到Service.2014-08-19.log 。 - 另一种解决方案是以EDOCX1(附加)模式而不是
w 模式打开文件,以允许并行写入。 - 删除备份文件也需要小心,因为多个并行进程同时删除相同的文件。
- 此实现不考虑闰秒(对于UNIX来说这不是问题)。在其他操作系统中,滚动时可能仍然是30/6/2008 23:59:60,所以日期没有更改,所以我们采用与昨天相同的文件名。
- 我知道标准的python建议是
logging 模块不能用于并行进程,我应该使用sockethandler,但至少在我的环境中,这是可行的。
代码只是标准python handlers.py模块中代码的微小变化。当然版权归版权所有者所有。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | 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 # one second 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 # one minute 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 # one hour 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 # one day 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 # one week 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 # multiply by units requested 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 DST changes and midnight or weekly rollover, adjust for this. 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: # DST kicks in before next rollover, so we need to deduct an hour newRolloverAt = newRolloverAt - 3600 else: # DST bows out before next rollover, so we need to add an hour newRolloverAt = newRolloverAt + 3600 self.rolloverAt = newRolloverAt |
据我所知,没有办法直接做到这一点。
您可以尝试的一种解决方案是重写默认行为。
- 创建自己的
TimedRotatingFileHandler class 并覆盖doRollover() function. 。 - 检查python安装中的源代码
。/Lib/logging/handlers.py
像这样:
1 2 3 4 5 6 | class MyTimedRotatingFileHandler(TimedRotatingFileHandler): def __init__(self, **kwargs): TimedRotatingFileHandler.__init__(self, **kwargs) def doRollover(self): # Do your stuff, rename the file as you want |