Discussion:
Lesen einer kontinuierlich ergänzten Log File
(zu alt für eine Antwort)
Warringer
2011-07-17 10:59:08 UTC
Permalink
Ich habe hier ein kleines Problem, bei dem ich gerne eine CSV Logfile
lesen möchte die mehrmals pro Sekunde durch neue Datensätze ergänzt
wird.

Gewissermaßen ein CSV Stream.

Jetzt würde ich gerne wissen, ob es da eine Möglichkeit gibt eine
derartige Log File zu lesen ohne jedes mal die vorherigen Datensätze
immer wieder neu einlesen zu müssen.

Ich kann mir zwar vorstellen dass es damit geht die Datei auf
Modifikation zu überwachen, aber ich wünsche mir da etwas eleganteres.

Vielen Dank für jegliche Hilfe die ich bekommen könnte.

- Tobias Funke
Florian Weimer
2011-07-17 11:24:23 UTC
Permalink
Post by Warringer
Jetzt würde ich gerne wissen, ob es da eine Möglichkeit gibt eine
derartige Log File zu lesen ohne jedes mal die vorherigen Datensätze
immer wieder neu einlesen zu müssen.
Du benötigtst eine Art LineNumberReader, der die aktuelle
Dateiposition in Bytes ausgegeben kann, und ein paar Heuristiken zum
Erkennen einer ersetzten Logdatei. Extrem effizient wird das nie, aber
das quadratische Laufzeitverhalten ist wenigstens weg.
Marcel Müller
2011-07-18 09:35:24 UTC
Permalink
Hallo,
Post by Florian Weimer
Du benötigtst eine Art LineNumberReader, der die aktuelle
Dateiposition in Bytes ausgegeben kann, und ein paar Heuristiken zum
Erkennen einer ersetzten Logdatei. Extrem effizient wird das nie, aber
das quadratische Laufzeitverhalten ist wenigstens weg.
das geht hocheffizient. Wir machen das seit Jahren und das nicht nur in
Java, sondern auch etlichen anderen Sprachen, sogar mit rollierenden
Logfiles. Der Parser ist komplett O(n), praktisch unabhängig von der
Updaterate. Und irgendeine Heuristik kommt da auch nicht zum Einsatz,
das Ding funktioniert sogar 100% sicher, wenn einer meinte eine 1MB
Binärdatei in den Logtext schreibt.

Die Kunst beginnt allerdings schon beim Schreiben der Logs:

- Man sollte sinnvollerweise einzelne Log-Einträge atomar schreiben. Das
gilt vor allem dann, wenn mehrere Java-VMs in dieselben Files schreiben
sollen. Im java.nio gibt es dafür geeignete Sperrmechanismen, die auch
noch nicht geschriebene Bereiche sperren können.
Dummerweise gibt es kein passendes Semaphor dazu in Java, was auf die
Entsperrung warten würde, weshalb man auf ein Spin-Lock mit Sleeps
dazwischen ausweichen muss. Die meisten Betriebssysteme oder Java-VMs
warten aber von sich aus einige Millisekunden, bevor sie einen Fehler
melden. Das kommt also in der Praxis fast nie vor.
Das Schreiben sollte tunlichst in einem write-Aufruf erfolgen, also
vorher schon den Log-Eintrag vorbereiten, um ein unnötig langes Halten
der Sperre zu vermeiden. Zudem sollte die Sperre exceptionsicher
freigegeben werden (finally).

- Dann sollte das CSV-Format Erdbebensicher sein. Also alle geeigneten
Escapes Nutzen. Texte in Gänsefüßchen, Gänsefüßchen in Texten
verdoppeln. Das funktioniert sogar, wenn Zeilensprünge im Text stehen,
da selbige zwischen den Gänsefüßchen eben /keine/ Satztrenner sind.

- Falls man mit größenbegrenzten Logs arbeiten möchte (immer dringend zu
Empfehlen), sollte man NIEMALS das Verfahren des Umbenennens wählen (wie
Linux syslogd, log4j). Dies würde eine vollständige Synchronisation
aller schreibenden und lesenden Prozessese voraussetzen. Stattdessen
verpasst man den Logfiles /streng monoton aufsteigende/ Nummern. Das
kann einfach ein sortierbarer Zeitstempel sein. Also z.B.
MyLog_2011-07-17_23-12-25.

- Wenn man das genommen hat, kann man einen Parser schreiben, der sich
einfach immer die Byteposition hinter dem zuletzt erfolgreich gelesenen
Ereignis merkt. Das ist sein Wiederaufsetzpunkt. Den Parser kann man so
schreiben, dass er entweder ebenfalls vom Sperrmechanismus der
schreibenden Prozesse Gebrauch macht oder einfach schaut, ob die
CSV-Zeile vollständig ist. Wenn nein, bleibt es beim letzten
Wiederaufsetzpunkt und der halbe Logeintrag muss halt beim nächsten mal
erneut gelesen werden. Ich empfehle letzteres, weil dabei ein
fehlerhaftes Programm, das die Logs liest, nicht den produktiven Prozess
beeinträchtigen kann (Halten der Sperre).

- Wenn man, wie oben beschrieben, mit größenbegrenzten Logs arbeitet,
muss man sich zusätzlich den Dateinamen des Wiederaufsetzpunktes merken.
Noch nicht gelesene Log-Einträge liegen dann notwendigerweise in dem
genannten Dateinamen hinter der gespeicherten Position oder in
lexikalisch größeren Dateinamen ab Position 0. Die Existenz eines
größeren Dateinamens nach dem Erreichen des Endes der aktuellen Datei
ist ein hinreichendes Kriterium dafür, dass die letzte Datei nicht mehr
angefasst werden muss. Man muss sich also immer nur eine Datei und
Position merken.
Ein Pferdefuß bleibt: Das Auffinden aller Dateien mit passendem Namen
ist bei allen mir bekannten Dateisystemen außer HPFS ein vollständiger
Scan des gesamten Verzeichnisinhalts. Kein Hexenwerk wenn man nicht mehr
als 30 Dateien hält. Aber das Ablegen von tausenden von Logs, auch mit
komplett anderen Namen, in einem Verzeichnis ist natürlich ein absolutes
no-go. Man sollte also tunlichst für jeden Satz Logfiles ein eigenes
Verzeichnis nehmen.

- Um Ereignisgesteuert auf neue Log-Einträge zu reagieren, könnte man
sich zwar an den Modifikationszeitstempel des Dateisystems hängen, aber
das hat verschiedene Nachteile. Zum einen aktualisieren manche
Dateisysteme diesen nur, wenn eine schreibende Anwendung die Datei
schließt. Das könnte theoretisch überhaupt nicht passieren, wenn die
schreibende Anwendung ein Serverdienst ist, und die Datei immer offen
hält. Zum anderen hat dieser Zeitstempel nur eine endliche Genauigkeit
und ist zwar monoton, aber nicht streng monoton. Sprich ein in einer
Sekunde geschriebenes /und/ bereits gelesenes Ereignis könnte in
derselben Sekunde um ein weiteres und letztes Ereignis ergänzt werden,
ohne das dies jemals am Zeitstempel auffallen würde.
Es bleibt also faktisch nur Polling, also regelmäßig nachgucken, ob es
etwas neues gibt. Das wiederum ist aber ob der oben genannten Position
kaum mehr aufwändig als das Lesen des Zeitstempels, denn man öffnet die
Datei, macht ein Seek zur Position und holt sich die End-of-File
Bedingung ab (oder eben auch nicht). Das alles fackelt jeder Disk-Cache ab.
Das Verwenden der Dateigröße aus dem Verzeichniseintrag ich aus
denselben Gründen problematisch, wie beim Zeitstempel. Nicht alle
Dateisysteme aktualisieren den Verzeichniseintrag bevor die Datei
geschlossen wird. Es kann also durchaus vorkommen, dass man mehr Bytes
aus einer Datei lesen kann, als der Verzeichniseintrag suggeriert. Wenn
das nicht stört kann man das aber auch dazu verwenden, um mit nahezu
niemals halb geschriebene Logeinträge präsentiert zu bekommen, denn
keine mir bekannte Plattform aktualisiert die Länge mehrmals während
eines Systemaufrufs zum Schreiben.


Marcel
Ali Al-kheder
2020-11-20 17:12:44 UTC
Permalink
Jetzt w�rde ich gerne wissen, ob es da eine M�glichkeit gibt eine
derartige Log File zu lesen ohne jedes mal die vorherigen Datens�tze
immer wieder neu einlesen zu m�ssen.
Du ben�tigtst eine Art LineNumberReader, der die aktuelle
Dateiposition in Bytes ausgegeben kann, und ein paar Heuristiken zum
Erkennen einer ersetzten Logdatei. Extrem effizient wird das nie, aber
das quadratische Laufzeitverhalten ist wenigstens weg.
ß0987654321^äölkjhgfdsaخهعغفقثصض
Claus Reibenstein
2020-11-23 14:16:00 UTC
Permalink
¯¯¯¯

Muss man das noch kommentieren?

Gruß
Claus

Michael Schulz
2011-07-17 11:57:35 UTC
Permalink
Post by Warringer
Ich habe hier ein kleines Problem, bei dem ich gerne eine CSV Logfile
lesen möchte die mehrmals pro Sekunde durch neue Datensätze ergänzt
wird.
Gewissermaßen ein CSV Stream.
Jetzt würde ich gerne wissen, ob es da eine Möglichkeit gibt eine
derartige Log File zu lesen ohne jedes mal die vorherigen Datensätze
immer wieder neu einlesen zu müssen.
Kannst Du den Reader nicht einfach offenlassen?
Bernd Hohmann
2011-07-17 13:59:11 UTC
Permalink
Post by Warringer
Jetzt würde ich gerne wissen, ob es da eine Möglichkeit gibt eine
derartige Log File zu lesen ohne jedes mal die vorherigen Datensätze
immer wieder neu einlesen zu müssen.
Vielleicht reicht es, wenn man einfach treudoof einliest wenn was da
ist. Allerdings hab ich das jetzt nicht getestet was passiert wenn der
BufferedReader nach .ready() nur einen halben Satz erwischt, aber ich
glaube dass der wartet bis ein \r oder \n kommt.

import java.io.*;
class Test {
public Test() {super();}

public static void main(String args[]) throws Throwable {
String buffer = null;
FileReader fr = new FileReader("/var/log/syslog");
BufferedReader br = new BufferedReader(fr);
while (true) {
if (!fr.ready()) {
Thread.sleep(1000);
continue;
}
while ((buffer = br.readLine()) != null)
System.out.println(buffer);
}
}
}


Die Pause entsprechend klein machen oder weglassen.

Bernd
--
Wenn Frauen nicht wissen was sie machen sollen, ziehen sie sich aus ...
Und wenn Männer nicht wissen, was sie machen sollen, dann schauen sie
sich Frauen an, die nicht wussten, was sie machen sollen ...
Loading...