python标准库中的logging模块在记录日志时经常会用到,但在实际使用发现它自带的用于本地日志回滚的类
logging.handlers.RotatingFileHandler 在多进程环境下会出现不同进程向不同文件写的问题,原因就是在当前
日志文件写满后回滚的时候没有处理好并发问题(或者可以说基本没处理),因此自己实现了一个相似功能的类,
本来是打算使用多进程锁,写完后发现没啥用。。。,于是就新建了一个.lock文件作为锁来处理多进程。用文件的
修改时间来控制只能有一个进程访问。
代码如下:
#coding=utf-8 import os import sys import json import time import logging import traceback import logging.handlers from multiprocessing import Lock class SpiderRotatingFileHandler(logging.handlers.RotatingFileHandler): u''' 文件回滚日志处理器 特点: 1. 利用备份文件修改时间做判断 修复了多进程下同时多个日志文件被写入的bug 2. 可选项 使用json格式记录日志文件 ''' def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0, is_json=False): logging.handlers.RotatingFileHandler.__init__(self, filename, mode, maxBytes, backupCount, encoding, delay) # 格式处理器 self.Formatter = logging.Formatter() # 进程锁 self.my_lock = Lock() self.is_json = is_json if self.is_json: self.format = self.json_format def json_format(self, record): u''' json 格式化日志 @record: 日志记录对象 type: logging.LogRecord ''' # 增加 asctime 属性 record.asctime = self.Formatter.formatTime(record) # message = record.getMessage() log_data = {} # 检查是否为json格式 并且是字典形式 try: log_data = json.loads(message) if not isinstance(log_data, dict): log_data = {} except Exception as e: exc_info = traceback.format_exc() #sys.stderr.write(exc_info) # 获取日志基本信息 log_record_basic_fields = [ "levelname", "filename", "lineno", "name", "created", "asctime", ] if not log_data: log_data.update({ "_message": message, }) for attr in log_record_basic_fields: value = getattr(record, attr, "") log_data.update({ "_{}".format(attr): value, }) try: result = json.dumps(log_data, ensure_ascii=False) except: result = json.dumps(log_data) return result def doRollover(self): """ Do a rollover, as described in __init__(). """ with self.my_lock: if self.stream: self.stream.close() self.stream = None lock_file = "%s.lock"%self.baseFilename max_modify_interval = 3 # seconds do_flag = 0 # 利用 Lock 文件被修改时间保证不会出现同时多个文件被写入 if not os.path.exists(lock_file): with open(lock_file, "w"): pass do_flag = 1 elif time.time() - os.stat(lock_file).st_mtime > max_modify_interval: do_flag = 1 else: pass if do_flag: for i in range(self.backupCount - 1, 0, -1): sfn = "%s.%d" % (self.baseFilename, i) dfn = "%s.%d" % (self.baseFilename, i + 1) if os.path.exists(sfn): # 删除最大备份文件 if os.path.exists(dfn): os.remove(dfn) os.rename(sfn, dfn) dfn = self.baseFilename + ".1" if os.path.exists(dfn): os.remove(dfn) if os.path.exists(self.baseFilename): os.rename(self.baseFilename, dfn) # 刷新 Lock 文件修改时间 with open(lock_file, "w"): pass if not self.delay: self.stream = self._open() return
经过测试后发现,日志文件不再出现混乱写入(不过总感觉 3 秒好像还会出现点问题,万一在3秒内写满了日
志文件可能会造成日志文件大小超过限制。)
json格式的日志输出算是附加的功能吧
ok,欢迎找茬