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.

Eli
quelle
1
subprocess.call(["tail", "-F", filename])
Whymarrh
Siehe diese Antwort .
Avaris
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().

Matt
quelle
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.

Paulo Scardine
quelle
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
nneonneo
quelle
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 )

Eli
quelle
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='')
Isaac Turner
quelle
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.

Raymond Hettinger
quelle
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='')
Ijaz Ahmad Khan
quelle
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.

Haroldo_OK
quelle
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.

Kentzo
quelle
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)
Peter M. - steht für Monica
quelle
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"
Anand Satya
quelle
1
Unter Linux mit X und installiertem csh. Das sind viele unnötige Abhängigkeiten!
kmarsh