繁体   English   中英

python 日志记录是否支持多处理?

[英]Does python logging support multiprocessing?

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

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

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)写入日志以增加冲突的机会。 但根本没有冲突。

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

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

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

它归结为检查您的管道缓冲区大小,在 linux 上定义在/usr/include/linux/limits.h并且是 4096 字节。 对于其他操作系统,您可以在这里找到一个很好的列表。

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

这个问题中已经有很多解决方案,所以我不会在这里添加我自己的解决方案。

更新:烧瓶和多处理

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

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

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

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

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

根据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)

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

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)

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

QueueHandler在 Python 3.2+ 中是原生的,并且可以安全地处理多处理日志记录。

Python 文档有两个完整的示例: 从多个进程记录到单个文件

对于那些使用 Python < 3.2 的用户,只需将QueueHandler复制到您自己的代码中: https: //gist.github.com/v

每个进程(包括父进程)将其日志记录放在Queue上,然后一个listener器线程或进程(为每个进程提供一个示例)拾取这些并将它们全部写入文件 - 没有损坏或乱码的风险。

注意:这个问题基本上与在 Python 中使用多处理时如何记录? 所以我从那个问题中复制了我的答案,因为我很确定这是目前最好的解决方案。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM