背景
日常使用的是concurrent-log来打印Python项目的日志,该库是基于原生logging库的二次开发,以支持多进程日志打印,但有个不满意的地方就是日志文件的后缀是日期结尾(当然可能有其他方式可以设置,但我没有找到),不管如何能解决自己的需求就行,过程不重要
修改前log文件
解决办法
对ConcurrentTimedRotatingFileHandler类下的doRollover方法重写
class PrivateConcurrentTimedRotatingFileHandler(ConcurrentTimedRotatingFileHandler):
# 重写doRollover方法,用于支撑日志翻转的文件命名
def doRollover(self):
"""
本方法继承Python标准库,修改的部分已在下方使用注释标记出
"""
if self.stream:
self.stream.close()
self.stream = None
# get the time that this sequence started at and make it a TimeTuple
currentTime = int(time.time())
dstNow = time.localtime(currentTime)[-1]
t = self.rolloverAt - self.interval
if self.utc:
timeTuple = time.gmtime(t)
else:
timeTuple = time.localtime(t)
dstThen = timeTuple[-1]
if dstNow != dstThen:
if dstNow:
addend = 3600
else:
addend = -3600
timeTuple = time.localtime(t + addend)
# 每天生成的日志文件没有后缀,需要修改源码:TimedRotatingFileHandler类下的doRollover方法-->
# dfn = self.rotation_filename(self.baseFilename + "." + time.strftime(self.suffix, timeTuple)后面拼接后缀名
# dfn = self.rotation_filename(self.baseFilename + "." + time.strftime(self.suffix, timeTuple) + ".log")
dfn = self.rotation_filename(self.baseFilename + "." + time.strftime(self.suffix, timeTuple) + ".log")
"""
如果翻转文件已经生成,则说明其他进程已经处理过翻转
处理日志文件已经翻转当前进程中未写入文件的日志副本,修改开始
"""
# 直接修改静态变量,因为代码执行到此处已经获取到非重入进程锁,保证同一时间只有一个线程对变量进行修改
# 由于Python GIL,同一时间同一进程内只有一个线程运行,线程切换后缓存自动失效,即其他线程可以看见修改后的最新值
# 记录每一次触发翻转动作的时间,不管反转是否真的执行
ConcurrentTimedRotatingFileHandler.before_rollover_at = self.rolloverAt
if os.path.exists(dfn):
# 因为进程变量不会在内存同步,所以存在其他进程已经翻转过日志文件当时当前进程中还标识为未翻转
# 日志内容创建时间如果小于等于下一个处理翻转时刻,则将日志写入反转后的日志文件,而不是当前的baseFilename
# 当前磁盘上的baseFilename对于当前进程中的标识副本来说已经是翻转后要写入的文件
# 所以当文件存在时,本次不再进行翻转动作
pass
else:
self.rotate(self.baseFilename, dfn)
"""
处理日志文件已经翻转当前进程中未写入文件的日志副本,修改结束
"""
if self.backupCount > 0:
for s in self.getFilesToDelete():
os.remove(s)
if not self.delay:
self.stream = self._open()
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:
dstAtRollover = time.localtime(newRolloverAt)[-1]
if dstNow != dstAtRollover:
if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
addend = -3600
else: # DST bows out before next rollover, so we need to add an hour
addend = 3600
newRolloverAt += addend
# 此刻,当前进程中的标识副本已经同步为最新
self.rolloverAt = newRolloverAt
重写后,调用自己的子类PrivateConcurrentTimedRotatingFileHandler即可
def get_logger(self):
"""在logger中添加日志句柄并返回,如果logger已有句柄,则直接返回
我们这里添加两个句柄,一个输出日志到控制台,另一个输出到日志文件。
两个句柄的日志级别不同,在配置文件中可设置。
"""
if not self.logger.handlers: # 避免重复日志
console_handler = logging.StreamHandler()
console_handler.setFormatter(self.formatter)
console_handler.setLevel(self.console_output_level)
self.logger.addHandler(console_handler)
# 每天重新创建一个日志文件,最多保留backup_count份
# 每天生成的日志文件没有后缀,需要修改源码:TimedRotatingFileHandler类下的doRollover方法-->
# dfn = self.rotation_filename(self.baseFilename + "." + time.strftime(self.suffix, timeTuple)后面拼接后缀名
# dfn = self.rotation_filename(self.baseFilename + "." + time.strftime(self.suffix, timeTuple) + ".log")
file_handler = PrivateConcurrentTimedRotatingFileHandler(filename=os.path.join(self.log_path, self.log_file_name),
when='MIDNIGHT',
interval=1,
backupCount=self.backup_count,
delay=True,
encoding='utf-8'
)
file_handler.setFormatter(self.formatter)
file_handler.setLevel(self.file_output_level)
self.logger.addHandler(file_handler)