Monitor Python subprocess' output streams in real-time

Here is my (or the) solution to monitor stdout and stderr of a subprocess in Python 3. Python’s documentation recommends using subprocess.Popen.communicate() rather than subprocess.Popen.stdout.read() to avoid blocking the application, but you only get to use the output once the subprocess ends. Things get complicated if you need to monitor both stdout and stderr. I use select.select() to solve this. I don’t think it works with MS Windows but that’s your problem, not mine :).

import subprocess
import logging
import select

p = subprocess.Popen(["spam", "--verbose"],
                     stdout=subprocess.PIPE,
                     stderr=subprocess.PIPE)
outputs = {p.stdout: {"EOF": False, "logcmd": logging.info},
           p.stderr: {"EOF": False, "logcmd": logging.error}}
while (not outputs[p.stdout]["EOF"] and
       not outputs[p.stderr]["EOF"]):
    for fd in select.select([p.stdout, p.stderr], [], [])[0]:
        output = fd.readline()
        if output == b"":
            outputs[fd]["EOF"] = True
        else:
            outputs[fd]["logcmd"](output.decode().rstrip())

Resources:

  • [http://docs.python.org/py3k/library/logging.html](The logging module)
  • [http://docs.python.org/py3k/library/select.html](The select module)
  • [http://docs.python.org/py3k/library/subprocess.html](The subprocess module)
  • [http://docs.python.org/py3k/howto/sockets.html](Socket Programming HOWTO)

Edit : Also read subprocess source code located at /usr/lib/python3.2/subprocess.py. You will learn how to do it with the threading module.


2012-06-02 edit

Mr Eric Pruitt was kind enough to share his improvement on this code. Thank you sir!

Date: 2012-06-02
From: Eric Pruitt
Website: https://www.codevat.com/

Hey Alexandre,

I was looking for some information on logging subprocess output when I found your post. I re-wrote the code in a little more compact form, and I thought you might be interested. Here is my version:

def iterate_fds(handles, functions):
    methods = dict(zip(handles, functions))
    while methods:
        for handle in select.select(methods.keys(), tuple(), tuple())[0]:
            line = handle.readline()
            if line:
                methods[handle](line[:-1])
            else:
                methods.pop(handle)

In my program, I am calling the code like this:

iterate_fds((rsync.stderr, rsync.stdout), (logging.warning, logging.info))

Eric

Alexandre de Verteuil
Alexandre de Verteuil
Senior Solutions Architect

I teach people how to see the matrix metrics.
Monkeys and sunsets make me happy.

Related