【问题标题】:Python Subprocess - filter out loggingPython Subprocess - 过滤掉日志记录
【发布时间】:2018-03-09 13:50:02
【问题描述】:

Python 3.6

我想从使用subprocess 模块运行的子进程中获取所有输入。我可以轻松地将此输出通过管道传输到日志文件,而且效果很好。

但是,我想过滤掉很多行(来自我无法控制的模块的大量嘈杂输出)。

尝试 1

def run_command(command, log_file):
    process = subprocess.Popen(command, stdout=subprocess.PIPE,
                               stderr=subprocess.STDOUT, bufsize=1,
                               universal_newlines=True)
    while True:
        output = process.stdout.readline()
        if output == '' and process.poll() is not None:
            break
        if output and not_noisy_line(output):
            log_file.write(output)
            log_file.flush()
    return process.poll()

但这在我的子进程和输出之间引入了竞争条件。

尝试 2

我创建了一个新方法和一个类来包装日志记录。

def run_command(command, log_file):
    process = subprocess.run(command, stdout=QuiteLogger(log_file), stderr=QuiteLogger(log_file), timeout=120)
    return process.returncode

class QuiteLogger(io.TextIOWrapper):
    def write(self, data, encoding=sys.getdefaultencoding()):
        data = filter(data)
        super().write(data)

然而,这只是完全跳过了我的过滤器功能,我的 write 方法根本不被子进程调用。 (如果我打电话给QuietLogger().write('asdasdsa'),它会通过过滤器)

有什么线索吗?

【问题讨论】:

  • “我的子进程和输出之间的竞争条件”是什么意思?这种竞争条件是如何表现出来的?
  • 两个进程都进入睡眠状态。

标签: python python-3.x subprocess python-3.6


【解决方案1】:

这是一个有趣的情况,文件对象抽象部分崩溃了。您的解决方案不起作用的原因是,subprocess 实际上并未使用您的QuietLogger,而是从中获取原始文件编号(然后将其重新打包为io.TextIOWrapper 对象)。

我不知道这是否是 subprocess 处理方式的内在限制,依赖于操作系统支持,或者这只是 Python 设计中的一个错误,但为了实现你想要的,你需要使用标准的subprocess.PIPE,然后滚动你自己的文件编写器。

如果您可以等待子流程完成,则可以轻松完成,使用subprocess.run,然后从CompletedProcess (p) 对象中挑选流:

p = subprocess.run(command, stdout=subprocess.PIPE, universal_newlines=True)

data = filter(p.stdout)

with open(logfile, 'w') as f:
    f.write(data)

如果您必须在生成输出时使用它(因此,您不能等待subprocess 结束),最简单的方法是诉诸subprocess.Popen 和线程:

import subprocess
import threading

logfile ='tmp.txt'
filter_passed = lambda line: line[:3] != 'Bad'
command = ['my_cmd', 'arg']

def writer(p, logfile):
    with open(logfile, 'w') as f:
        for line in p.stdout:
            if filter_passed(line):
                f.write(line)

p = subprocess.Popen(command, stdout=subprocess.PIPE, universal_newlines=True)

t = threading.Thread(target=writer, args=(p,logfile))
t.start()

t.join()

【讨论】:

  • 线程看起来很不错。无论如何我要在您的第二个示例中引入超时吗?如果我的进程超时,我想杀死它,然后让我的进程重新启动。
  • 不需要引入超时来处理打印。如果你杀死子进程,stdout 将被关闭,线程将完成并加入主线程。如果您的问题是如何生成度量和生成超时,那是另一个单独的问题,那应该是另一个 SO 条目。 (不过,实现起来并不难。)
【解决方案2】:

[编辑:我的大脑在途中出轨了,我最终回答了另一个问题,而不是实际提出的问题。以下解决方案对于同时写入文件很有用,而不是用于以任何方式使用日志记录模块。但是,因为至少它对此有用,所以我暂时将答案留在原处。]

如果您只是使用线程,而不是单独的进程,那么您只需要一个标准锁。所以你可以尝试类似的东西。

始终可以选择锁定输出文件。我不知道你的操作系统是否支持这样的东西,但通常的 Unix 方法是创建一个锁定文件。基本上,如果文件存在,则等待;否则在写入日志文件之前创建文件,完成后再次删除锁定文件。你可以使用这样的上下文管理器:

import os
import os.path
from time import sleep


class LockedFile():
    def __init__(self, filename, mode):
        self.filename = filename
        self.lockfile = filename + '.lock'
        self.mode = mode

    def __enter__(self):
        while True:
            if os.path.isfile(self.lockfile):
                sleep(0.1)
            else:
                break
        with open(self.lockfile, 'a'):
            os.utime(self.lockfile)
        self.f = open(self.filename, self.mode)
        return self.f

    def __exit__(self, *args):
        self.f.close()
        os.remove(self.lockfile)

# And here's how to use it:
with LockedFile('blorg', 'a') as f:
    f.write('foo\n')

【讨论】:

  • 这与 OP 问题有什么关系?
  • 呃...你知道,你是对的。我是如此专注于“写入文件”和“竞争条件”,以至于我完全忽略了他正在使用日志记录模块这一事实。
猜你喜欢
  • 2015-05-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-02
  • 2013-08-25
  • 1970-01-01
  • 2015-04-03
  • 1970-01-01
相关资源
最近更新 更多