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

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

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

1 时序图

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

2 实现代码

2.1 日志进程

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

  1. import os
  2. import logging
  3. import logging.handlers
  4. import traceback
  5. import cPickle
  6. import struct
  7. import SocketServer
  8. from multiprocessing import Process
  9.  
  10. class LogRecordStreamHandler(SocketServer.StreamRequestHandler):
  11. def handle(self):
  12. while True:
  13. try:
  14. chunk = self.connection.recv(4)
  15. if len(chunk) < 4:
  16. break
  17. slen = struct.unpack(">L", chunk)[0]
  18. chunk = self.connection.recv(slen)
  19. while len(chunk) < slen:
  20. chunk = chunk + self.connection.recv(slen - len(chunk))
  21. obj = self.unpickle(chunk)
  22. record = logging.makeLogRecord(obj)
  23. self.handle_log_record(record)
  24.  
  25. except:
  26. break
  27.  
  28. @classmethod
  29. def unpickle(cls, data):
  30. return cPickle.loads(data)
  31.  
  32. def handle_log_record(self, record):
  33. if self.server.logname is not None:
  34. name = self.server.logname
  35. else:
  36. name = record.name
  37. logger = logging.getLogger(name)
  38. logger.handle(record)
  39.  
  40. class LogRecordSocketReceiver(SocketServer.ThreadingTCPServer):
  41. allow_reuse_address = 1
  42.  
  43. def __init__(self, host='localhost', port=logging.handlers.DEFAULT_TCP_LOGGING_PORT, handler=LogRecordStreamHandler):
  44. SocketServer.ThreadingTCPServer.__init__(self, (host, port), handler)
  45. self.abort = 0
  46. self.timeout = 1
  47. self.logname = None
  48.  
  49. def serve_until_stopped(self):
  50. import select
  51. abort = 0
  52. while not abort:
  53. rd, wr, ex = select.select([self.socket.fileno()], [], [], self.timeout)
  54. if rd:
  55. self.handle_request()
  56. abort = self.abort
  57.  
  58. def _log_listener_process(log_format, log_time_format, log_file):
  59. log_file = os.path.realpath(log_file)
  60. logging.basicConfig(level=logging.DEBUG, format=log_format, datefmt=log_time_format, filename=log_file, filemode='a+')
  61.  
  62. # Console log
  63. console = logging.StreamHandler()
  64. console.setLevel(logging.INFO)
  65. console.setFormatter(logging.Formatter(fmt=log_format, datefmt=log_time_format))
  66. logging.getLogger().addHandler(console)
  67.  
  68. tcp_server = LogRecordSocketReceiver()
  69.  
  70. logging.debug('Log listener process started ...')
  71. tcp_server.serve_until_stopped()

  关键点:

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

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

2.2 其他进程

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

  1. class LogHelper:
  2. # 默认日志存储路径(相对于当前文件路径)
  3. default_log_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'logs')
  4.  
  5. # 记录当前实际的日志所在目录
  6. current_log_path = ''
  7.  
  8. # 记录当前实际的日志完整路径
  9. current_log_file = ''
  10.  
  11. # 日志文件内容格式
  12. log_format = '[%(asctime)s.%(msecs)03d][%(processName)s][%(levelname)s][%(filename)s:%(lineno)d] %(message)s'
  13.  
  14. # 日志中时间格式
  15. log_time_format = '%Y%m%d %H:%M:%S'
  16.  
  17. # 日志进程
  18. log_process = None
  19.  
  20. def __init__(self):
  21. pass
  22.  
  23. @staticmethod
  24. def print_console_log(level, message):
  25. print '--------------------------------------------------'
  26. if level == logging.WARN:
  27. level_str = '[WARN]'
  28. elif level == logging.ERROR:
  29. level_str = '[ERROR]'
  30. elif level == logging.FATAL:
  31. level_str = '[FATAL]'
  32. else:
  33. level_str = '[INFO]'
  34. print '\t%s %s' % (level_str, message)
  35. print '--------------------------------------------------'
  36.  
  37. @staticmethod
  38. def init(clear_logs=True, log_path=''):
  39. #
  40. console = logging.StreamHandler()
  41. console.setLevel(logging.FATAL)
  42. logging.getLogger().addHandler(console)
  43.  
  44. try:
  45. # 如果外部没有指定日志存储路径则默认在common同级路径存储
  46. if log_path == '':
  47. log_path = LogHelper.default_log_path
  48. if not os.path.exists(log_path):
  49. os.makedirs(log_path)
  50. LogHelper.current_log_path = log_path
  51.  
  52. # 清理旧的日志并初始化当前日志路径
  53. if clear_logs:
  54. LogHelper.clear_old_log_files()
  55. LogHelper.current_log_file = LogHelper._get_latest_log_file()
  56.  
  57. socket_handler = logging.handlers.SocketHandler('localhost', logging.handlers.DEFAULT_TCP_LOGGING_PORT)
  58. logging.getLogger().setLevel(logging.DEBUG)
  59. logging.getLogger().addHandler(socket_handler)
  60.  
  61. #
  62. LogHelper.start()
  63.  
  64. except Exception, ex:
  65. LogHelper.print_console_log(logging.FATAL, 'init() exception: %s' % str(ex))
  66. traceback.print_exc()
  67.  
  68. @staticmethod
  69. def start():
  70. if LogHelper.log_process is None:
  71. LogHelper.log_process = Process(target=_log_listener_process, name='LogRecorder', args=(LogHelper.log_format, LogHelper.log_time_format, LogHelper.current_log_file))
  72. LogHelper.log_process.start()
  73. else:
  74. pass
  75.  
  76. @staticmethod
  77. def stop():
  78. if LogHelper.log_process is None:
  79. pass
  80. else:
  81. LogHelper.log_process.terminate()
  82. LogHelper.log_process.join()
  83.  
  84. @staticmethod
  85. def _get_latest_log_file():
  86. latest_log_file = ''
  87. try:
  88. if os.path.exists(LogHelper.current_log_path):
  89. for maindir, subdir, file_name_list in os.walk(LogHelper.current_log_path):
  90. for file_name in file_name_list:
  91. apath = os.path.join(maindir, file_name)
  92. if apath > latest_log_file:
  93. latest_log_file = apath
  94.  
  95. if latest_log_file == '':
  96. latest_log_file = LogHelper.current_log_path + os.sep + 'system_'
  97. latest_log_file += time.strftime("%Y%m%d_%H%M%S", time.localtime(time.time())) + '.log'
  98.  
  99. except Exception, ex:
  100. logging.error('EXCEPTION: %s' % str(ex))
  101. traceback.print_exc()
  102.  
  103. finally:
  104. return latest_log_file
  105.  
  106. @staticmethod
  107. def get_log_file():
  108. return LogHelper.current_log_file
  109.  
  110. @staticmethod
  111. def clear_old_log_files():
  112. if not os.path.exists(LogHelper.current_log_path):
  113. logging.warning('clear_old_log_files() Not exist: %s' % LogHelper.current_log_path)
  114. return
  115.  
  116. try:
  117. for maindir, subdir, file_name_list in os.walk(LogHelper.current_log_path):
  118. for file_name in file_name_list:
  119. apath = os.path.join(maindir, file_name)
  120. if apath != LogHelper.current_log_file:
  121. logging.info('DEL -> %s' % str(apath))
  122. os.remove(apath)
  123. else:
  124. with open(LogHelper.current_log_file, 'w') as f:
  125. f.write('')
  126.  
  127. logging.debug('Clear log done.')
  128.  
  129. except Exception, ex:
  130. logging.error('EXCEPTION: %s' % str(ex))
  131. 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. tinymce编辑器从word粘贴公式

    很多时候我们用一些管理系统的时候,发布新闻.公告等文字类信息时,希望能很快的将word里面的内容直接粘贴到富文本编辑器里面,然后发布出来.减少排版复杂的工作量. 下面是借用百度doc 来快速实现这个w ...

  2. imp

    imp 重载模块功能 from imp import reload 当在shell中使用一个模块,原始代码改了,要使用新的还得退出shell重新载入一次,这样的话原来的变量就都不在了 这时候可以使用i ...

  3. 查找 Linux 发行版名称、版本和内核详细信息

    作者: Sk 译者: LCTT geekpi | 2019-08-26 11:40   收藏: 1 本指南介绍了如何查找 Linux 发行版名称.版本和内核详细信息.如果你的 Linux 系统有 GU ...

  4. HDU4254 A Famous Game

    luogu嘟嘟嘟 这题刚开始特别容易理解错:直接枚举所有\(n + 1\)种情况,然后算哪一种情况合法,再统计答案. 上述思想的问题就在于我们从已知的结果出发,默认这种每一种情况中取出\(q\)个红球 ...

  5. docker安装mysql5.7 数据挂载

    docker安装mysql5.7,并数据卷挂载到主机 # docker 中下载 mysql docker pull mysql:5.7 #启动 docker run --name mysql3306 ...

  6. codeforces164A

    Variable, or There and Back Again CodeForces - 164A Life is not easy for the perfectly common variab ...

  7. MyBatis:Error evaluating expression ''''. Return value () was not iterable错误

    Error evaluating expression ''''.  Return value () was not iterable 出现原因:xml文件中遍历List 时,该参数的实际值为非Lis ...

  8. OpenFOAM 中边界条件的设定【转载】

    转载自:http://blog.sina.com.cn/s/blog_a0b4201d0102v7jt.html 用习惯了FLUENT的操作界面,再使用OpenFOAM就会觉得非常繁琐.遇到的第一个问 ...

  9. android通用的UUID唯一标示符

    http://stackoverflow.com/questions/2785485/is-there-a-unique-android-device-id 版权声明:本文为博主原创文章,未经博主允许 ...

  10. Arts打卡第5周

    Algorithm.主要是为了编程训练和学习. 每周至少做一个 leetcode 的算法题(先从Easy开始,然后再Medium,最后才Hard). 进行编程训练,如果不训练你看再多的算法书,你依然不 ...