【问题标题】:Does python logging support multiprocessing?python日志记录是否支持多处理?
【发布时间】:2018-06-06 17:19:59
【问题描述】:

有人告诉我,日志记录不能在多处理中使用。你必须做并发控制,以防多处理弄乱日志。

但我做了一些测试,在多处理中使用登录似乎没有问题

import time
import logging
from multiprocessing import Process, current_process, pool


# setup log
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                    datefmt='%a, %d %b %Y %H:%M:%S',
                    filename='/tmp/test.log',
                    filemode='w')


def func(the_time, logger):
    proc = current_process()
    while True:
        if time.time() >= the_time:
            logger.info('proc name %s id %s' % (proc.name, proc.pid))
            return



if __name__ == '__main__':

    the_time = time.time() + 5

    for x in xrange(1, 10):
        proc = Process(target=func, name=x, args=(the_time, logger))
        proc.start()

从代码中可以看出。

我故意让子进程在同一时刻(启动后5s)写日志,以增加冲突的机会。但是完全没有冲突。

所以我的问题是我们可以在多处理中使用日志记录吗? 为什么这么多帖子说我们不能?

【问题讨论】:

    标签: python python-multiprocessing


    【解决方案1】:

    从多个进程写入单个文件是不安全的。

    根据https://docs.python.org/3/howto/logging-cookbook.html#logging-to-a-single-file-from-multiple-processes

    虽然日志记录是线程安全的,并且从 支持单个进程中的多个线程,记录到单个进程中 不支持来自多个进程的文件,因为没有 跨多个序列化对单个文件的访问的标准方法 Python 中的进程。

    一种可能的解决方案是让每个进程写入自己的文件。您可以通过编写自己的处理程序将进程 pid 添加到文件末尾来实现这一点:

    import logging.handlers
    import os
    
    
    class PIDFileHandler(logging.handlers.WatchedFileHandler):
    
        def __init__(self, filename, mode='a', encoding=None, delay=0):
            filename = self._append_pid_to_filename(filename)
            super(PIDFileHandler, self).__init__(filename, mode, encoding, delay)
    
        def _append_pid_to_filename(self, filename):
            pid = os.getpid()
            path, extension = os.path.splitext(filename)
            return '{0}-{1}{2}'.format(path, pid, extension)
    

    那你只需拨打addHandler:

    logger = logging.getLogger('foo')
    fh = PIDFileHandler('bar.log')
    logger.addHandler(fh)
    

    【讨论】:

      【解决方案2】:

      正如 Matino 正确解释的那样:登录多进程设置并不安全,因为多个进程(它们对现有的其他进程一无所知)正在写入同一个文件,可能会相互干扰。

      现在发生的情况是每个进程都持有一个打开的文件句柄,并对该文件进行“追加写入”。问题是在什么情况下追加写入是“原子的”(也就是说,不能被例如另一个进程写入同一个文件并混合他的输出而中断)。这个问题适用于每一种编程语言,因为最终它们会对内核进行系统调用。 This answer 回答在什么情况下共享日志文件是可以的。

      它归结为检查您的管道缓冲区大小,在/usr/include/linux/limits.h 中定义的 linux 上是 4096 字节。对于其他操作系统,您会发现 here 是一个不错的列表。

      这意味着:如果您的日志行小于 4'096 字节(如果在 Linux 上),那么附加是安全的,如果磁盘是直接连接的(即中间没有网络)。但有关更多详细信息,请查看我的答案中的第一个链接。要对此进行测试,您可以使用不同长度的 logger.info('proc name %s id %s %s' % (proc.name, proc.pid, str(proc.name)*5000))。以 5000 为例,我已经在 /tmp/test.log 中混淆了日志行。

      this question已经有不少解决方案了,这里就不添加自己的解决方案了。

      更新:Flask 和多处理

      如果由 uwsgi 或 nginx 托管,像 flask 之类的 Web 框架将在多个 worker 中运行。在这种情况下,多个进程可能会写入一个日志文件。会不会有问题?

      flask 中的错误处理是通过 stdout/stderr 完成的,然后由 web 服务器(uwsgi、nginx 等)处理,需要注意日志以正确的方式写入(参见例如 [this flask+nginx example ])(http://flaviusim.com/blog/Deploying-Flask-with-nginx-uWSGI-and-Supervisor/),可能还会添加进程信息,以便您可以将错误行与进程相关联。来自flasks doc

      默认情况下,从 Flask 0.11 开始,错误会自动记录到您的网络服务器的日志中。然而,警告不是。

      因此,如果您使用 warn 并且消息超出管道缓冲区大小,您仍然会遇到混合日志文件的问题。

      【讨论】:

      • 还有一个问题。如果由 uwsgi 或 nginx 托管,像烧瓶这样的 Web 框架将在多个工作人员中运行。在这种情况下,多个进程可能会写入一个日志文件。会不会有问题?
      • @KramerLi:我已经在我的回答中的新部分下回答了你的问题
      【解决方案3】:

      使用队列正确处理并发,同时通过管道将所有内容提供给父进程,从而从错误中恢复。

      from logging.handlers import RotatingFileHandler
      import multiprocessing, threading, logging, sys, traceback
      
      class MultiProcessingLog(logging.Handler):
          def __init__(self, name, mode, maxsize, rotate):
              logging.Handler.__init__(self)
      
              self._handler = RotatingFileHandler(name, mode, maxsize, rotate)
              self.queue = multiprocessing.Queue(-1)
      
              t = threading.Thread(target=self.receive)
              t.daemon = True
              t.start()
      
          def setFormatter(self, fmt):
              logging.Handler.setFormatter(self, fmt)
              self._handler.setFormatter(fmt)
      
          def receive(self):
              while True:
                  try:
                      record = self.queue.get()
                      self._handler.emit(record)
                  except (KeyboardInterrupt, SystemExit):
                      raise
                  except EOFError:
                      break
                  except:
                      traceback.print_exc(file=sys.stderr)
      
          def send(self, s):
              self.queue.put_nowait(s)
      
          def _format_record(self, record):
               # ensure that exc_info and args
               # have been stringified.  Removes any chance of
               # unpickleable things inside and possibly reduces
               # message size sent over the pipe
              if record.args:
                  record.msg = record.msg % record.args
                  record.args = None
              if record.exc_info:
                  dummy = self.format(record)
                  record.exc_info = None
      
              return record
      
          def emit(self, record):
              try:
                  s = self._format_record(record)
                  self.send(s)
              except (KeyboardInterrupt, SystemExit):
                  raise
              except:
                  self.handleError(record)
      
          def close(self):
              self._handler.close()
              logging.Handler.close(self)
      

      处理程序从父进程执行所有文件写入,并仅使用一个线程接收从子进程传递的消息

      【讨论】:

      • 看看这个类的使用例子也不错。
      • 是的,你能举个例子吗
      • 这个答案似乎是来自this answer 的复制粘贴。请注意,这不适用于 Windows。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-04-12
      • 2016-08-30
      • 1970-01-01
      • 2021-04-29
      • 1970-01-01
      • 2017-04-15
      • 1970-01-01
      相关资源
      最近更新 更多