关于日志记录:如何使用python的TimedRotatingFileHandler强制旋转名称?

How to force a rotating name with python's TimedRotatingFileHandler?

我尝试使用TimeDrotatingFileHandler将每日日志保存在单独的日志文件中。旋转完全按预期工作,但我不喜欢它的工作方式是命名文件。

如果我将一个日志文件设置为"我的日志"file.log,这将是"今天的"日志文件,当它在午夜更改日期时,它将被重命名为my_log_file.log.2014-07-08,结尾没有.log扩展名,并且将为新的一天创建一个新的my_log_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