Як я можу оформити файл журналу в Python?


78

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

В ідеалі я мав би щось подібне, до tail.getNewData()чого я міг би телефонувати щоразу, коли б мені хотілося більше даних.


1
subprocess.call(["tail", "-F", filename])
Whymarrh


1
@Avaris, що відповідь не є "наступним" хвостом.
Кіт,

Аваріс: Ні. Це просто хвости. Мені потрібен tail -F, щоб або він постійно видавав мені всі нові рядки, або я міг постійно отримувати їх кожен раз, коли викликаю якусь функцію getData ().
Елі

1
Чи потрібно вашому гіпотетичному get_new_dataметоду (назва PEP-8) повертати всі дані після останнього дзвінка, або лише поточний хвіст (можливо, втрачаючи деякі дані)?
Кіт,

Відповіді:


73

Не блокує

Якщо ви використовуєте Linux (оскільки Windows не підтримує виклик select on files), ви можете використовувати модуль підпроцесу разом із модулем 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().


Ну, технічно f.stdoutце конвеєр, а не файл (але я вважаю, що Windows все ще не може використовувати selectна ньому).
nneonneo

3
У рішенні "Блокування", замість print line, використовуйте, sys.stdout.write(line)щоб подбати про зайві нові рядки, які вставить друк.
Mayank Jaiswal

line = f.stdout.readline (). strip () також видалить зайвий новий рядок
mork

1
@mork Чи надруковані додаткові нові рядки, яких не повинно бути? У будь-якому випадку, я вважаю, .strip()це також призведе до видалення провідних пробілів, які можуть бути значними.
Метт,

1
Це читає не більше одного рядка в секунду, що є проблемою, якщо журнал зростає більше ніж на один рядок в секунду.
Даніель Уолтріп,

43

За допомогою модуля 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 це буде блокування. Мені було б цікавіше мати функцію, яку я міг би викликати, щоб отримувати нові дані, коли я цього хочу, якщо це можливо. - Елі

Генератор контейнерів, розміщуючи зворотний виклик всередині циклу 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-подібних середовищах.

Можливо, якщо ви оновите своє запитання, щоб пояснити, яка ваша справжня мета (причина, по якій ви хочете імітувати хвіст - спробу), ви отримаєте кращу відповідь.

Остання відповідь насправді не відповідає хвосту, а лише читає те, що доступно під час виконання. - Елі

Звичайно, за замовчуванням хвіст відображатиме останні 10 рядків ... Ви можете розташувати вказівник на файл у кінці файлу, використовуючи file.seek, я залишу належну реалізацію як вправу для читача.

IMHO підхід file.read () набагато витонченіший, ніж рішення на основі підпроцесу.


Це працює, якщо замінити -f на -F, але в Python це буде блокування. Мені було б цікавіше мати функцію, яку я міг би викликати, щоб отримувати нові дані, коли я цього хочу, якщо це можливо.
Елі

Я думаю, що генератор контейнерів, що розміщує tailвиклик всередині while Trueциклу та фіксує можливі винятки вводу-виводу, матиме такий же ефект, як -F.
Пауло Скардін,

Відповідь останньої останньої обводиться до верхньої частини файлу, здається, коли у нього закінчуються дані. Остання відповідь насправді не відповідає хвосту, а лише читає те, що доступно під час виконання.
Елі

@Eli: пошук (0, 2) перемістить покажчик на файл у кінець файлу.
Пауло Скардін,

1
Просто цікаво: що для вас здається більш елегантним у file.read()підході? tailналежним чином обробляє показ останніх 10 рядків файлу (навіть якщо вони величезні), читання нових рядків назавжди, пробудження, коли надходять нові рядки (залежно від платформи), і відкриття нових файлів за потреби. Одним словом, утиліта досить добре розроблена для того, для чого вона призначена - її реалізація здається не настільки елегантною. (Однак я визнаю, що shмодуль досить витончений.)
nneonneo

24

Здається, єдиним портативним способом до tail -fфайлу є його читання та повторна спроба (після a 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

4
Якщо головна проблема OP - це не позбавлення від залежності від зовнішньої команди (tail), він повинен слідувати традиції unix - писати програму-процесор журналу для читання з stdin та конвеєра tail -Fв неї. Я не можу зрозуміти, чому додавання складності потоків, черги та підпроцесу призведе до будь-якої переваги над традиційним підходом.
Пауло Скардін,

Коли він сказав, що пише процесор журналів?
nneonneo

11
Англійська мова не є моєю рідною ідіомою, але, мабуть, це можна зробити з назви запитання (Як я можу оформити файл журналу в Python?).
Пауло Скардін,

13

Отже, це відбувається досить пізно, але я знову зіткнувся з тією ж проблемою, і зараз є набагато краще рішення. Просто використовуйте косичку :

Pygtail читає рядки файлу журналу, які не були прочитані. Він навіть буде обробляти файли журналів, які були повернені. На основі logcheck's logtail2 ( http://logcheck.org )


Будь ласка, зверніть увагу, що він не зовсім поводиться як хвіст, але може бути корисним, залежно від того, що хочеться зробити.
Haroldo_OK 02

12

Адаптування Ijaz Ахмад Хан відповідь на тільки лінії прибутковості , коли вони повністю написані (рядки закінчуються символом нового рядка напівкоксу) дає віщий рішення без будь - яких зовнішніх залежностей:

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

11

В ідеалі я мав би щось на зразок 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 для робочого коду.


1
Справа в тому, що я хотів стежити за файлом. Якщо я відкриваю файл, f.read () продовжується лише до кінця того, що файл був під час запуску. Після цього він не прочитає нічого нового.
Елі

1
Я перевірив це перед публікацією. Я щойно зробив: blah = open ('some_file', r), а 1: sleep (1) print blah.read () і спробував записати у файл. Нещастить.
Елі,

1
@Eli: тоді ви повинні бути в Windows. Це важлива інформація, яка відсутня у вашому запитанні.
Пауло Скардін,

10
@Paulo: У відповіді бракує важливої ​​інформації. Якщо жодна операційна система не вказана, ви створюєте щось, що працює загалом, або принаймні щось, що працює для * nix. Ви ніколи не припускаєте Windows.
Елі

Чому ніколи не припускати вікна? python ближче до вікон, ніж ніж nix, наприклад: UTF-16 проти UTF-8
Ясен

8

Усі відповіді, в яких використовується tail -f, не є пітонічними.

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

def follow(thefile):
     while True:
        line = thefile.readline()
        if not line or not line.endswith('\n'):
            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='')

1
Якщо файл журналу додається до двох системних викликів, цей спосіб "слідування" файлу іноді повертає 2 частини рядка, а не повний рядок
Феррібіг,

Я відправив відповідь на адресу помилки @Ferrybig зазначив: stackoverflow.com/a/54263201/431087
Ісаак Тернер

Поміркуйте, що інша програма python пише в цей файл за допомогою програвача запису. Чи є якийсь спосіб, яким ми могли б програмовано зупинити цю операцію, коли програма припинила запис?
codelord

так, ви можете використовувати такий механізм, як замок, щоб отримати замок перед тим, як писати на нього, і відпустити його, коли закінчите
Іджаз Ахмад Хан,

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

Якщо хтось бажає поведінки, подібної до хвоста, це здається хорошим варіантом.


1
Це не follow()той самий файл після його видалення / відтворення, тому не працював для мене: /
Jose Alban

1
@JoseAlban просто не відповідальність бібліотеки стежити за видаленням / створенням файлів, make-all-the-things-work-by-themselvesзамість цього використовуйте модуль pypi
Павел Власов

3

Інший варіант - це tailheadбібліотека, яка надає як версії Python, так tailі headутиліти та API, які можна використовувати у вашому власному модулі.

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


1

Python - це "батареї в комплекті" - для нього є гарне рішення: https://pypi.python.org/pypi/pygtail

Зчитує рядки файлу журналу, які не були прочитані. Пам'ятає, де закінчився минулого разу, і продовжує звідти.

import sys
from pygtail import Pygtail

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

14
Потрібно встановлювати пакет, щоб отримати функціонал, це зовсім протилежність "батареї в комплекті".
bfontaine

ну, не всі пакунки встановлюються за замовчуванням, на щастя. Але вам не потрібно писати (і налагоджувати та підтримувати) будь-який хитрий код, використовуючи підпроцес, як пропонують відповіді з набагато вищою кармою.
Пітер М. - розшифровується як Моніка

@Eli - так, у вашій відповіді згадується пігтейл, але в ньому немає прикладу того, як легко ним користуватися. І до того ж я підтримав вашу відповідь, тож будьте не дуже засмучені :-)
Пітер М. - означає Моніка,

1
як використовувати параметр --full-lines у вашому прикладі
косички

0

Ви також можете використовувати команду 'AWK'.
Детальніше див. На: http://www.unix.com/shell-programming-scripting/41734-how-print-specific-lines-awk.html
awk може використовуватися для виділення останнього рядка, останніх декількох рядків або будь-якого рядка в файл.
Це можна викликати з python.


0

Якщо ви використовуєте Linux, ви реалізуєте неблокуючу реалізацію в python наступним чином.

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

1
У Linux із запущеним X та встановленим csh. Це БАГАТО непотрібних залежностей!
kmarsh
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.