运行子进程并将输出打印到日志记录

Kos*_*nov 30 python logging subprocess

我正在寻找从python调用shell脚本的方法,并使用日志记录将他们的stdout和stderr写入文件.这是我的代码:

import logging
import tempfile
import shlex
import os

def run_shell_command(command_line):
    command_line_args = shlex.split(command_line)

    logging.info('Subprocess: \"' + command_line + '\"')

    process_succeeded = True
    try:
        process_output_filename = tempfile.mktemp(suffix = 'subprocess_tmp_file_')
        process_output = open(process_output_filename, 'w')

        command_line_process = subprocess.Popen(command_line_args,\
                                                stdout = process_output,\
                                                stderr = process_output)
        command_line_process.wait()
        process_output.close()

        process_output = open(process_output_filename, 'r')
        log_subprocess_output(process_output)
        process_output.close()

        os.remove(process_output_filename)
    except:
        exception = sys.exc_info()[1]
        logging.info('Exception occured: ' + str(exception))
        process_succeeded = False

    if process_succeeded:
        logging.info('Subprocess finished')
    else:
        logging.info('Subprocess failed')

    return process_succeeded
Run Code Online (Sandbox Code Playgroud)

我确信无需创建临时文件来存储流程输出就可以实现.有任何想法吗?

jfs*_*jfs 27

您可以尝试直接传递管道而不缓冲内存中的整个子进程输出:

from subprocess import Popen, PIPE, STDOUT

process = Popen(command_line_args, stdout=PIPE, stderr=STDOUT)
with process.stdout:
    log_subprocess_output(process.stdout)
exitcode = process.wait() # 0 means success
Run Code Online (Sandbox Code Playgroud)

在哪里log_subprocess_output()可能看起来像:

def log_subprocess_output(pipe):
    for line in iter(pipe.readline, b''): # b'\n'-separated lines
        logging.info('got line from subprocess: %r', line)
Run Code Online (Sandbox Code Playgroud)

  • 如果预计输出很大,那么这可以处理子进程:http://thraxil.org/users/anders/posts/2008/03/13/Subprocess-Hanging-PIPE-is-your-enemy/ (3认同)
  • @IoannisFilippidis:答案中的代码适用于任何输出,无论它有多大.您链接的文章不适用于此处. (3认同)
  • @mark:你对'p`这个名字是正确的.这意味着`command_line_process`对于一个小代码示例来说太长了,我已经将它们重命名为`process`你的代码`log.info(p.stdout)`没有意义(这不是我的答案)这就是为什么我用一个完整的代码示例提出了一个新问题(你的代码中可能还有其他错误)你的链接会在记录任何内容之前在内存中累积整个子进程输出.我的答案中的代码允许在子进程仍在运行时记录*而不会耗尽所有可用内存. (2认同)

Bak*_*riu 24

我确信无需创建临时文件来存储流程输出就可以实现

您只需检查文档Popen,特别是关于stdoutstderr:

stdin,stdout并分别stderr指定执行程序的标准输入,标准输出和标准错误文件句柄.有效值是PIPE,现有文件描述符(正整数),现有文件对象和None.PIPE表示应创建到子项的新管道.使用默认设置 None,不会发生重定向; 子项的文件句柄将从父项继承.另外,stderr可以是STDOUT,这表示stderr来自子进程的数据应该被捕获到与之相同的文件句柄中stdout.

因此,您可以看到您可以使用文件对象或PIPE值.这允许您使用该communicate()方法来检索输出:

from StringIO import StringIO
process = subprocess.Popen(arguments, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
output, error = process.communicate()
log_subprocess_output(StringIO(output))
Run Code Online (Sandbox Code Playgroud)

我将你的代码重写为:

import shlex
import logging
import subprocess
from StringIO import StringIO

def run_shell_command(command_line):
    command_line_args = shlex.split(command_line)

    logging.info('Subprocess: "' + command_line + '"')

    try:
        command_line_process = subprocess.Popen(
            command_line_args,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
        )

        process_output, _ =  command_line_process.communicate()

        # process_output is now a string, not a file,
        # you may want to do:
        # process_output = StringIO(process_output)
        log_subprocess_output(process_output)
    except (OSError, CalledProcessError) as exception:
        logging.info('Exception occured: ' + str(exception))
        logging.info('Subprocess failed')
        return False
    else:
        # no exception was raised
        logging.info('Subprocess finished')

    return True
Run Code Online (Sandbox Code Playgroud)

  • 您的代码示例的log_suprocess_output是什么样的? (3认同)
  • 如果你不需要在文件里面寻找; [管道可以按原样用于记录子进程'输出](http://stackoverflow.com/a/21978778/4279) (2认同)
  • 这会导致整个“stdout”/“stderr”内容作为字符串驻留在内存中,这对于需要大量写入“stdout”/“stderr”的进程来说可能并不理想。 (2认同)

tha*_*s.a 8

这对我有用:

from subprocess import Popen, PIPE, STDOUT

command = f"shell command with arguments"
process = Popen(command, shell=True, stdout=PIPE, stderr=STDOUT)

with process.stdout:
    for line in iter(process.stdout.readline, b''):
        print(line.decode("utf-8").strip())
Run Code Online (Sandbox Code Playgroud)

异常处理

from subprocess import Popen, PIPE, STDOUT, CalledProcessError

command = f"shell command with arguments"
process = Popen(command, shell=True, stdout=PIPE, stderr=STDOUT)

with process.stdout:
    try:
        for line in iter(process.stdout.readline, b''):
            print(line.decode("utf-8").strip())
            
    except CalledProcessError as e:
        print(f"{str(e)}")
Run Code Online (Sandbox Code Playgroud)


get*_*one 6

我试图在check_call和上实现相同的目标check_ouput。我发现这个解决方案有效。

import logging
import threading
import os
import subprocess

logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO)

class LogPipe(threading.Thread):

    def __init__(self, level):
        """Setup the object with a logger and a loglevel
        and start the thread
        """
        threading.Thread.__init__(self)
        self.daemon = False
        self.level = level
        self.fdRead, self.fdWrite = os.pipe()
        self.pipeReader = os.fdopen(self.fdRead)
        self.start()

    def fileno(self):
        """Return the write file descriptor of the pipe"""
        return self.fdWrite

    def run(self):
        """Run the thread, logging everything."""
        for line in iter(self.pipeReader.readline, ''):
            logging.log(self.level, line.strip('\n'))

        self.pipeReader.close()

    def close(self):
        """Close the write end of the pipe."""
        os.close(self.fdWrite)

   def write(self):
       """If your code has something like sys.stdout.write"""
       logging.log(self.level, message)

   def flush(self):
       """If you code has something like this sys.stdout.flush"""
       pass
Run Code Online (Sandbox Code Playgroud)

实施后,我执行了以下步骤:

try:
    # It works on multiple handlers as well
    logging.basicConfig(handlers=[logging.FileHandler(log_file), logging.StreamHandler()])
    sys.stdout = LogPipe(logging.INFO)
    sys.stderr = LogPipe(logging.ERROR)
...
    subprocess.check_call(subprocess_cmd, stdout=sys.stdout, stderr=sys.stderr)
    export_output = subprocess.check_output(subprocess_cmd, stderr=sys.stderr)
...
finally:
    sys.stdout.close()
    sys.stderr.close()
    # It is neccessary to close the file handlers properly.
    sys.stdout = sys.__stdout__
    sys.stderr = sys.__stderr__
    logging.shutdown()
    os.remove(log_file)
Run Code Online (Sandbox Code Playgroud)