Выход перенаправления подпроцессов' (stdout и stderr) в модуль лесозаготовки


Я работаю на Python-скрипт, и я искал способ, чтобы перенаправить стандартный вывод и стандартный поток ошибок в подпроцесс на входе модуля. Подпроцесс создается с помощью подпроцесса.звоните() метод.

Трудность с которой я столкнулся заключается в том, что с подпроцессом я могу перенаправить стандартный вывод и стандартный вывод только через дескриптор файла. Я не нашел любой другой способ, но если он есть, пожалуйста, дайте мне знать!

Чтобы решить эту проблему, я написал следующий код, который в основном создает канал и использует поток для чтения из трубы и создает сообщение журнала с помощью метода ведения журнала на Python:

import subprocess
import logging
import os
import threading

class LoggerWrapper(threading.Thread):
    """
    Read text message from a pipe and redirect them
    to a logger (see python's logger module),
    the object itself is able to supply a file
    descriptor to be used for writing

    fdWrite ==> fdRead ==> pipeReader
    """

    def __init__(self, logger, level):
        """
        Setup the object with a logger and a loglevel
        and start the thread
        """

        # Initialize the superclass
        threading.Thread.__init__(self)

        # Make the thread a Daemon Thread (program will exit when only daemon
        # threads are alive)
        self.daemon = True

        # Set the logger object where messages will be redirected
        self.logger = logger

        # Set the log level
        self.level = level

        # Create the pipe and store read and write file descriptors
        self.fdRead, self.fdWrite = os.pipe()

        # Create a file-like wrapper around the read file descriptor
        # of the pipe, this has been done to simplify read operations
        self.pipeReader = os.fdopen(self.fdRead)

        # Start the thread
        self.start()
    # end __init__

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

    def run(self):
        """
        This is the method executed by the thread, it
        simply read from the pipe (using a file-like
        wrapper) and write the text to log.
        NB the trailing newline character of the string
           read from the pipe is removed
        """

        # Endless loop, the method will exit this loop only
        # when the pipe is close that is when a call to
        # self.pipeReader.readline() returns an empty string
        while True:

            # Read a line of text from the pipe
            messageFromPipe = self.pipeReader.readline()

            # If the line read is empty the pipe has been
            # closed, do a cleanup and exit
            # WARNING: I don't know if this method is correct,
            #          further study needed
            if len(messageFromPipe) == 0:
                self.pipeReader.close()
                os.close(self.fdRead)
                return
            # end if

            # Remove the trailing newline character frm the string
            # before sending it to the logger
            if messageFromPipe[-1] == os.linesep:
                messageToLog = messageFromPipe[:-1]
            else:
                messageToLog = messageFromPipe
            # end if

            # Send the text to the logger
            self._write(messageToLog)
        # end while

        print 'Redirection thread terminated'

    # end run

    def _write(self, message):
        """
        Utility method to send the message
        to the logger with the correct loglevel
        """
        self.logger.log(self.level, message)
    # end write

# end class LoggerWrapper

# # # # # # # # # # # # # #
# Code to test the class  #
# # # # # # # # # # # # # #
logging.basicConfig(filename='command.log',level=logging.INFO)
logWrap = LoggerWrapper( logging, logging.INFO)

subprocess.call(['cat', 'file_full_of_text.txt'], stdout = logWrap, stderr = logWrap)

print 'Script terminated'

Для входа выхода подпроцессов', компания Google предлагает напрямую перенаправить вывод в файл, как это:

sobprocess.call( ['ls'] stdout = open( 'logfile.log', 'w') ) 

Это не вариант для меня, так как мне нужно использовать форматирование и уровень услуги лесозаготовки модуль. Я также полагаю, что файл открыт в режиме записи, но двумя разными лицами не допускается / не нормальная вещь, чтобы сделать.

Теперь я хотел бы видеть ваши комментарии и повышения предложения. Я также хотел бы знать, если есть уже подобный объект в библиотеку Python, так как я не нашел ничего, чтобы выполнить эту задачу!



61728
47
задан 6 декабря 2011 в 01:12 Источник Поделиться
Комментарии
1 ответ

Отличная идея. У меня была та же проблема и это помогло мне решить. Ваш метод хоть и заниматься уборкой не так (как вы упомянули, это может быть). В основном, вам нужно закрыть написать конец трубы после прохождения им подпроцесса. Таким образом, когда дочерний процесс завершает работу и закрывает конец трубы, на входе поток получит сигнал sigpipe, и возвращает нулевую длину сообщения, как вы ожидали.

В противном случае, главный процесс будет держать записи конец трубы открыт навсегда, вызывая заставляет блокировать на неопределенный срок, что приведет ваш поток, чтобы жить вечно, а также трубы. Это становится серьезной проблемой через некоторое время, потому что вы достигнете лимита на количество открытых файловых дескрипторов.

Кроме того, поток не должен быть поток демона, потому что это создает риск потери данных журнала при завершении работы процесса. Если вы правильно очистки, как описано, все темы будут выходить должным образом, устраняя необходимость, чтобы пометить их как демонов.

Наконец, в то время как петли могут быть упрощены, используя для петли.

Реализация всех этих изменений дает:

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)

# For testing
if __name__ == "__main__":
import sys

logpipe = LogPipe(logging.INFO)
with subprocess.Popen(['/bin/ls'], stdout=logpipe, stderr=logpipe) as s:
logpipe.close()

sys.exit()

Я использовал различные имена в пару пятен, но в остальном это та же идея, только немного чище и надежнее.

Установка close_fds=правда для подпроцесса вызова (который на самом деле по умолчанию) не поможет, потому что причины файловый дескриптор должен быть закрыт в вилке (ребенка) процесс перед вызовом exec. Нам нужен файловый дескриптор должен быть закрыт в Родительском процессе (т. е. до развилки) хотя.

Два потока все равно не правильно синхронизированы. Я уверен, что причина в том, что мы используем два отдельных потока. Я думаю, что если бы мы использовали только один поток под для лесозаготовки, проблема будет решена.

Проблема в том, что мы имеем дело с двумя различными буферами (трубы). Имеющие две нити (я вот помню) дает приблизительную синхронизацию записи данных, как она станет доступной. Это все-таки гонки, но есть два "сервера", так это обычно не имеет большого значения. Только для одного потока, есть только один "сервер", поэтому гонки показывает довольно плохие в виде несинхронизированный выходной. Единственным способом я могу думать, чтобы решить проблему, чтобы продлить ОС.трубы() вместо этого, но я понятия не имею, как это осуществить.

22
ответ дан 26 октября 2012 в 12:10 Источник Поделиться