log文件可以方便查询系统运行状态及排查定位BUG,不同级别的日志可以输出到不同文件中,可以设置日志的格式规范,按日期创建日志文件等,以下是一个简单的示例
import logging
import os
import sys
import time
from logging import handlers
# 获取当前执行文件的文件目录
def script_path():
path = os.path.realpath(sys.argv[0])
if os.path.isfile(path):
path = os.path.dirname(path)
return os.path.abspath(path)
# 控制台日志设置
LOGGING_MSG_FORMAT = '[%(asctime)s] [%(levelname)s] [%(module)s] [%(funcName)s] [%(lineno)d] %(message)s'
LOGGING_DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(logging.Formatter(LOGGING_MSG_FORMAT, datefmt=LOGGING_DATE_FORMAT))
# 创建日志文件夹
log_path = os.path.join(script_path(), 'logs')
if not os.path.exists(log_path):
os.makedirs(log_path)
# INFO级别日志输出设置
info_log_file = os.path.join(log_path, 'info_log')
info_file_handler = logging.handlers.TimedRotatingFileHandler(info_log_file, when="midnight", interval=1,
backupCount=7)
info_file_handler.setLevel(logging.INFO)
info_file_handler.setFormatter(logging.Formatter(LOGGING_MSG_FORMAT, datefmt=LOGGING_DATE_FORMAT))
# ERROR级别日志输出设置
error_log_file = os.path.join(log_path, 'error_log')
error_file_handler = logging.handlers.TimedRotatingFileHandler(error_log_file, when="midnight", interval=1,
backupCount=7)
error_file_handler.setLevel(logging.ERROR)
error_file_handler.setFormatter(logging.Formatter(LOGGING_MSG_FORMAT, datefmt=LOGGING_DATE_FORMAT))
# 日志设置
logging.basicConfig(level=logging.INFO, handlers=[console, info_file_handler, error_file_handler])
# logging = logging
if __name__ == '__main__':
logging.debug('this is a logger debug message')
logging.info('this is a logger info message')
logging.warning('this is a logger warning message')
logging.error('this is a logger error message')
logging.info('it is running.')
在实际应用中发现在上面的设置下回发生日志漏掉的情况,网上大部分分析为多进程导致的日志文件重写,先换掉TimedRotatingFileHandler,重写一个新的类使其支持多进程下的日志记录:
import os
import re
import datetime
import logging
try:
import codecs
except ImportError:
codecs = None
class MultiprocessHandler(logging.FileHandler):
"""支持多进程的TimedRotatingFileHandler"""
def __init__(self,filename,when='D',backupCount=0,encoding=None,delay=False):
"""filename 日志文件名,when 时间间隔的单位,backupCount 保留文件个数
delay 是否开启 OutSteam缓存
True 表示开启缓存,OutStream输出到缓存,待缓存区满后,刷新缓存区,并输出缓存数据到文件。
False表示不缓存,OutStrea直接输出到文件"""
self.prefix = filename
self.backupCount = backupCount
self.when = when.upper()
# 正则匹配 年-月-日
self.extMath = r"^\d{4}-\d{2}-\d{2}"
# S 每秒建立一个新文件
# M 每分钟建立一个新文件
# H 每天建立一个新文件
# D 每天建立一个新文件
self.when_dict = {
'S':"%Y-%m-%d-%H-%M-%S",
'M':"%Y-%m-%d-%H-%M",
'H':"%Y-%m-%d-%H",
'D':"%Y-%m-%d"
}
#日志文件日期后缀
self.suffix = self.when_dict.get(when)
if not self.suffix:
raise ValueError(u"指定的日期间隔单位无效: %s" % self.when)
#拼接文件路径 格式化字符串
self.filefmt = os.path.join("logs","%s.%s" % (self.prefix,self.suffix))
#self.filefmt = "%s.%s" % (self.prefix,self.suffix)
#使用当前时间,格式化文件格式化字符串
self.filePath = datetime.datetime.now().strftime(self.filefmt)
#获得文件夹路径
_dir = os.path.dirname(self.filefmt)
try:
#如果日志文件夹不存在,则创建文件夹
if not os.path.exists(_dir):
os.makedirs(_dir)
except Exception:
print(u"创建文件夹失败")
print(u"文件夹路径:" + self.filePath)
pass
if codecs is None:
encoding = None
logging.FileHandler.__init__(self,self.filePath,'a+',encoding,delay)
def shouldChangeFileToWrite(self):
"""更改日志写入目的写入文件
:return True 表示已更改,False 表示未更改"""
#以当前时间获得新日志文件路径
_filePath = datetime.datetime.now().strftime(self.filefmt)
#新日志文件日期 不等于 旧日志文件日期,则表示 已经到了日志切分的时候
# 更换日志写入目的为新日志文件。
#例如 按 天 (D)来切分日志
# 当前新日志日期等于旧日志日期,则表示在同一天内,还不到日志切分的时候
# 当前新日志日期不等于旧日志日期,则表示不在
#同一天内,进行日志切分,将日志内容写入新日志内。
if _filePath != self.filePath:
self.filePath = _filePath
return True
return False
def doChangeFile(self):
"""输出信息到日志文件,并删除多于保留个数的所有日志文件"""
#日志文件的绝对路径
self.baseFilename = os.path.abspath(self.filePath)
#stream == OutStream
#stream is not None 表示 OutStream中还有未输出完的缓存数据
if self.stream:
#flush close 都会刷新缓冲区,flush不会关闭stream,close则关闭stream
#self.stream.flush()
self.stream.close()
#关闭stream后必须重新设置stream为None,否则会造成对已关闭文件进行IO操作。
self.stream = None
#delay 为False 表示 不OutStream不缓存数据 直接输出
# 所有,只需要关闭OutStream即可
if not self.delay:
#这个地方如果关闭colse那么就会造成进程往已关闭的文件中写数据,从而造成IO错误
#delay == False 表示的就是 不缓存直接写入磁盘
#我们需要重新在打开一次stream
#self.stream.close()
self.stream = self._open()
#删除多于保留个数的所有日志文件
if self.backupCount > 0:
print('删除日志')
for s in self.getFilesToDelete():
print(s)
os.remove(s)
def getFilesToDelete(self):
"""获得过期需要删除的日志文件"""
#分离出日志文件夹绝对路径
#split返回一个元组(absFilePath,fileName)
#例如:split('I:\ScripPython\char4\mybook\util\logs\mylog.2017-03-19)
#返回(I:\ScripPython\char4\mybook\util\logs, mylog.2017-03-19)
# _ 表示占位符,没什么实际意义,
dirName,_ = os.path.split(self.baseFilename)
fileNames = os.listdir(dirName)
result = []
#self.prefix 为日志文件名 列如:mylog.2017-03-19 中的 mylog
#加上 点号 . 方便获取点号后面的日期
prefix = os.path.split(self.prefix)[-1] + '.'
plen = len(prefix)
for fileName in fileNames:
if fileName[:plen] == prefix:
#日期后缀 mylog.2017-03-19 中的 2017-03-19
suffix = fileName[plen:]
#匹配符合规则的日志文件,添加到result列表中
if re.compile(self.extMath).match(suffix):
result.append(os.path.join(dirName,fileName))
result.sort()
#返回 待删除的日志文件
# 多于 保留文件个数 backupCount的所有前面的日志文件。
if len(result) < self.backupCount:
result = []
else:
result = result[:len(result) - self.backupCount]
return result
def emit(self, record):
"""发送一个日志记录
覆盖FileHandler中的emit方法,logging会自动调用此方法"""
try:
if self.shouldChangeFileToWrite():
self.doChangeFile()
logging.FileHandler.emit(self,record)
except (KeyboardInterrupt,SystemExit):
raise
except:
self.handleError(record)
用上面的MultiprocessHandler类替换TimedRotatingFileHandler类,解决了漏日志的问题
import logging
import os
import sys
import time
from logging import handlers
from multilog import MultiprocessHandler
# 获取当前执行文件的文件目录
def script_path():
path = os.path.realpath(sys.argv[0])
if os.path.isfile(path):
path = os.path.dirname(path)
return os.path.abspath(path)
# 控制台日志设置
LOGGING_MSG_FORMAT = '[%(asctime)s] [%(levelname)s] [%(module)s] [%(funcName)s] [%(lineno)d] %(message)s'
LOGGING_DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(logging.Formatter(LOGGING_MSG_FORMAT, datefmt=LOGGING_DATE_FORMAT))
# 创建日志文件夹
log_path = os.path.join(script_path(), 'logs')
if not os.path.exists(log_path):
os.makedirs(log_path)
# INFO级别日志输出设置
info_log_file = os.path.join(log_path, 'info_log')
info_file_handler = MultiprocessHandler(info_log_file, when="M", backupCount=3, encoding='utf-8')
info_file_handler.setLevel(logging.INFO)
info_file_handler.setFormatter(logging.Formatter(LOGGING_MSG_FORMAT, datefmt=LOGGING_DATE_FORMAT))
# ERROR级别日志输出设置
error_log_file = os.path.join(log_path, 'error_log')
error_file_handler = MultiprocessHandler(error_log_file, when="M", backupCount=3, encoding='utf-8')
error_file_handler.setLevel(logging.ERROR)
error_file_handler.setFormatter(logging.Formatter(LOGGING_MSG_FORMAT, datefmt=LOGGING_DATE_FORMAT))
# 日志设置
logging.basicConfig(level=logging.INFO, handlers=[console, info_file_handler, error_file_handler])
# logging = logging
if __name__ == '__main__':
logging.debug('this is a logger debug message')
logging.info('this is a logger info message')
logging.warning('this is a logger warning message')
logging.error('this is a logger error message')
logging.info('it is running.')
以上,对日志进行观察中,如有问题,后续更新