подпроцесс stdout / stderr в файл журнала конечного размера

1

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

foo 2> /tmp/foo.log

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

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

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

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

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

Теги:
logging
file-io
subprocess

3 ответа

3
Лучший ответ

Для этого вы можете использовать пакет ведения журнала 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 файлов из 100K с именем foo.log(и после вращения foo.log.1, foo.log.2 и т.д., где foo.log является последним). Вы также можете передать 1000000, 1, чтобы дать вам только foo.log и foo.log.1, где вращение происходит, когда размер файла будет превышать 1000000 байт.

1

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

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

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

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

Удачи!

1

Путь с циклическим буфером будет трудно реализовать, так как вам придется постоянно переписывать весь файл, как только что-то выпадет.

Подход с логротатом или чем-то будет вашим путем. В этом случае вы просто будете похожи на это:

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. Конечно, вы можете повернуть свой файл журнала самостоятельно, если это необходимо.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню