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

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

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

14 ответов

Решение

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

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

Не блокирует

Если вы используете Linux (поскольку Windows не поддерживает вызовы select для файлов), вы можете использовать модуль подпроцесса вместе с модулем 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() будет заменен полезным кодом.

блокировка

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

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

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

Используя модуль 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)

[Обновить]

Так как ш.хвост с _iter= Истинный генератор, вы можете:

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

Затем вы можете "getNewData" с:

new_data = tail.next()

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

[Обновить]

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

Генератор контейнера, помещающий хвостовой вызов в цикл while True и отлавливающий возможные исключения ввода-вывода, будет иметь почти тот же эффект, что и -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 будет отображать последние 10 строк по умолчанию... Вы можете расположить указатель файла в конце файла, используя file.seek, я оставлю правильную реализацию в качестве упражнения для читателя.

ИМХО подход file.read() гораздо более элегантен, чем решение на основе подпроцесса.

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

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='')

Единственный портативный способ 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

Все ответы, использующие tail -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='')

В идеале, я хотел бы иметь что-то вроде 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 для рабочего кода.

Вы можете использовать библиотеку '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

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

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

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

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

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

import sys
from pygtail import Pygtail

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

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

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

Простая хвостовая функция из приложения pypi tailread

Вы можете использовать его также через pip install tailread

Рекомендуется для хвостового доступа к большим файлам.

      from io import BufferedReader


def readlines(bytesio, batch_size=1024, keepends=True, **encoding_kwargs):
    '''bytesio: file path or BufferedReader
       batch_size: size to be processed
    '''
    path = None
    
    if isinstance(bytesio, str):
        path = bytesio
        bytesio = open(path, 'rb')
    elif not isinstance(bytesio, BufferedReader):
        raise TypeError('The first argument to readlines must be a file path or a BufferedReader')

    bytesio.seek(0, 2)
    end = bytesio.tell()

    buf = b""
    for p in reversed(range(0, end, batch_size)):
        bytesio.seek(p)
        lines = []
        remain = min(end-p, batch_size)
        while remain > 0:
            line = bytesio.readline()[:remain]
            lines.append(line)
            remain -= len(line)

        cut, *parsed = lines
        for line in reversed(parsed):
            if buf:
                line += buf
                buf = b""
            if encoding_kwargs:
                line = line.decode(**encoding_kwargs)
            yield from reversed(line.splitlines(keepends))
        buf = cut + buf
    
    if path:
        bytesio.close()

    if encoding_kwargs:
        buf = buf.decode(**encoding_kwargs)
    yield from reversed(buf.splitlines(keepends))


for line in readlines('access.log', encoding='utf-8', errors='replace'):
    print(line)
    if 'line 8' in line:
        break

# line 11
# line 10
# line 9
# line 8

      # -*- coding:utf-8 -*-
import sys
import time


class Tail():
    def __init__(self, file_name, callback=sys.stdout.write):
        self.file_name = file_name
        self.callback = callback

    def follow(self, n=10):
        try:
            # 打开文件
            with open(self.file_name, 'r', encoding='UTF-8') as f:
            # with open(self.file_name,'rb') as f:
                self._file = f
                self._file.seek(0, 2)
                # 存储文件的字符长度
                self.file_length = self._file.tell()
                # 打印最后10行
                self.showLastLine(n)
                # 持续读文件 打印增量
                while True:
                    line = self._file.readline()
                    if line:
                        self.callback(line)
                    time.sleep(1)
        except Exception as e:
            print('打开文件失败,囧,看看文件是不是不存在,或者权限有问题')
            print(e)

    def showLastLine(self, n):
        # 一行大概100个吧 这个数改成1或者1000都行
        len_line = 100
        # n默认是10,也可以follow的参数传进来
        read_len = len_line * n
        # 用last_lines存储最后要处理的内容
        while True:
            # 如果要读取的1000个字符,大于之前存储的文件长度
            # 读完文件,直接break
            if read_len > self.file_length:
                self._file.seek(0)
                last_lines = self._file.read().split('\n')[-n:]
                break
            # 先读1000个 然后判断1000个字符里换行符的数量
            self._file.seek(-read_len, 2)
            last_words = self._file.read(read_len)
            # count是换行符的数量
            count = last_words.count('\n')

            if count >= n:
                # 换行符数量大于10 很好处理,直接读取
                last_lines = last_words.split('\n')[-n:]
                break
            # 换行符不够10个
            else:
                # break
                # 不够十行
                # 如果一个换行符也没有,那么我们就认为一行大概是100个
                if count == 0:

                    len_perline = read_len
                # 如果有4个换行符,我们认为每行大概有250个字符
                else:
                    len_perline = read_len / count
                # 要读取的长度变为2500,继续重新判断
                read_len = len_perline * n
        for line in last_lines:
            self.callback(line + '\n')


if __name__ == '__main__':
    py_tail = Tail('test.txt')
    py_tail.follow(1)

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

Другие вопросы по тегам