1 и 2 - разумные решения, но переопределения write () будет недостаточно.
Проблема в том, что Popen нужны файловые дескрипторы для присоединения к процессу, поэтому файловые объекты Python не работают, они должны быть на уровне ОС. Чтобы решить эту проблему, у вас должен быть объект Python, имеющий дескриптор файла уровня os. Единственный способ решить эту проблему - использовать каналы, так что у вас есть дескриптор файла уровня os для записи. Но тогда вам нужен другой поток, который сидит и опрашивает этот канал, чтобы что-то было прочитано, чтобы он мог его записать. (Так что это более строго реализация 2, поскольку она делегирует ведение журнала).
Сказано и сделано:
import io
import logging
import os
import select
import subprocess
import time
import threading
LOG_FILENAME = 'output.log'
logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG)
class StreamLogger(io.IOBase):
def __init__(self, level):
self.level = level
self.pipe = os.pipe()
self.thread = threading.Thread(target=self._flusher)
self.thread.start()
def _flusher(self):
self._run = True
buf = b''
while self._run:
for fh in select.select([self.pipe[0]], [], [], 0)[0]:
buf += os.read(fh, 1024)
while b'\n' in buf:
data, buf = buf.split(b'\n', 1)
self.write(data.decode())
time.sleep(1)
self._run = None
def write(self, data):
return logging.log(self.level, data)
def fileno(self):
return self.pipe[1]
def close(self):
if self._run:
self._run = False
while self._run is not None:
time.sleep(1)
os.close(self.pipe[0])
os.close(self.pipe[1])
Таким образом, этот класс запускает канал уровня ОС, к которому Попен может прикрепить stdin / out / error для подпроцесса. Он также запускает поток, который опрашивает другой конец этого канала один раз в секунду для записи в журнал, который затем регистрируется модулем регистрации.
Возможно, этот класс должен реализовывать больше вещей для полноты, но в любом случае он работает.
Пример кода:
with StreamLogger(logging.INFO) as out:
with StreamLogger(logging.ERROR) as err:
subprocess.Popen("ls", stdout=out, stderr=err, shell=True)
output.log заканчивается примерно так:
INFO:root:output.log
INFO:root:streamlogger.py
INFO:root:and
INFO:root:so
INFO:root:on
Протестировано с Python 2.6, 2.7 и 3.1.
Я думаю, что любая реализация 1 и 3 должна использовать аналогичные методы. Это немного усложняется, но если вы не сможете правильно вести журнал команд Popen, у меня нет лучшей идеи).