Wie kann ich eine Protokolldatei in Python abschließen?


76

Ich möchte die Ausgabe von tail -F oder ähnlichem in Python für mich verfügbar machen, ohne sie zu blockieren oder zu sperren. Ich habe einige wirklich alten Code zu tun , dass gefunden hier , aber ich denke , es muss einen besseren Weg oder eine Bibliothek sein , die gleiche Sache jetzt zu tun. Kennt jemand einen?

Im Idealfall hätte ich so etwas tail.getNewData(), das ich jedes Mal anrufen könnte, wenn ich mehr Daten wollte.


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


1
@Avaris diese Antwort ist kein "folgender" Schwanz.
Keith

Avaris: Nein. Ich brauche tail -F, damit es mir entweder ständig alle neuen Zeilen gibt, oder ich kann sie jedes Mal abrufen, wenn ich eine getData () -Funktion aufrufe.
Eli

1
Müsste Ihre hypothetische get_new_dataMethode (PEP-8-Name) alle Daten seit dem letzten Aufruf oder nur den aktuellen Schwanz zurückgeben (möglicherweise gehen einige Daten verloren)?
Keith

Antworten:


70

Nicht blockierend

Wenn Sie unter Linux arbeiten (da Windows das Aufrufen von select on files nicht unterstützt), können Sie das Unterprozessmodul zusammen mit dem select-Modul verwenden.

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)

Dadurch wird die Ausgabepipe nach neuen Daten abgefragt und gedruckt, sobald sie verfügbar sind. Normalerweise wird das time.sleep(1)und print f.stdout.readline()durch nützlichen Code ersetzt.

Blockierung

Sie können das Unterprozessmodul ohne die zusätzlichen Aufrufe des Auswahlmoduls verwenden.

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

Dadurch werden auch neue Zeilen gedruckt, wenn sie hinzugefügt werden, aber es wird blockiert, bis das Endprogramm geschlossen wird, wahrscheinlich mit f.kill().


Technisch gesehen f.stdouthandelt es sich um eine Pipe, nicht um eine Datei (aber ich glaube, Windows kann sie immer noch nicht verwenden select).
Nneonneo

3
print lineVerwenden Sie sys.stdout.write(line)in der Lösung "Blockieren" stattdessen " Zusätzliche Zeilenumbrüche", die beim Einfügen eingefügt werden.
Mayank Jaiswal

line = f.stdout.readline (). strip () würde auch die zusätzliche Newline entfernen
mork

1
@mork Gibt es zusätzliche gedruckte Zeilenumbrüche, die nicht gedruckt werden sollten? Wie auch immer, ich glaube, ich .strip()würde auch führende Leerzeichen entfernen, die von Bedeutung sein könnten.
Matt

1
Dies liest höchstens eine einzelne Zeile pro Sekunde, was ein Problem ist, wenn das Protokoll um mehr als eine Zeile pro Sekunde wächst.
Daniel Waltrip

42

Verwenden des sh-Moduls (pip install sh):

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

[aktualisieren]

Da sh.tail with _iter= True ein Generator ist, können Sie:

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

Dann können Sie "getNewData" mit:

new_data = tail.next()

Beachten Sie, dass der Endpuffer, wenn er leer ist, blockiert wird, bis weitere Daten vorhanden sind (aus Ihrer Frage geht nicht hervor, was Sie in diesem Fall tun möchten).

[aktualisieren]

Dies funktioniert, wenn Sie -f durch -F ersetzen, aber in Python wird es gesperrt. Ich wäre mehr an einer Funktion interessiert, die ich aufrufen könnte, um neue Daten zu erhalten, wenn ich möchte, wenn dies möglich ist. - Eli

Ein Containergenerator, der den Tail-Aufruf innerhalb einer while-True-Schleife platziert und eventuelle E / A-Ausnahmen abfängt, hat fast den gleichen Effekt wie -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

Wenn auf die Datei nicht mehr zugegriffen werden kann, gibt der Generator None zurück. Es wird jedoch weiterhin blockiert, bis neue Daten vorliegen, wenn auf die Datei zugegriffen werden kann. Es bleibt mir unklar, was Sie in diesem Fall tun möchten.

Der Ansatz von Raymond Hettinger scheint ziemlich gut zu sein:

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

Dieser Generator gibt '' zurück, wenn auf die Datei nicht mehr zugegriffen werden kann oder wenn keine neuen Daten vorhanden sind.

[aktualisieren]

Die vorletzte Antwort wird an der Spitze der Datei angezeigt, wenn die Daten ausgehen. - Eli

Ich denke, die zweite gibt die letzten zehn Zeilen aus, wenn der Endprozess endet, und -fzwar immer dann, wenn ein E / A-Fehler vorliegt. Das tail --follow --retryVerhalten ist in den meisten Fällen, die ich mir in Unix-ähnlichen Umgebungen vorstellen kann, nicht weit davon entfernt.

Wenn Sie Ihre Frage aktualisieren, um zu erklären, was Ihr eigentliches Ziel ist (der Grund, warum Sie die Schwanzwiederholung imitieren möchten), erhalten Sie möglicherweise eine bessere Antwort.

Die letzte Antwort folgt nicht dem Schwanz und liest lediglich, was zur Laufzeit verfügbar ist. - Eli

Natürlich zeigt tail standardmäßig die letzten 10 Zeilen an ... Sie können den Dateizeiger mit file.seek am Ende der Datei positionieren. Ich überlasse dem Leser eine ordnungsgemäße Implementierung als Übung.

Meiner Meinung nach ist der file.read () -Ansatz weitaus eleganter als eine auf Subprozessen basierende Lösung.


Dies funktioniert, wenn Sie -f durch -F ersetzen, aber in Python wird es gesperrt. Ich wäre mehr an einer Funktion interessiert, die ich aufrufen könnte, um neue Daten zu erhalten, wenn ich möchte, wenn dies möglich ist.
Eli

Ich denke, ein Containergenerator, der den tailAufruf in eine while TrueSchleife platziert und eventuelle E / A-Ausnahmen abfängt, hat den gleichen Effekt wie -F.
Paulo Scardine

Die vorletzte Antwort wird an der Spitze der Datei angezeigt, wenn keine Daten mehr vorhanden sind. Die letzte Antwort folgt nicht dem Schwanz und liest lediglich, was zur Laufzeit verfügbar ist.
Eli

@Eli: Eine Suche (0, 2) bewegt den Dateizeiger an das Ende der Datei.
Paulo Scardine

1
Nur neugierig: Was scheint Ihnen an einem file.read()Ansatz eleganter zu sein ? tailBehandelt ordnungsgemäß das Anzeigen der letzten 10 Zeilen der Datei (auch wenn die Zeilen sehr groß sind), das Lesen neuer Zeilen für immer, das Aufwachen beim Eintreffen neuer Zeilen (plattformabhängig) und das Öffnen neuer Dateien bei Bedarf. Mit einem Wort, das Dienstprogramm ist ziemlich gut für das konzipiert, was es tun soll - die Neuimplementierung scheint bei weitem nicht so elegant zu sein. (Ich gebe jedoch zu, dass das shModul ziemlich geschickt ist.)
Nneonneo

24

Der einzige tragbare Weg zu tail -feiner Datei scheint darin zu bestehen, daraus zu lesen und (nach a sleep) erneut zu versuchen, wenn read0 zurückgegeben wird. Die tailDienstprogramme auf verschiedenen Plattformen verwenden plattformspezifische Tricks (z. B. kqueueauf BSD), um eine Datei für immer effizient zu verwalten ohne zu brauchen sleep.

Daher ist die Implementierung eines Goods tail -fnur in Python wahrscheinlich keine gute Idee, da Sie die Implementierung mit dem kleinsten gemeinsamen Nenner verwenden müssten (ohne auf plattformspezifische Hacks zurückzugreifen). Mit einem einfachen subprocessÖffnen tail -fund Durchlaufen der Zeilen in einem separaten Thread können Sie problemlos eine nicht blockierende tailOperation in Python implementieren .

Beispielimplementierung:

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
Wenn das Hauptanliegen des OP nicht darin besteht, die Abhängigkeit vom externen Befehl (tail) zu beseitigen, sollte er der Unix-Tradition folgen, die Protokollprozessoranwendung zu schreiben, um sie von stdin zu lesen und tail -Fin sie zu leiten . Ich kann nicht verstehen, warum das Hinzufügen der Komplexität von Threading, Warteschlange und Unterprozess zu einem Vorteil gegenüber dem herkömmlichen Ansatz führt.
Paulo Scardine

Wann hat er gesagt, dass er einen Protokollprozessor schreibt?
Nneonneo

11
Englisch ist nicht meine Muttersprache, aber ich denke, es kann aus dem Fragentitel abgeleitet werden (Wie kann ich eine Protokolldatei in Python abschließen?).
Paulo Scardine

13

Das kommt also ziemlich spät, aber ich bin wieder auf dasselbe Problem gestoßen, und es gibt jetzt eine viel bessere Lösung. Verwenden Sie einfach Pygtail :

Pygtail liest nicht gelesene Protokolldateizeilen. Es werden sogar Protokolldateien verarbeitet, die gedreht wurden. Basierend auf logchecks logtail2 ( http://logcheck.org )


Bitte beachten Sie, dass es sich nicht ganz wie ein Schwanz verhält, aber es kann nützlich sein, je nachdem, was man tun möchte.
Haroldo_OK

12

Wenn Sie die Antwort von Ijaz Ahmad Khan so anpassen, dass Zeilen nur dann ausgegeben werden, wenn sie vollständig geschrieben sind (Zeilen enden mit einem Zeilenumbruchzeichen), erhalten Sie eine pythonische Lösung ohne externe Abhängigkeiten:

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

Im Idealfall hätte ich so etwas wie tail.getNewData (), das ich jedes Mal aufrufen könnte, wenn ich mehr Daten wollte

Wir haben bereits eine und es ist sehr schön. Rufen Sie einfach f.read () auf, wenn Sie weitere Daten wünschen. Es beginnt dort zu lesen, wo der vorherige Lesevorgang aufgehört hat, und es liest das Ende des Datenstroms durch:

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

Verwenden Sie zum zeilenweisen Lesen f.readline () . Manchmal endet die gelesene Datei mit einer teilweise gelesenen Zeile. Behandeln Sie diesen Fall, indem f.tell () die aktuelle Dateiposition ermittelt und mit f.seek () den Dateizeiger wieder an den Anfang der unvollständigen Zeile bewegt. In diesem ActiveState-Rezept finden Sie Informationen zum Arbeitscode.


1
Der Punkt war, ich wollte der Datei folgen. Wenn ich eine Datei öffne, wird f.read () nur bis zum Ende der Laufzeit der Datei ausgeführt. Danach wird nichts Neues mehr gelesen.
Eli

1
Ich habe es vor dem Posten getestet. Ich habe gerade: blah = open ('some_file', r) während 1: sleep (1) blah.read () gedruckt und versucht, in die Datei zu schreiben. Kein Glück.
Eli

1
@Eli: Dann solltest du in Windows sein. Dies sind wichtige Informationen, die in Ihrer Frage fehlen.
Paulo Scardine

10
@Paulo: Das sind wichtige Informationen, die in der Antwort fehlen. Wenn kein Betriebssystem angegeben ist, erstellen Sie etwas, das allgemein funktioniert, oder zumindest etwas, das für * nix funktioniert. Sie nehmen niemals Windows an.
Eli

Warum niemals Fenster annehmen? Python ist näher an Windows als an Nix, zB: UTF-16 vs UTF-8
Jasen

8

Alle Antworten, die tail -f verwenden, sind nicht pythonisch.

Hier ist der pythonische Weg: (ohne externes Tool oder Bibliothek)

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
Wenn eine Protokolldatei in 2 Systemaufrufen angehängt wird, gibt diese Art des "Folgens" der Datei manchmal 2 Teile der Zeile zurück, anstatt der vollständigen Zeile selbst
Ferrybig

Ich habe eine Antwort gepostet, um den Fehler zu beheben, auf den @Ferrybig hingewiesen hat: stackoverflow.com/a/54263201/431087
Isaac Turner

Angenommen, ein anderes Python-Programm schreibt mit einem Writer in diese Datei. Gibt es eine Möglichkeit, diesen Vorgang programmgesteuert zu stoppen, wenn der Writer aufhört zu schreiben?
Codeslord

Ja, Sie können einen Mechanismus wie das Sperren verwenden, um das Schloss zu erwerben, bevor Sie darauf schreiben und es freigeben, wenn Sie fertig sind
Ijaz Ahmad Khan

6

Sie können die 'Tailer'-Bibliothek verwenden: https://pypi.python.org/pypi/tailer/

Es besteht die Möglichkeit, die letzten Zeilen abzurufen:

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

Und es kann auch einer Datei folgen:

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

Wenn man schwanzartiges Verhalten will, scheint dies eine gute Option zu sein.


1
Es hat nicht follow()die gleiche Datei, nachdem es entfernt / neu erstellt wurde, also hat es bei mir nicht funktioniert: /
Jose Alban

1
@ JoseAlban Es liegt einfach nicht in der Verantwortung der Bibliothek, auf das Löschen / Erstellen von Dateien zu achten. Verwenden Sie make-all-the-things-work-by-themselvesstattdessen das Pypi-Modul
Pavel Vlasov

3

Eine weitere Option ist die tailheadBibliothek, die sowohl Python-Versionen von tailals auch headDienstprogramme und API bereitstellt, die in Ihrem eigenen Modul verwendet werden können.

Ursprünglich basierend auf dem tailerModul, besteht sein Hauptvorteil in der Möglichkeit, Dateien nach Pfad zu folgen, dh es kann Situationen behandeln, in denen Dateien neu erstellt werden. Außerdem hat es einige Fehlerbehebungen für verschiedene Randfälle.


1

Python ist "Batterien enthalten" - es hat eine gute Lösung dafür: https://pypi.python.org/pypi/pygtail

Liest nicht gelesene Protokolldateizeilen. Erinnert sich, wo es das letzte Mal beendet wurde, und fährt von dort fort.

import sys
from pygtail import Pygtail

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

14
Das Installieren eines Pakets, um eine Funktionalität zu erhalten, ist das Gegenteil von "Batterien enthalten".
Bfontaine

Glücklicherweise sind nicht alle Pakete standardmäßig installiert. Sie müssen jedoch keinen kniffligen Code mithilfe von Unterprozessen schreiben (und debuggen und warten), wie Antworten mit viel höherem Karma nahe legen.
Peter M. - steht für Monica

@Eli - ja, Pygtail wird in Ihrer Antwort erwähnt, hat aber kein Beispiel dafür, wie einfach es zu bedienen ist. Und übrigens habe ich Ihre Antwort positiv bewertet, also seien Sie bitte nicht zu verärgert :-)
Peter M. - steht für Monica

1
Verwendung der Option --full-lines in Ihrem Pygtail-Beispiel
G.ONE


0

Wenn Sie unter Linux arbeiten, implementieren Sie eine nicht blockierende Implementierung in Python auf folgende Weise.

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

1
Unter Linux mit X und installiertem csh. Das sind viele unnötige Abhängigkeiten!
kmarsh
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.