Вход в Python и вывод подпроцесса и поток ошибок - PullRequest
3 голосов
/ 02 февраля 2012

Я хотел бы запустить процесс Python и записать сообщения об ошибках подпроцесса в объект журнала родительского сценария.В идеале я хотел бы объединить потоки журналов в один файл.Могу ли я как-то получить доступ к выходному потоку класса регистрации?Одно из известных мне решений - использовать журнал логов для регистрации.Как описано в ответе ниже, я мог читать из proc.stdin и stderr, но у меня были бы дублирующие заголовки журналов.Интересно, есть ли способ передать файловый дескриптор, лежащий в основе класса ведения журнала, непосредственно в подпроцесс?

logging.basicConfig(filename="test.log",level=logging.DEBUG)
logging.info("Started")
procLog = open(os.path.expanduser("subproc.log"), 'w')
proc = subprocess.Popen(cmdStr, shell=True, stderr=procLog, stdout=procLog)
proc.wait()
procLog.flush()

1 Ответ

5 голосов
/ 02 февраля 2012

Исходя из кода Адама Розенфилда , вы можете

  1. использовать select.select для блокировки до тех пор, пока не будет прочитан вывод из proc.stdout или proc.stderr,
  2. прочитайте и запишите этот вывод, затем
  3. повторяйте до тех пор, пока процесс не будет завершен.

Обратите внимание, что следующее записывает в /tmp/test.log и запускает команду ls -laR /tmp.Измените в соответствии с вашими потребностями.

(PS: Обычно / tmp содержит каталоги, которые не могут быть прочитаны обычными пользователями, поэтому запуск ls -laR /tmp производит вывод как для stdout, так и для stderr. Приведенный ниже код правильно чередует эти два потокапо мере их производства.)

import logging
import subprocess
import shlex
import select
import fcntl
import os
import errno
import contextlib

logger = logging.getLogger(__name__)

def make_async(fd):
    '''add the O_NONBLOCK flag to a file descriptor'''
    fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)

def read_async(fd):
    '''read some data from a file descriptor, ignoring EAGAIN errors'''
    try:
        return fd.read()
    except IOError, e:
        if e.errno != errno.EAGAIN:
            raise e
        else:
            return ''

def log_fds(fds):
    for fd in fds:
        out = read_async(fd)
        if out:
            logger.info(out)

@contextlib.contextmanager
def plain_logger():
    root = logging.getLogger()    
    hdlr = root.handlers[0]
    formatter_orig = hdlr.formatter
    hdlr.setFormatter(logging.Formatter('%(message)s'))
    yield 
    hdlr.setFormatter(formatter_orig)

def main():
    # fmt = '%(name)-12s: %(levelname)-8s %(message)s'
    logging.basicConfig(filename = '/tmp/test.log', mode = 'w',
                        level = logging.DEBUG)

    logger.info("Started")
    cmdStr = 'ls -laR /tmp'

    with plain_logger():
        proc = subprocess.Popen(shlex.split(cmdStr),
                                stdout = subprocess.PIPE, stderr = subprocess.PIPE)
        # without `make_async`, `fd.read` in `read_async` blocks.
        make_async(proc.stdout)
        make_async(proc.stderr)
        while True:
            # Wait for data to become available 
            rlist, wlist, xlist = select.select([proc.stdout, proc.stderr], [], [])
            log_fds(rlist)
            if proc.poll() is not None:
                # Corner case: check if more output was created
                # between the last call to read_async and now                
                log_fds([proc.stdout, proc.stderr])                
                break

    logger.info("Done")

if __name__ == '__main__':
    main()

Редактировать:

Вы можете перенаправить stdout и stderr на logfile = open('/tmp/test.log', 'a').Однако небольшая трудность заключается в том, что любой обработчик логгера, который также записывает в /tmp/test.log, не будет знать о том, что пишет подпроцесс, и поэтому файл журнала может быть искажен.

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

handler.stream.seek(0, 2)

, чтобы обработчик возобновил запись в конце файла.


import logging
import subprocess
import contextlib
import shlex

logger = logging.getLogger(__name__)

@contextlib.contextmanager
def suspended_logger():
    root = logging.getLogger()    
    handler = root.handlers[0]
    yield 
    handler.stream.seek(0, 2)

def main():
    logging.basicConfig(filename = '/tmp/test.log', filemode = 'w',
                        level = logging.DEBUG)

    logger.info("Started")
    with suspended_logger():
        cmdStr = 'test2.py 1>>/tmp/test.log 2>&1'
        logfile = open('/tmp/test.log', 'a')
        proc = subprocess.Popen(shlex.split(cmdStr),
                                stdout = logfile,
                                stderr = logfile)
        proc.communicate()
    logger.info("Done")

if __name__ == '__main__':
    main()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...