记录多进程处理的输出日志

82

在Python中,当使用multiprocessing.Process类时,有没有一种方法可以记录来自给定Process的标准输出(stdout)?

6个回答

71

最简单的方法可能是仅仅覆盖 sys.stdout。稍微修改一下multiprocessing 手册中的一个例子:

from multiprocessing import Process
import os
import sys

def info(title):
    print title
    print 'module name:', __name__
    print 'parent process:', os.getppid()
    print 'process id:', os.getpid()

def f(name):
    sys.stdout = open(str(os.getpid()) + ".out", "w")
    info('function f')
    print 'hello', name

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    q = Process(target=f, args=('fred',))
    q.start()
    p.join()
    q.join()

然后运行它:

$ ls
m.py
$ python m.py
$ ls
27493.out  27494.out  m.py
$ cat 27493.out 
function f
module name: __main__
parent process: 27492
process id: 27493
hello bob
$ cat 27494.out 
function f
module name: __main__
parent process: 27492
process id: 27494
hello fred


23

我想对 @Mark Rushakoff 的回答补充两点。在调试时,我发现把 open() 调用的 buffering 参数设置为 0 非常有用。

Translated text:

There are only two things I would add to @Mark Rushakoff answer. When debugging, I found it really useful to change the buffering parameter of my open() calls to 0.

sys.stdout = open(str(os.getpid()) + ".out", "a", buffering=0)
否则,疯狂,因为当tail -f输出文件时,结果可能非常间歇性。对于tail -f来说,buffering=0很好。

为了完整起见,请将sys.stderr也重定向。

sys.stderr = open(str(os.getpid()) + "_error.out", "a", buffering=0)

另外,为了方便起见,如果您愿意,您可以将其转储到单独的进程类中。

class MyProc(Process):
    def run(self):
        # Define the logging in run(), MyProc's entry function when it is .start()-ed 
        #     p = MyProc()
        #     p.start()
        self.initialize_logging()

        print 'Now output is captured.'

        # Now do stuff...

    def initialize_logging(self):
        sys.stdout = open(str(os.getpid()) + ".out", "a", buffering=0)
        sys.stderr = open(str(os.getpid()) + "_error.out", "a", buffering=0)

        print 'stdout initialized'

这里有相应的代码片段


5
在尝试这个操作时,我在Python 3.6中遇到了ValueError错误,因为buffering=0只允许在二进制模式下使用。但你可以通过从sys.stdout/stderr的猴子补丁中移除buffering=0,并在print语句中添加flush=True来修复它。即:print("Stdout initialized", flush=True)。它能够顺利地工作。 - JMDE
1
发现打开的文件没有被关闭。这会成为一个问题吗? - Tian

13
您可以设置 sys.stdout = Logger(),其中Logger是一个类,它的write方法(立即执行或累积直到检测到\n)调用logging.info(或您想要记录的其他方式)。在此示例中可以看到它的运行方式。 我不确定您所说的“给定”进程是什么意思(由谁指定,它与其他所有进程有何区别……?),但如果您意思是在实例化进程时知道要单独处理哪个进程,那么您可以将其target函数(仅限该函数)或您在Process子类中覆盖的run方法包装到一个包装器中,以执行这种sys.stdout "重定向",而让其他进程保持不变。
也许如果您能更明确地说明要求,我可以提供更详细的帮助......?

4

以下是用简单直接的方法来捕获 multiprocessing.Processio.TextIOWrapper 的stdout的方式:

import app
import io
import sys
from multiprocessing import Process


def run_app(some_param):
    out_file = open(sys.stdout.fileno(), 'wb', 0)
    sys.stdout = io.TextIOWrapper(out_file, write_through=True)
    app.run()

app_process = Process(target=run_app, args=('some_param',))
app_process.start()
# Use app_process.termninate() for python <= 3.7.
app_process.kill() 

该应用正在运行,因此不允许更改重定向。 - Tian

3

log_to_stderr()函数是最简单的解决方案。

来自PYMOTW

multiprocessing提供了一个方便的模块级函数log_to_stderr(),用于启用日志记录。 它使用logging设置一个记录器对象,并添加处理程序,以便将日志消息发送到标准错误通道。 默认情况下,日志记录级别设置为NOTSET,因此不会产生任何消息。 将不同的级别传递给日志记录器以初始化所需的详细级别。

import logging
from multiprocessing import Process, log_to_stderr

print("Running main script...")

def my_process(my_var):
    print(f"Running my_process with {my_var}...")

# Initialize logging for multiprocessing.
log_to_stderr(logging.DEBUG)

# Start the process.
my_var = 100;
process = Process(target=my_process, args=(my_var,))
process.start()
process.kill()

这段代码将把两个print()语句输出到标准错误(stderr)。

整洁,对我来说比覆盖“stdout”更清晰。谢谢! - Bouncner

0
除了接受的答案之外,如果您的子进程可能会创建子进程,以捕获孙子进程的输出,您可以尝试以下方法:
dst = open("stdout.log", "a")
dst_fd = dst.fileno()
stdout_fd = sys.stdout.fileno()
os.close(stdout_fd)
os.dup2(dst_fd, stdout_fd)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接