Hallo,
ich möchte etwas ähnliches wie "tail -f" schreiben. Im Grunde möchte ich logfiles realtime überwachen.
Lösung 1:
Ich lese die gewünschten Dateien in einer Endlosschleife ein und immer wenn das Zeilenende(bzw. "") erreicht wurde, wartet das "Programm" 0.1 Sekunden(um das System nicht unnötig zu belasten) und liest danach weiter. Logrotate könnte hier ein Problem werden.
Lösung 2:
Ich nehme die FUSE Bindings und schreib mir ein Filesystem das diese Aufgabe für mich erledigt. Ich nehme an das es Performanter ist - was die I/O Last angeht, oder liege ich mit dieser Annahme falsch?
Im Grunde sollen die neuen Logfile Einträge von weiteren Programmen ausgewertet werden.
Kennt ihr noch eine andere Möglichkeit?
lg Richi
tail -f
Wieso sollte ein Dateisystem im Userspace "performanter" sein als ein normales Programm im Userspace?
Was die Implementierung von "tail -f" angeht … was liegt näher, als den Quelltext von "tail" zu lesen? Außerdem bist Du bestimmt nicht der erste, der sowas schreiben möchte, such halt mal im Netz.
Was die Implementierung von "tail -f" angeht … was liegt näher, als den Quelltext von "tail" zu lesen? Außerdem bist Du bestimmt nicht der erste, der sowas schreiben möchte, such halt mal im Netz.
http://paste.pocoo.org/show/141167/
Das Ganze hat allerdings Probleme, wenn die Datei während der Programmausführung weniger Zeilen bekommt, da dann die Cursorposition nicht mehr stimmt, um eine neue Zeile zu lesen. Ich hab da jetzt noch keinen Mechanismus für eingebaut.
Das Ganze hat allerdings Probleme, wenn die Datei während der Programmausführung weniger Zeilen bekommt, da dann die Cursorposition nicht mehr stimmt, um eine neue Zeile zu lesen. Ich hab da jetzt noch keinen Mechanismus für eingebaut.
Ungetestet:
Edit: "start" übersehen.
Code: Alles auswählen
import itertools
def follow_output(stream):
def spam():
while True:
pos = stream.tell()
line = stream.readline()
if not line:
sleep(UPDATE_SEC)
stream.seek(pos)
else:
yield line
start = -abs(LAST_LINES)
return itertools.chain(itertools.islice(stream, start, None), spam())
Das Leben ist wie ein Tennisball.
EyDu hat geschrieben:Ungetestet
Code: Alles auswählen
ValueError: Indices for islice() must be non-negative integers or None.
Stimmt, mit negativen Indizes kann das natürlich nicht funktionieren.
Je nach Menge der Datenkann man natürlich auch folgendes nutzen:
Beim letzten nehme ich mal an, dass LAST_LINES nicht negativ werden darf. Sonst könnte man den index als "index = min(0, -LAST_LINES)" bestimmen, im Gegensatz zu meinem vorherigen Post.
Code: Alles auswählen
def follow_output(stream):
def spam():
while True:
pos = stream.tell()
line = stream.readline()
if not line:
sleep(UPDATE_SEC)
stream.seek(pos)
else:
yield line
if LAST_LINES > 0:
data = stream.readlines()[LAST_LINES:]
else:
data = stream
return itertools.chain(data, spam())
Code: Alles auswählen
def follow_output(stream):
def spam():
while True:
pos = stream.tell()
line = stream.readline()
if not line:
sleep(UPDATE_SEC)
stream.seek(pos)
else:
yield line
return itertools.chain(stream.readlines()[-LAST_LINES:], spam())
Das Leben ist wie ein Tennisball.
Ich war mal so frei, die abgeänderte Variante nochmal hochzuladen: http://paste.pocoo.org/show/141185/
Hm, `data` sollte wohl besser `start` heißen, aber das ist ja jetzt egal.
EDIT:
Wenn `LAST_LINES` negativ ist, wird `tail` auf 0 gesetzt und ein Slice mittels `[0:]` gibt die komplette Liste zurück. 
Den Nachteil, dass man bei großen Dateien auch einen großen Satz `lines` im Puffer hätte, übergehe ich jetzt mal.
Hm, `data` sollte wohl besser `start` heißen, aber das ist ja jetzt egal.

EDIT:
Code: Alles auswählen
tail = min(0, -LAST_LINES)
start = stream.readlines()[tail:]
[...]
return itertools.chain(start, yield_line())

Den Nachteil, dass man bei großen Dateien auch einen großen Satz `lines` im Puffer hätte, übergehe ich jetzt mal.

@lunar:
Ich hab selber funktionstüchtigen Code und auch noch einige Beispiele über Google gefunden. Doch all diese Beispiele sind sich sehr ähnlich, genau so wie bei meinem wird die Datei im Sekunden Intervall überprüft, ob sich etwas geändert hat. Die Größe muss man auch immer im Auge behalten, wegen logroate.
Deshalb wollte ich wissen ob es Performantere Lösungen gibt. Bei FUSE kümmert sich dann Python um das schreiben der Datei, das heißt ich kann schon mitlesen, was geloggt wird, ohne die Datei einlesen zu müssen bzw. die Datei zum lesen zu öffnen und bei vielen Log Dateien macht das schon sinn.
Ein simples Beispiel. Für Log Dateien reicht es, jedoch sollte man eine Fehlerbehandlung einbauen. Immerhin könnte während eines logrotates ein Fehler bezüglich nicht vorhandener Datei kommen. Würde man diese Datei nun mit einem Editor wie vim editieren und Zeilen raus löschen und/oder hinzufügen(irgendwo mitten drinnen), dann würde dieses Beispiel auch nicht mehr funktionieren. Der Cursor würde richtig positioniert werden, jedoch würde immer ein hauch von nichts zurückkommen. Genauso könnte beim Speichervorgang ein Fehler auftreten das die Datei nicht mehr vorhanden währe. Warscheinlich ändert sich der Inode? Man müsste dann die Datei wohl neu öffnen.
lg Richi
Ich hab selber funktionstüchtigen Code und auch noch einige Beispiele über Google gefunden. Doch all diese Beispiele sind sich sehr ähnlich, genau so wie bei meinem wird die Datei im Sekunden Intervall überprüft, ob sich etwas geändert hat. Die Größe muss man auch immer im Auge behalten, wegen logroate.
Deshalb wollte ich wissen ob es Performantere Lösungen gibt. Bei FUSE kümmert sich dann Python um das schreiben der Datei, das heißt ich kann schon mitlesen, was geloggt wird, ohne die Datei einlesen zu müssen bzw. die Datei zum lesen zu öffnen und bei vielen Log Dateien macht das schon sinn.
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
import os, sys
PATH = "path_to_test.log"
TIME_SLEEP = 1
def main():
file = open(PATH, "r")
pos = 0
while 1:
size = os.path.getsize(PATH)
if size >= pos:
pos = file.tell()
line = file.readline()
if not line:
time.sleep(TIME_SLEEP)
file.seek(pos)
else:
print line,
else:
print "file got truncated"
pos = 0
file.seek(pos)
if __name__ == '__main__':
main()
lg Richi
Zuletzt geändert von PNS-Richi am Donnerstag 24. September 2009, 09:30, insgesamt 1-mal geändert.
@PNS-Richi: Auf der anderen Seite Fuse schickt dann alle Schreibzugriffe unter entsprechend vielen Kontextwechseln durch ein im Vergleich zu einem C-Dateisystem relativ langsames Python-Programm. Letztlich erhöhst Du damit die Effizienz der Überwachung auf Kosten der Effizienz beim Schreibzugriff. Außerdem musst Du dann parallel auch woanders hin loggen, schließlich könnte sonst ein Ausnahme an der falschen Stelle wichtige Logeinträge ins Nirvana schicken.
Ist es Dir denn möglich, Logeinträge direkt in eine Datenbank zu schreiben? Mit entsprechenden Triggern sollte es dann doch recht effizient möglich sein, die Dateien annähernd in Echtzeit zu überwachen.
Ansonsten kannst Du noch inotify zur Überwachung der Dateien nutzen, und eventuell in eine Ramdisk loggen, um Festplatten-E/A so weit als möglich zu vermeiden.
Dein Beispiel funktioniert übrigens nur, wenn "copytruncate" in der logrotate-Konfiguration gesetzt ist. Ansonsten verschiebt logrotate die Datei, das Dateiobjekt muss somit neu geöffnet werden.
Ist es Dir denn möglich, Logeinträge direkt in eine Datenbank zu schreiben? Mit entsprechenden Triggern sollte es dann doch recht effizient möglich sein, die Dateien annähernd in Echtzeit zu überwachen.
Ansonsten kannst Du noch inotify zur Überwachung der Dateien nutzen, und eventuell in eine Ramdisk loggen, um Festplatten-E/A so weit als möglich zu vermeiden.
Dein Beispiel funktioniert übrigens nur, wenn "copytruncate" in der logrotate-Konfiguration gesetzt ist. Ansonsten verschiebt logrotate die Datei, das Dateiobjekt muss somit neu geöffnet werden.
@lunar: Da könntest du recht haben mit Python und FUSE, deshalb überleg ich noch, wie ich das alles am besten lösen kann. Das mit FUSE ist trotzdem in Interessanter Ansatz. Immerhin müsste man ja nicht alles ans untere Dateisystem weiterleiten, sondern könnte direkt in eine Datenbank loggen. Ist dann halt ein höherer Aufwand.
Wenn nun logrotate die alte logfile verschiebt, dann würde doch der filehandler auf den falschen inode zeigen, oder? Bei den ganzen "tail -f" Beispielen.
lg Richi
Wenn nun logrotate die alte logfile verschiebt, dann würde doch der filehandler auf den falschen inode zeigen, oder? Bei den ganzen "tail -f" Beispielen.
Code: Alles auswählen
...
inode = os.stat(PATH)[ST_INO]
while 1:
inode_new = os.stat(PATH)[ST_INO]
if inode != inode_new
file = open(PATH, "r")
pos = 0
inode = inode_new
...
Meinem Verständnis nach schon. "rename()" weist einem Inode ähnlich einem Hardlink nur einen neuen Namen zu, während eine neue Datei einen eigenen INode erhält.
Fuse zum Loggen in eine Datenbank erscheint mir übrigens ziemlich kompliziert
Kannst Du nicht direkt ohne Umwege in eine Datenbank loggen, so wie syslog-ng das beispielsweise unterstützt? Oder Lognachrichten zumindest an einen Dienst wie syslog-ng weiterleiten, der sie anschließend in die Datenbank schreiben kann?
Dann könntest Du dein Überwachungsprogramm einfach nur auf die Trigger der Datenbank lauschen lassen, und dann mit einem einfachen SELECT alle neuen Einträge holen.
Fuse zum Loggen in eine Datenbank erscheint mir übrigens ziemlich kompliziert

Dann könntest Du dein Überwachungsprogramm einfach nur auf die Trigger der Datenbank lauschen lassen, und dann mit einem einfachen SELECT alle neuen Einträge holen.
Klar, würde er das. Wenn eine Datei nicht meher existiert, da sie verschoben oder gelöscht wurde und wenn danach eine neue Datei mit dem Namen der alten erstellt wird, dann bekommt die neue Datei auch einen neuen Inode.
Code: Alles auswählen
sebastian@deepthought:~$ touch test.txt
sebastian@deepthought:~$ stat test.txt
File: „test.txt“
Size: 0 Blocks: 0 IO Block: 4096 reguläre leere Datei
Device: 806h/2054d Inode: 2050009 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1000/sebastian) Gid: ( 1000/sebastian)
Access: 2009-09-24 11:10:19.000000000 +0200
Modify: 2009-09-24 11:10:19.000000000 +0200
Change: 2009-09-24 11:10:19.000000000 +0200
sebastian@deepthought:~$ mv test.txt test2.txt
sebastian@deepthought:~$ touch test.txt
sebastian@deepthought:~$ stat test.txt
File: „test.txt“
Size: 0 Blocks: 0 IO Block: 4096 reguläre leere Datei
Device: 806h/2054d Inode: 2050079 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1000/sebastian) Gid: ( 1000/sebastian)
Access: 2009-09-24 11:10:43.000000000 +0200
Modify: 2009-09-24 11:10:43.000000000 +0200
Change: 2009-09-24 11:10:43.000000000 +0200
@snafu: So klar ist das nur beim Verschieben. Wenn man eine Datei löscht, und eine Neue anlegt, kann die Neue durchaus den Inode der Alten bekommen. Der wird durch das Löschen ja wieder frei.
Ach syslog hab ich erst vor kurzem ersetzt. Hab mir da etwas eigenes geschrieben das sich an die RFC des „syslog Protokolls“ hält. Ok wie sinnvoll es ist, ist mir egal – mir geht’s hauptsächlich um den Lerneffekt und den Spaß. Lauscht genau so an einem Unix und Network Socket mit Datagram Protokoll. Benutzt jedoch dann TCP/IP um es an einen anderen LogServer weiter zu leiten(natürlich eigener Dienst, TCP/IP auf UDP wäre etwas sinnlos), oder eben direkt in eine Datenbank. Dazu gibt es ein Unix Socket, damit es mit anderen Programmiersprachen möglich ist die neuen Log Einträge zu parsen. Immerhin taugt TCP/IP mehr als UDP, wenn das Netz stark ausgelastet ist. Nun wollte ich halt, jedenfalls seit gestern auch noch nen Daemon schreiben der vorhandene Log Dateien überwacht und an weitere Dienste weiterleitet die auf gewisse Einträge reagieren sollen, oder sie in diverse Datenbanken eintragen können – klingt immer so einfach, deshalb das „tail –f“ Beispiel. Klar könnte man auch ein Plugin System schreiben, aber so kann ich Beispiele dazu in anderen Sprachen schreiben, überhaupt Gambas wäre dafür mal interessant. Geht rein um den Spaß bei der Sache 

Gambas ist doch Visual Basic?! Lernen hin oder her, aber dazu muss man sich doch nicht herablassen 
Im Ernst: Wenn es Dir um das Lernen geht, stellt sich die Frage nach dem Sinn ja nicht. Wenn Du aber eh die Kontrolle über den Logging-Dienst hast, dann ist es imho halt einfacher, in eine Datenbank zu loggen, die man aus jeder Sprache problemlos und ohne Kopfzerbrechen über Synchronisation und Atomarität von Dateisystemoperationen nutzen kann.

Im Ernst: Wenn es Dir um das Lernen geht, stellt sich die Frage nach dem Sinn ja nicht. Wenn Du aber eh die Kontrolle über den Logging-Dienst hast, dann ist es imho halt einfacher, in eine Datenbank zu loggen, die man aus jeder Sprache problemlos und ohne Kopfzerbrechen über Synchronisation und Atomarität von Dateisystemoperationen nutzen kann.