Python 多进程 日志
最近需要给多进程加日志 但是在加的过程中出现了诸多问题,比如日志丢失,代码报错等一切问题。经过一番查询找到了大牛的解决方案,可行,在这里进行记录以及帮助大家。
目前这个还是丢日志 后期加了我自己的解决方法
# coding=utf-8
import os
import sys
import re
import datetime
import time
import logging
import multiprocessing
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.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 = self.prefix + '.'
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)
def init_logging_handler():
# 定义日志输出格式
formattler = '%(levelname)s - %(name)s - %(asctime)s - %(message)s'
fmt = logging.Formatter(formattler)
# 获得logger,默认获得root logger对象
# 设置logger级别 debug
# root logger默认的级别是warning级别。
# 不设置的话 只能发送 >= warning级别的日志
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
# 设置handleer日志处理器,日志具体怎么处理都在日志处理器里面定义
# SteamHandler 流处理器,输出到控制台,输出方式为stdout
# StreamHandler默认输出到sys.stderr
# 设置handler所处理的日志级别。
# 只能处理 >= 所设置handler级别的日志
# 设置日志输出格式
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(logging.DEBUG)
stream_handler.setFormatter(fmt)
# 使用我们写的多进程版Handler理器,定义日志输出到mylog.log文件内
# 文件打开方式默认为 a
# 按分钟进行日志切割
file_handler = MultiprocessHandler('mylog', when='M')
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(fmt)
# 对logger增加handler日志处理器
logger.addHandler(stream_handler)
logger.addHandler(file_handler)
return logger
logger = init_logging_handler()
def test(num):
time.sleep(10)
logger.debug('日志测试' + str(num))
if __name__ == '__main__':
pool = multiprocessing.Pool(processes=3)
for i in range(10):
pool.apply_async(func=test, args=(i,))
pool.close()
pool.join()
print('完毕')
借鉴地址:https://www.jianshu.com/p/d874a05edf19
日志不丢失的代码:
# coding=utf-8
import os
import sys
import re
import datetime
import time
import logging
import multiprocessing
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.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 = self.prefix + '.'
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)
def init_logging_handler():
# 定义日志输出格式
formattler = '%(levelname)s - %(name)s - %(asctime)s - %(message)s'
fmt = logging.Formatter(formattler)
# 获得logger,默认获得root logger对象
# 设置logger级别 debug
# root logger默认的级别是warning级别。
# 不设置的话 只能发送 >= warning级别的日志
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
# 设置handleer日志处理器,日志具体怎么处理都在日志处理器里面定义
# SteamHandler 流处理器,输出到控制台,输出方式为stdout
# StreamHandler默认输出到sys.stderr
# 设置handler所处理的日志级别。
# 只能处理 >= 所设置handler级别的日志
# 设置日志输出格式
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(logging.DEBUG)
stream_handler.setFormatter(fmt)
# 使用我们写的多进程版Handler理器,定义日志输出到mylog.log文件内
# 文件打开方式默认为 a
# 按分钟进行日志切割
file_handler = MultiprocessHandler('mylog', when='D')
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(fmt)
# 对logger增加handler日志处理器
logger.addHandler(stream_handler)
logger.addHandler(file_handler)
return logger
logger = init_logging_handler()
def test(num, m_queue):
time.sleep(2)
m_queue.put('日志测试' + str(num))
def write_logging(m_queue):
while True:
data = m_queue.get()
if data:
logger.debug(data)
if __name__ == '__main__':
m_queue = multiprocessing.Manager().Queue(maxsize=1000)
multiprocessing.Manager().Queue()
pool = multiprocessing.Pool(processes=10)
p1 = multiprocessing.Process(target=write_logging, args=(m_queue,))
p1.start()
for i in range(10):
pool.apply_async(func=test, args=(i, m_queue,))
pool.close()
pool.join()
p1.join()
print('完毕')
解决方法 再加一个进程,专门加一个日志管理进程。
在测试的过程中上面的代码因为监控日志所以是死循环 程序不能结束 可以将里面的函数替换下面代码 10秒没有新日志 就退出程序 这里的信号可以大家自己设置 比如监控进程 或者有特殊信号什么的 大家根据自己的需求而定就行
def write_logging(m_queue):
num = 0
while True:
if m_queue.qsize() != 0:
num = 0
data = m_queue.get()
logger.debug(data)
else:
time.sleep(1)
num += 1
if num == 10:
break
用心写每一篇博客

浙公网安备 33010602011771号