Как я могу закрепить файл журнала в Python?

Я хотел бы сделать вывод tail -F или чего-то подобного доступного мне в Python без блокировки или блокировки. Я нашел какой-то действительно старый код, чтобы сделать это здесь, но я думаю, что должен быть лучший способ или библиотека, чтобы сделать то же самое к настоящему времени. Кто-нибудь знает об одном?

В идеале, у меня было бы что-то вроде tail.getNewData(), которое я мог бы вызвать каждый раз, когда мне нужно больше данных.

Ответ 1

Итак, это происходит довольно поздно, но я снова столкнулся с той же проблемой, и теперь есть гораздо лучшее решение. Просто используйте pygtail:

Pygtail считывает строки журнала, которые не были прочитаны. Это будет даже обрабатывать файлы журналов, которые были повернуты. На основе logtail logtail2 (http://logcheck.org)

Ответ 2

Неблокирование

Если вы используете linux (поскольку окна не поддерживают выбор вызова в файлах), вы можете использовать модуль подпроцесса вместе с модулем select.

import time
import subprocess
import select

f = subprocess.Popen(['tail','-F',filename],\
        stdout=subprocess.PIPE,stderr=subprocess.PIPE)
p = select.poll()
p.register(f.stdout)

while True:
    if p.poll(1):
        print f.stdout.readline()
    time.sleep(1)

Обследует выходной канал для новых данных и печатает его, когда он доступен. Обычно time.sleep(1) и print f.stdout.readline() заменяются полезным кодом.

Блокировка

Вы можете использовать модуль подпроцесса без дополнительных вызовов модуля.

import subprocess
f = subprocess.Popen(['tail','-F',filename],\
        stdout=subprocess.PIPE,stderr=subprocess.PIPE)
while True:
    line = f.stdout.readline()
    print line

Это также будет печатать новые строки по мере их добавления, но он будет блокироваться до закрытия хвостовой программы, возможно, с помощью f.kill().

Ответ 3

Используя модуль sh (pip install sh):

from sh import tail
# runs forever
for line in tail("-f", "/var/log/some_log_file.log", _iter=True):
    print(line)

[обновление]

Так как sh.tail с _iter= True - это генератор, вы можете:

import sh
tail = sh.tail("-f", "/var/log/some_log_file.log", _iter=True)

Затем вы можете "getNewData" с помощью:

new_data = tail.next()

Обратите внимание, что если буфер хвоста пуст, он будет блокироваться, пока не появится больше данных (из вашего вопроса не ясно, что вы хотите сделать в этом случае).

[обновление]

Это работает, если вы замените -f на -F, но в Python он будет заблокирован. Меня больше интересовала бы функция, которую я мог бы вызвать, чтобы получить новые данные, когда захочу, если это возможно. - Эли

Генератор контейнера, помещающий хвостовой вызов через некоторое время. Истинный цикл и перехватывание возможных исключений I/O будет иметь почти такой же эффект -F.

def tail_F(some_file):
    while True:
        try:
            for line in sh.tail("-f", some_file, _iter=True):
                yield line
        except sh.ErrorReturnCode_1:
            yield None

Если файл становится недоступным, генератор возвращает None. Однако он все еще блокируется до появления новых данных, если файл доступен. Мне остается неясным, что вы хотите сделать в этом случае.

Раймонд Хеттингер выглядит довольно неплохо:

def tail_F(some_file):
    first_call = True
    while True:
        try:
            with open(some_file) as input:
                if first_call:
                    input.seek(0, 2)
                    first_call = False
                latest_data = input.read()
                while True:
                    if '\n' not in latest_data:
                        latest_data += input.read()
                        if '\n' not in latest_data:
                            yield ''
                            if not os.path.isfile(some_file):
                                break
                            continue
                    latest_lines = latest_data.split('\n')
                    if latest_data[-1] != '\n':
                        latest_data = latest_lines[-1]
                    else:
                        latest_data = input.read()
                    for line in latest_lines[:-1]:
                        yield line + '\n'
        except IOError:
            yield ''

Этот генератор вернет '', если файл становится недоступным или нет новых данных.

[обновление]

От второго до последнего ответа кружки вокруг верхней части файла появляются, когда у него заканчиваются данные. - Эли

Я думаю, что второй будет выводить последние десять строк всякий раз, когда заканчивается хвостовой процесс, который с -f возникает всякий раз, когда возникает ошибка ввода-вывода. Поведение tail --follow --retry не так уж и мало для большинства случаев, о которых я могу думать в unix-подобных средах.

Возможно, если вы обновите свой вопрос, чтобы объяснить, какова ваша реальная цель (причина, по которой вы хотите имитировать tail -retry), вы получите лучший ответ.

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

Конечно, хвост покажет последние 10 строк по умолчанию... Вы можете поместить указатель файла в конец файла с помощью file.seek, я оставил правильную реализацию как упражнение для читателя.

IMHO метод file.read() гораздо шире, чем решение на основе подпроцесса.

Ответ 4

Единственный переносимый способ tail -f - это, по-видимому, файл читать и повторять (после sleep), если read возвращает 0. Утилиты tail на разных платформах используют специфичные для платформы трюки (например, kqueue в BSD), чтобы эффективно навсегда закрепить файл без необходимости sleep.

Следовательно, реализация хорошего tail -f чисто в Python, вероятно, не очень хорошая идея, так как вам придется использовать реализацию с наименьшим общим знаменателем (не прибегая к хакам платформы). Используя простой subprocess для открытия tail -f и итерации по строкам в отдельном потоке, вы можете легко реализовать неблокирующую операцию tail в Python.

Пример реализации:

import threading, Queue, subprocess
tailq = Queue.Queue(maxsize=10) # buffer at most 100 lines

def tail_forever(fn):
    p = subprocess.Popen(["tail", "-f", fn], stdout=subprocess.PIPE)
    while 1:
        line = p.stdout.readline()
        tailq.put(line)
        if not line:
            break

threading.Thread(target=tail_forever, args=(fn,)).start()

print tailq.get() # blocks
print tailq.get_nowait() # throws Queue.Empty if there are no lines to read

Ответ 5

В идеале, у меня было бы что-то вроде tail.getNewData(), которое я мог бы вызвать каждый раз, когда мне нужно больше данных

У нас уже есть один и его очень хороший. Просто позвоните f.read(), когда вам нужно больше данных. Он начнет чтение, когда предыдущее чтение будет остановлено, и оно будет считываться через конец потока данных:

f = open('somefile.log')
p = 0
while True:
    f.seek(p)
    latest_data = f.read()
    p = f.tell()
    if latest_data:
        print latest_data
        print str(p).center(10).center(80, '=')

Для чтения строки за строкой используйте f.readline(). Иногда считываемый файл заканчивается частично прочитанной строкой. Обработайте этот случай, когда f.tell() находит текущую позицию файла и использует f.seek() для перемещения указателя файла назад к началу неполной строки. См. этот рецепт ActiveState для рабочего кода.

Ответ 6

Вы можете использовать библиотеку 'tailer': https://pypi.python.org/pypi/tailer/

У него есть возможность получить последние несколько строк:

# Get the last 3 lines of the file
tailer.tail(open('test.txt'), 3)
# ['Line 9', 'Line 10', 'Line 11']

И он также может следовать за файлом:

# Follow the file as it grows
for line in tailer.follow(open('test.txt')):
    print line

Если вы хотите хвостовое поведение, это кажется хорошим вариантом.

Ответ 7

Другим вариантом является библиотека tailhead, которая предоставляет как версии Python tail, так и head утилит и API, которые могут быть используется в вашем собственном модуле.

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

Ответ 8

Все ответы, которые используют хвост -f, не являются питоническими.

Вот питонский способ: (без использования внешнего инструмента или библиотеки)

def follow(thefile):
     while True:
        line = thefile.readline()
        if not line:
            time.sleep(0.1)
            continue
        yield line



if __name__ == '__main__':
    logfile = open("run/foo/access-log","r")
    loglines = follow(logfile)
    for line in loglines:
        print(line, end='')

Ответ 9

Вы также можете использовать команду "AWK".
См. Больше на: http://www.unix.com/shell-programming-scripting/41734-how-print-specific-lines-awk.html
awk можно использовать для хвоста последней строки, последних нескольких строк или любой строки в файле.
Это можно вызвать из python.

Ответ 10

Если вы используете linux, вы реализуете неблокирующую реализацию в python следующим образом.

import subprocess
subprocess.call('xterm -title log -hold -e \"tail -f filename\"&', shell=True, executable='/bin/csh')
print "Done"

Ответ 11

Python - это "батареи в комплекте" - у него есть хорошее решение для него: https://pypi.python.org/pypi/pygtail

Считывает строки журнала, которые не были прочитаны. Помнит, где он закончил последний раз, и продолжается оттуда.

import sys
from pygtail import Pygtail

for line in Pygtail("some.log"):
    sys.stdout.write(line)

Ответ 12

Адаптация ответа Иджаза Ахмада Кхана к выводу строк только тогда, когда они полностью написаны (строки заканчиваются символом новой строки), дает питонное решение без внешних зависимостей:

def follow(file) -> Iterator[str]:
    """ Yield each line from a file as they are written. """
    line = ''
    while True:
        tmp = file.readline()
        if tmp is not None:
            line += tmp
            if line.endswith("\n"):
                yield line
                line = ''
        else:
            time.sleep(0.1)


if __name__ == '__main__':
    for line in follow(open("test.txt", 'r')):
        print(line, end='')