因为涉及到进程间互斥与通信问题,因此默认情况下Python中的logging无法在多进程环境下打印日志。但是查询了官方文档可以发现,推荐了一种利用logging.SocketHandler的方案来实现多进程日志打印。

其原理很简单,概括一句话就是说:多个进程将各自环境下的日志通过Socket发送给一个专门打印日志的进程,这样就可以防止多进程打印的冲突与混乱情况。

本文主要记录下SocketHandler真实的用法情况:

1 时序图

简单说明下逻辑:主进程(MainProcess)启动一个专门打印日志的进程(LogReceiverProcess),并且将自己(主进程)环境下的日志都“重定向”给LogReceiverProcess。同理,在后续逻辑中启动的所有工作子进程(WorkerProcess)都做一样的操作,把自己环境下的日志都“重定向”给日志进程去打印。

2 实现代码

2.1 日志进程

  日志进程的代码核心在于要建立一个TCP Server来接收并处理Log record,代码如下:

 import os
import logging
import logging.handlers
import traceback
import cPickle
import struct
import SocketServer
from multiprocessing import Process class LogRecordStreamHandler(SocketServer.StreamRequestHandler):
def handle(self):
while True:
try:
chunk = self.connection.recv(4)
if len(chunk) < 4:
break
slen = struct.unpack(">L", chunk)[0]
chunk = self.connection.recv(slen)
while len(chunk) < slen:
chunk = chunk + self.connection.recv(slen - len(chunk))
obj = self.unpickle(chunk)
record = logging.makeLogRecord(obj)
self.handle_log_record(record) except:
break @classmethod
def unpickle(cls, data):
return cPickle.loads(data) def handle_log_record(self, record):
if self.server.logname is not None:
name = self.server.logname
else:
name = record.name
logger = logging.getLogger(name)
logger.handle(record) class LogRecordSocketReceiver(SocketServer.ThreadingTCPServer):
allow_reuse_address = 1 def __init__(self, host='localhost', port=logging.handlers.DEFAULT_TCP_LOGGING_PORT, handler=LogRecordStreamHandler):
SocketServer.ThreadingTCPServer.__init__(self, (host, port), handler)
self.abort = 0
self.timeout = 1
self.logname = None def serve_until_stopped(self):
import select
abort = 0
while not abort:
rd, wr, ex = select.select([self.socket.fileno()], [], [], self.timeout)
if rd:
self.handle_request()
abort = self.abort def _log_listener_process(log_format, log_time_format, log_file):
log_file = os.path.realpath(log_file)
logging.basicConfig(level=logging.DEBUG, format=log_format, datefmt=log_time_format, filename=log_file, filemode='a+') # Console log
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(logging.Formatter(fmt=log_format, datefmt=log_time_format))
logging.getLogger().addHandler(console) tcp_server = LogRecordSocketReceiver() logging.debug('Log listener process started ...')
tcp_server.serve_until_stopped()

  关键点:

(1)TCPServer的构建逻辑,拆包还原Log记录;

(2)在日志进程中设定好logging记录级别和打印方式,这里除了指定文件存储还添加了Console打印。

2.2 其他进程

  除了日志进程之外的进程,设置logging都“重定向”给日志进程,并且要关闭当前进程的日志在Console打印(默认会显示Warning级别及以上的日志到Console),否则Console上日志展示会有重复凌乱的感觉。

 class LogHelper:
# 默认日志存储路径(相对于当前文件路径)
default_log_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'logs') # 记录当前实际的日志所在目录
current_log_path = '' # 记录当前实际的日志完整路径
current_log_file = '' # 日志文件内容格式
log_format = '[%(asctime)s.%(msecs)03d][%(processName)s][%(levelname)s][%(filename)s:%(lineno)d] %(message)s' # 日志中时间格式
log_time_format = '%Y%m%d %H:%M:%S' # 日志进程
log_process = None def __init__(self):
pass @staticmethod
def print_console_log(level, message):
print '--------------------------------------------------'
if level == logging.WARN:
level_str = '[WARN]'
elif level == logging.ERROR:
level_str = '[ERROR]'
elif level == logging.FATAL:
level_str = '[FATAL]'
else:
level_str = '[INFO]'
print '\t%s %s' % (level_str, message)
print '--------------------------------------------------' @staticmethod
def init(clear_logs=True, log_path=''):
#
console = logging.StreamHandler()
console.setLevel(logging.FATAL)
logging.getLogger().addHandler(console) try:
# 如果外部没有指定日志存储路径则默认在common同级路径存储
if log_path == '':
log_path = LogHelper.default_log_path
if not os.path.exists(log_path):
os.makedirs(log_path)
LogHelper.current_log_path = log_path # 清理旧的日志并初始化当前日志路径
if clear_logs:
LogHelper.clear_old_log_files()
LogHelper.current_log_file = LogHelper._get_latest_log_file() socket_handler = logging.handlers.SocketHandler('localhost', logging.handlers.DEFAULT_TCP_LOGGING_PORT)
logging.getLogger().setLevel(logging.DEBUG)
logging.getLogger().addHandler(socket_handler) #
LogHelper.start() except Exception, ex:
LogHelper.print_console_log(logging.FATAL, 'init() exception: %s' % str(ex))
traceback.print_exc() @staticmethod
def start():
if LogHelper.log_process is None:
LogHelper.log_process = Process(target=_log_listener_process, name='LogRecorder', args=(LogHelper.log_format, LogHelper.log_time_format, LogHelper.current_log_file))
LogHelper.log_process.start()
else:
pass @staticmethod
def stop():
if LogHelper.log_process is None:
pass
else:
LogHelper.log_process.terminate()
LogHelper.log_process.join() @staticmethod
def _get_latest_log_file():
latest_log_file = ''
try:
if os.path.exists(LogHelper.current_log_path):
for maindir, subdir, file_name_list in os.walk(LogHelper.current_log_path):
for file_name in file_name_list:
apath = os.path.join(maindir, file_name)
if apath > latest_log_file:
latest_log_file = apath if latest_log_file == '':
latest_log_file = LogHelper.current_log_path + os.sep + 'system_'
latest_log_file += time.strftime("%Y%m%d_%H%M%S", time.localtime(time.time())) + '.log' except Exception, ex:
logging.error('EXCEPTION: %s' % str(ex))
traceback.print_exc() finally:
return latest_log_file @staticmethod
def get_log_file():
return LogHelper.current_log_file @staticmethod
def clear_old_log_files():
if not os.path.exists(LogHelper.current_log_path):
logging.warning('clear_old_log_files() Not exist: %s' % LogHelper.current_log_path)
return try:
for maindir, subdir, file_name_list in os.walk(LogHelper.current_log_path):
for file_name in file_name_list:
apath = os.path.join(maindir, file_name)
if apath != LogHelper.current_log_file:
logging.info('DEL -> %s' % str(apath))
os.remove(apath)
else:
with open(LogHelper.current_log_file, 'w') as f:
f.write('') logging.debug('Clear log done.') except Exception, ex:
logging.error('EXCEPTION: %s' % str(ex))
traceback.print_exc()

Python中logging在多进程环境下打印日志的更多相关文章

  1. Python 中 logging 日志模块在多进程环境下的使用

    因为我的个人网站 restran.net 已经启用,博客园的内容已经不再更新.请访问我的个人网站获取这篇文章的最新内容,Python 中 logging 日志模块在多进程环境下的使用 使用 Pytho ...

  2. Python中logging模块的基本用法

    在 PyCon 2018 上,Mario Corchero 介绍了在开发过程中如何更方便轻松地记录日志的流程. 整个演讲的内容包括: 为什么日志记录非常重要 日志记录的流程是怎样的 怎样来进行日志记录 ...

  3. python中logging模块的用法

    很多程序都有记录日志的需求,并且日志中包含的信息即有正常的程序访问日志,还可能有错误.警告等信息输出,python的logging模块提供了标准的日志接口,你可以通过它存储各种格式的日志,loggin ...

  4. 尚学python课程---11、linux环境下安装python注意

    尚学python课程---11.linux环境下安装python注意 一.总结 一句话总结: 准备安装依赖包:zlib.openssl:yum install zlib* openssl*:pytho ...

  5. 重写NSLog,Debug模式下打印日志和当前行数

    在pch文件中加入以下命令,NSLog在真机测试中就不会打印了 //重写NSLog,Debug模式下打印日志和当前行数 #if DEBUG #define NSLog(FORMAT, ...) fpr ...

  6. python中logging模块的一些简单用法

    用Python写代码的时候,在想看的地方写个print xx 就能在控制台上显示打印信息,这样子就能知道它是什么了,但是当我需要看大量的地方或者在一个文件中查看的时候,这时候print就不大方便了,所 ...

  7. python中logging模块使用

    1.logging模块使用场景 在写程序的时候,尤其是大型的程序,在程序中加入日志系统是必不可少的,它能记录很多的信息.刚刚接触python的时候肯定都在用print来输出信息,这样是最简单的输出,正 ...

  8. python中logging的使用

    什么是日志: 日志是一种可以追踪某些软件运行时所发生事件的方法 软件开发人员可以向他们的代码中调用日志记录相关的方法来表明发生了某些事情 一个事件可以用一个可包含可选变量数据的消息来描述 此外,事件也 ...

  9. Python中的输入(input)和输出打印

    目录 最简单的打印 打印数字 打印字符 字符串的格式化输出 python中让输出不换行 以下的都是在Python3.X环境下的 使用 input 函数接收用户的输入,返回的是 str 字符串 最简单的 ...

随机推荐

  1. Java基础系列 - 子类继承父类,调用父类的构造函数

    package com.test7; public class test7 { public static void main(String[] args) { Son son = new Son(1 ...

  2. elasticsearch 动态映射

    https://www.elastic.co/guide/cn/elasticsearch/guide/current/dynamic-mapping.html#dynamic-mapping当 El ...

  3. Linux系统下vi编辑器的一些简单使用操作

    Linux系统安装vi编辑器 打开终端,输入: sudo apt install vim 然后输入Y等待安装即可 Linux系统下使用vi编辑器的使用 安装好vim后,终端中输入vim即可进入vim编 ...

  4. js学习笔记(1)

    前言 ​ 因为后期的软件工程组队作业需要js,在纪华裕大佬的带领下(我觉得他好像更喜欢纪华裕这个名字),我开始了js的学习.其实这篇博客应该在两天前的晚上就发出来了,因为忙着写个人编程,拖到了现在,组 ...

  5. javascript创建对象的几种方式?

    javascript创建对象简单的说,无非就是使用内置对象或各种自定义对象,当然还可以用JSON:但写法有很多种,也能混合使用. 1.对象字面量的方式 person={ firstname:" ...

  6. 三种Timer使用

    System.Windows.Forms.Timer,  System.Threading.Timer,  System.Timer,三种Timer使用如下 第一种:System.Windows.Fo ...

  7. OpenJudge计算概论-最长单词2

    /*======================================================================== 最长单词2 总时间限制: 1000ms 内存限制: ...

  8. PHP无限级树形结构算法(递归和引用)

    测试数组 $array = [ [, , 'name' => '这是主类'], [, , 'name' => '这是主类'], [, , 'name' => '父级为1子类'], [ ...

  9. Golang基础笔记

    <基础> Go语言中的3个关键字用于标准的错误处理流程: defer,panic,recover. 定义一个名为f 的匿名函数: Go 不支持继承和重载. Go的goroutine概念:使 ...

  10. ctl +→ = MAC 触控板三指手势

    我只想发一个这个:一直用外接显示器,但是不舍得抛弃全屏程序的切换,即:触控板三指手势. 期间各种百度无果,最多找到出最多的是触发角: 今天终于发现了:ctl +→  =  MAC 触控板三指手势 (外 ...