подпроцесс stdout / stderr в файл журнала конечного размера - PullRequest
1 голос
/ 23 августа 2011

У меня есть процесс, который много общается с stderr, и я хочу записать этот материал в файл.

foo 2> /tmp/foo.log

На самом деле я запускаю его с python subprocess.Popen, но это может быть также из оболочки для целей этого вопроса.

with open('/tmp/foo.log', 'w') as stderr:
  foo_proc = subprocess.Popen(['foo'], stderr=stderr)

Проблема в том, что через несколько дней мой файл журнала может быть очень большим, например> 500 МБ. Я заинтересован во всем этом stderr чате, но только в последнее время. Как я могу ограничить размер файла журнала, скажем, до 1 МБ? Файл должен быть немного похож на кольцевой буфер в том смысле, что будет записан самый последний материал, но более старый материал должен выпасть из файла, чтобы он никогда не превышал заданный размер. * * 1010

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

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

Ответы [ 3 ]

3 голосов
/ 27 августа 2011

Вы должны быть в состоянии использовать пакет ведения журнала stdlib для этого. Вместо того, чтобы напрямую соединять вывод подпроцесса с файлом, вы можете сделать что-то вроде этого:

import logging

logger = logging.getLogger('foo')

def stream_reader(stream):
    while True:
        line = stream.readline()
        logger.debug('%s', line.strip())

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

foo_proc = subprocess.Popen(['foo'], stderr=subprocess.PIPE)

thread = threading.Thread(target=stream_reader, args=(foo_proc.stderr,))
thread.setDaemon(True) # optional 
thread.start()

# do other stuff

thread.join() # await thread termination (optional for daemons)

Конечно, вы также можете позвонить stream_reader(foo_proc.stderr), но я предполагаю, что у вас может быть другая работа, пока подпроцесс foo делает свое дело.

Вот один способ настроить ведение журнала (код, который должен выполняться только один раз):

import logging, logging.handlers

handler = logging.handlers.RotatingFileHandler('/tmp/foo.log', 'a', 100000, 10)
logging.getLogger().addHandler(handler)
logging.getLogger('foo').setLevel(logging.DEBUG)

Это создаст до 10 файлов размером 100 КБ с именем foo.log (и после поворота foo.log.1, foo.log.2 и т. Д., Где foo.log является последним). Вы также можете передать 1000000, 1, чтобы получить только foo.log и foo.log.1, где поворот происходит, когда размер файла превышает 1000000 байт.

1 голос
/ 23 августа 2011

Вы можете использовать свойства «открытых описаний файлов» (отличных от «открытых дескрипторов файлов», но тесно связанных с ними).В частности, текущая позиция записи связана с описанием открытого файла, поэтому каждый из двух процессов, которые совместно используют одно описание открытого файла, может корректировать позицию записи.

Таким образом, в контексте исходный процесс может сохранить файлдескриптор стандартной ошибки дочернего процесса и периодически, когда позиция достигает размера в 1 МБ, перемещайте указатель на начало файла, тем самым достигая требуемого эффекта кругового буфера.

Самая большая проблема заключается в определениигде пишутся текущие сообщения, так что вы можете прочитать из самого старого материала (прямо перед позицией файла) в самый новый материал.Маловероятно, что новые строки, перезаписывающие старую, будут точно совпадать, так что там будет немного мусора.Вы можете быть в состоянии следовать за каждой строкой от дочерней с известной последовательностью символов (скажем, «XXXXXX»), а затем иметь каждую запись из дочерней репозиции, чтобы перезаписать предыдущий маркер ... но это определенно требует контроля над программой, которая являетсязапустить.Если он не находится под вашим контролем или не может быть изменен, этот параметр исчезает.

Альтернативой может быть периодическое обрезание файла (возможно, после его копирования) и запись в дочерний процесс в режиме добавления (потому что файл открывается в родительском в режиме добавления).Вы можете организовать копирование материала из файла в запасной файл перед усечением, чтобы сохранить предыдущие 1 МБ данных.Таким образом, вы можете использовать до 2 МБ, что намного лучше, чем 500 МБ, а размеры можно настроить, если вам не хватает места.

Веселитесь!

1 голос
/ 23 августа 2011

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

import subprocess
import signal

def hupsignal(signum, frame):
    global logfile
    logfile.close()
    logfile = open('/tmp/foo.log', 'a')

logfile = open('/tmp/foo.log', 'a')
signal.signal()
foo_proc = subprocess.Popen(['foo'], stderr=subprocess.PIPE)
for chunk in iter(lambda: foo_proc.stderr.read(8192), ''):
    # iterate until EOF occurs
    logfile.write(chunk)
    # or do you want to rotate yourself?
    # Then omit the signal stuff and do it here.
    # if logfile.tell() > MAX_FILE_SIZE:
    #     logfile.close()
    #     logfile = open('/tmp/foo.log', 'a')

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

Кроме того, это пример того, как заставить его работать с logrotate.Конечно, вы можете при необходимости самостоятельно вращать лог-файл.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...