PDA

View Full Version : [Java] Come fare un log parser che simula tail -f?


tylerdurden83
01-08-2008, 13:52
Ciao a tutti. Avrei bisogno di un consiglio su come realizzare l'oggetto del topic. Fin'ora non ho mai scritto programmi che non utilizzassero gli stream per leggere da file di input, mentre ora avrei bisogno di:

mettermi in "tail" su un certo log
possibilmente filtrare la roba che viene effettivamente caricata in memoria
parsare la roba che viene caricata in memoria

Il punto 3 è ok in quanto è virtualmente analogo a ciò che ho gia, i primi due sono più oscuri. Qualcuno mi sa indicare una libreria o qualcosa di utile?

Grazie,
TD

ally
01-08-2008, 16:44
...mi verrebbe da consigliare l'uso di librerie ssh come le jsch e catturare l'output della console...

...ciao...

ally
01-08-2008, 16:52
...ma a volte la soluzione è piu' banale...


public class Tail {

public static void main(String[] args) {

new Tail("c:/test.txt");

}

public Tail(String file){

try{
Reader fileReader = new FileReader(file);
BufferedReader input = new BufferedReader(fileReader);
String line = null;
while (true) {
if ((line = input.readLine()) != null) {
System.out.println(line);
continue;
}
try {
Thread.sleep(1000L);
} catch (InterruptedException x) {
Thread.currentThread().interrupt();
break;
}
}
input.close();
}catch(Exception e){
System.out.println(e);
}
}
}


...ciao...

tylerdurden83
01-08-2008, 20:46
Innanzitutto grazie mille perchè dovrebbe andare bene!
Avrei però un paio di domande...

1) immagino che non sia possibile evitare di fare polling ogni x secondi, ma innescare una read solo quando lo stato del file cambia?

2) scusa ma dove vai di next() sulla linea che leggi?


EDIT: l' ho provato e questo è quello che fa...

ho scritto
x
y
z

su un file, e un programma ke scrivesse sullo stesso file
x
y
z
a
b
c

Ho lanciato prima il programma di tail, che mi ha quindi stampato
x
y
z

dato che nel file erano gia presenti. Poi ho lanciato lo scrittore, che è andato a sovrascrivere il precedente file, tuttavia riscrivendo i primi 3 caratteri identici a quelli che c'erano prima.
Con mio grande stupore ho notato che il programma di tail però NON li ha ristampati, ha stampato solo i nuovi, quindi
x
y
z
a
b
c

mentre mi sarei per lo meno aspettato
x
y
z
x
y
z
a
b
c

Umm, sono un po' confuso...

EDIT2: il continue serve a non mandare il thread in wait finchè ho linee da processare, giusto?

tylerdurden83
04-08-2008, 09:57
mi sorge un dubbio, non è che rilegge il log dall'inizio?...

ally
04-08-2008, 10:01
mi sorge un dubbio, non è che rilegge il log dall'inizio?...

...dal tuo post precedente si capisce il contrario...come giustamente dovrebbe comportarsi...cioè il programma scive solamente le nuove linee aggiunte al file di testo come un tail -f fa...

...ciao...

tylerdurden83
06-08-2008, 09:02
...dal tuo post precedente si capisce il contrario...come giustamente dovrebbe comportarsi...cioè il programma scive solamente le nuove linee aggiunte al file di testo come un tail -f fa...

...ciao...

si lasciamo perdere pensavo di scrivere in append sul file di input...

anyway, ho una domanda:

Reader fileReader = new FileReader(log);
BufferedReader input = new BufferedReader(fileReader);
String line = null;

while (true) {
if ((line = input.readLine()) != null) {

se non ho capito male, il BufferedReader legge tutto il file di input, readLine ne legge riga per riga, sia quelle già presenti al lancio che quelle che si aggiungono successivamente, però alla fine input contiene una bufferizzazzione di tutto il log, anche quando, una volta letto tutto, resto in ascolto e quindi catturo solo le nuove righe.
In pratica, se il file di input è un log enorme, lo bufferizza, legge e lavora, ma quando si mette effettivamente in tail, non svuota il buffer con tutta la roba preesistente...

incipit1970
06-08-2008, 11:18
si lasciamo perdere pensavo di scrivere in append sul file di input...

anyway, ho una domanda:

Reader fileReader = new FileReader(log);
BufferedReader input = new BufferedReader(fileReader);
String line = null;

while (true) {
if ((line = input.readLine()) != null) {

se non ho capito male, il BufferedReader legge tutto il file di input, readLine ne legge riga per riga, sia quelle già presenti al lancio che quelle che si aggiungono successivamente, però alla fine input contiene una bufferizzazzione di tutto il log, anche quando, una volta letto tutto, resto in ascolto e quindi catturo solo le nuove righe.
In pratica, se il file di input è un log enorme, lo bufferizza, legge e lavora, ma quando si mette effettivamente in tail, non svuota il buffer con tutta la roba preesistente...

No, BufferedReader non bufferizza tutto il file, semplicemente usa un buffer intermedio per evitare di leggere dallo stream sottostante ad ogni richiesta. Puoi indicare le dimensioni del buffer nel costruttore del BufferedReader, e se non lo specifichi usa una dimensione fissa di 8Kb. Quindi puoi leggere un file di qualsiasi dimensione, il BufferedReader terrà in buffer solo gli ultimi 8kb letti.

Il codice di ally dovrebbe funzionare bene, l'unico dettaglio è che legge sempre tutto il file, mentre il tail -f apre il file "dalla fine", non scoda tutto il contenuto. Una variazione semplice che tenga questo di conto potrebbe essere:

public class TailLogger {

public static void main(String[] args) throws Exception {
new TailLogger("C:/test.txt");
}

public TailLogger(String file) {
try{
FileInputStream fs = new FileInputStream(file);
fs.skip(fs.available());
Reader fileReader = new InputStreamReader(fs);
BufferedReader input = new BufferedReader(fileReader);
String line = null;
while (true) {
if ((line = input.readLine()) != null) {
System.out.println(line);
continue;
}
try {
Thread.sleep(1000L);
} catch (InterruptedException x) {
Thread.currentThread().interrupt();
break;
}
}
input.close();
}catch(Exception e){
System.out.println(e);
}
}
}

In pratica, si apre lo stream e si skippa tutto il contenuto del file prima di mettersi in tail sul file stesso. Altrimenti, se ti vai a mettere in coda su un file grosso, ti tocca prima leggerti tutto il contenuto. Questo è un problema se, invece di semplicemente stampare il contenuto del file, devi farci ad esempio dei conteggi o qualcosa del genere. Poi dipende del tipo specifico di uso che devi dare al codice, forse rileggere tutto il file dal inizio prima di metterti in coda ti serve proprio...

ally
06-08-2008, 11:33
Il codice di ally dovrebbe funzionare bene, l'unico dettaglio è che legge sempre tutto il file, mentre il tail -f apre il file "dalla fine", non scoda tutto il contenuto. Una variazione semplice che tenga questo di conto potrebbe essere:



...si giustissimo...effettivamente il tail al massimo recupera le ultime 5 righe del file che si sta esaminando...

...ciao...

tylerdurden83
06-08-2008, 22:59
Grazie di nuovo ad entrambi per i consigli!

La storia del leggere tutto il log, di per sè, direi che mi farebbe anche comodo, ti spiego da dove m'è sorto quel quesito però.

In pratica, lanciando il jar sotto windows, e dandogli in pasto un log di prova con una 30 di entry, vedo ke il processo occupa sui 17 mega di ram.

Mentre, lanciandolo sotto unix, e dandogli in pasto un log di migliaia di entry (log che comunque pesava max 10 mega), il processo inspiegabilmente occupa 623 mega! Tu mi dirai, se pure bufferizzasse tutto il log da 10 mega, come ci arriva a 600+? Non ne ho idea ad essere sincero, il jar è lo stesso, cambiano solo il log e l'SO...

Per quanto riguarda il mio primissimo quesito posto, cioè come si spostasse senza usare una funzione next(), solo ora ho trovato una spiegazione, le api potrebbero spiegare un pochino meglio cosa fa esattamente readline() invece di dire solo legge una riga...

incipit1970
07-08-2008, 09:21
Riguardo al next(), il reader non è un Iterator e dunque non c'è motivo per cui dovrebbe avere una funzione next(). Quasi tutte le API di I/O di Java usano degli stream, ed i Reader hanno dunque la logica di uno stream (leggi lo stream andando in avanti, quindi quando leggi una riga con readLine() il puntatore del file si sposta di conseguenza e la prossima lettura parte da dove è rimasta la precedente). E' poco ovvio ma quando ci hai fatto l'abitudine non ci pensi più.

Per quanto riguarda l'utilizzo di memoria, i fattori possono essere tanti, JVM diverse e diversa gestione della garbage collection e quant'altro. In ogni caso ti confermo che, nella jvm 1.5, il BufferedReader usa una cache di 8kb, ho verificato nella sorgente della libreria. Direi dunque che il problema sia che per ogni lettura che fai crei da qualche parte un oggetto String che contiene la riga letta. Prova a ridurre la quantità di memoria per la vm (opzione -xmx), metti una quantità piccola, 30M o giù di lì, e vedi come va.

tylerdurden83
08-08-2008, 18:12
Allora, il comando per forzare il processo ad occupare max 30 mega è andato bene, anche se alla fine facendo TOP mi dice sui 34, comunque meglio di 600!

Mi sfugge ancora una cosa sugli stream. Metti che creo lo stream di un file, salta il contenuto pre-esistente e si mette in tail.

Se da shell faccio > nomelog.log, il programma rimane running ma non leggerà mai più da quel log...

tylerdurden83
02-09-2008, 08:38
UPDATE:

ragazzi, ho di nuovo bisogno del vostro aiuto.

Ho messo su l'applicazione. Avendo necessità di mettermi in tail su due log diversi ho fatto in modo di creare un thread per ogni log, che si mette in

while (true) {
if ((line = input.readLine()) != null) {

su di essi, così come mi avevate suggerito.
Ulteriore complicazione era la rotazione dei log una volta raggiunta una certa dimensione, per cui ho messo un controllo, se la dimensione precedente è > della corrente rigrea lo stream etc, e sta funzionando.

Il problema enorme è però che si perde qualche riga. Mi spiego, facendo dei controlli rispetto ai log originali, mettendo delle print su un nuovo log subito dopo la
if ((line = input.readLine()) != null) {
mi sono accorto che alcune righe non le ha proprio lette.

So che è difficile debuggare così, le sole cose che vi posso dire sono:
- le righe perse non sono la prima o l'ultima di un nuovo log dopo aver ruotato, ma al centro di log da cui ha letto la riga prima e quella dopo
- i thread non cadono, ho messo un controllo periodico (isAlive)
- il processo non sfora mai la memoria impostata dalla virtual machine come mi avete detto di fare. A parte che se non ho capito male dovrebbe lanciare un'eccezione qualora ciò avvenga, ad ogni modo non si è mai avvicinato manco lontanamente al max heap size memory impostato
- ovviamente il programma non genera nessuna eccezione, nè tantomeno cade mai

Ho deciso quindi di fare una prova di "stress" sulla mia macchina locale (windows, java 1.6 - mentre la macchina di lavoro è unix, java 1.5, sto compilando sulla mia con l'opzione -target 1.5). Ho pensato potesse essere un problema di troppe entry nello stesso istante nel log su cui sono in tail, così ho scritto un'applicazioncina che mi spara sui due log 2 righe, il tutto in un ciclo for i=0 i<15.000, in 3-4 secondi spara 15.000 righe per log, eppure non ne ha persa manco una.

Non so più che pensare, nè che prove fare. L'unica cosa che non ho provato ancora tra le 1000 che ho provato è sostituire
if ((line = input.readLine()) != null)
con
if (bufferedReader.ready()==true) {
line = input.readLine();

Grazie 1000 a tutti per l'aiuto,
TD

tylerdurden83
02-09-2008, 17:13
Boh, ho fatto altre prove...

Ho messo il jar in ascolto su un log mio usato per i test, vuoto.

Ho provato, sul server unix, a sparare con un altro jar 15000 righe una alla volta, e non ha dato problemi.
Ho provato a spararle 500 alla volta, cioè creando una stringa che concat("\n") e concat(riga successiva), quindi in una sola write sul log di test ho scritto 500 righe, anche in questo caso nessun problema....

Non ho proprio più idea di dove andare a parare....fin'ora in ogni test non ha mai perso una sola riga, anche se i test sono stati molto più "stressanti" rispetto ai log su cui sta in tail normalmente, eppure pare perdere dati solo da quelli....