Implementación de Java IO de Unix / Linux “tail -f”

Me pregunto qué técnicas y / o biblioteca usar para implementar la funcionalidad del comando de Linux “tail -f”. Básicamente, estoy buscando un complemento de add-on / replacement para java.io.FileReader . El código del cliente podría verse más o menos así:

 TailFileReader lft = new TailFileReader("application.log"); BufferedReader br = new BufferedReader(lft); String line; try { while (true) { line= br.readLine(); // do something interesting with line } } catch (IOException e) { // barf } 

La pieza faltante es una implementación razonable de TailFileReader . Debería poder leer partes del archivo que existen antes de que se abra el archivo, así como las líneas que se agregan.

La capacidad de continuar leyendo un archivo y esperar hasta que el archivo tenga más actualizaciones no debería ser tan difícil de lograr en el código. Aquí hay un pseudo-código:

 BufferedReader br = new BufferedReader(...); String line; while (keepReading) { line = reader.readLine(); if (line == null) { //wait until there is more of the file for us to read Thread.sleep(1000); } else { //do something interesting with the line } } 

Supongo que le gustaría poner este tipo de funcionalidad en su propio subproceso, para que pueda dormir y no afectar a otras áreas de su aplicación. keepReading exponer keepReading en un setter para que su clase principal / otras partes de la aplicación puedan cerrar el hilo de forma segura sin ningún otro dolor de cabeza, simplemente llamando a stopReading() o algo similar.

Eche un vistazo a la implementación Apache Commons de la clase Tailer . Parece que también maneja la rotación de registros.

Compruebe JLogTailer , que hace esta lógica.

El punto principal en el código es:

 public void run() { try { while (_running) { Thread.sleep(_updateInterval); long len = _file.length(); if (len < _filePointer) { // Log must have been jibbled or deleted. this.appendMessage("Log file was reset. Restarting logging from start of file."); _filePointer = len; } else if (len > _filePointer) { // File must have had something added to it! RandomAccessFile raf = new RandomAccessFile(_file, "r"); raf.seek(_filePointer); String line = null; while ((line = raf.readLine()) != null) { this.appendLine(line); } _filePointer = raf.getFilePointer(); raf.close(); } } } catch (Exception e) { this.appendMessage("Fatal error reading log file, log tailing has stopped."); } // dispose(); } 

Construí una implementación corta de “tail -f” en Scala hace un tiempo: tailf . También se ocupa de la rotación de archivos y puede definir su propia lógica qué hacer cuando llega a EOF o encuentra que el archivo ha sido renombrado.

Puedes echarle un vistazo y llevarlo a Java, ya que en realidad no hay nada complejo allí. Pocas notas: el archivo principal es Tail.scala y básicamente define FollowingInputStream que se encarga de EOF / rename y follow método, que envuelve a FollowingInputStream en una enumeración ilimitada en SequenceInputStream . Entonces, tan pronto como FollowingInputStream finaliza, SequenceInputStream solicita el siguiente elemento de una Enumeration y se crea otro FollowingInputStream .

Me encontré recientemente con rxjava-file , es una extensión de RxJava . A diferencia de las otras soluciones, esto hace uso del NIO de Java.

 import rx.Observable; import rx.functions.Action1; import com.github.davidmoten.rx.FileObservable; // ... class definition omitted public void tailLogFile() throws InterruptedException { Observable tailer = FileObservable.tailer() .file("application.log") // absolute path .tailText(); tailer.subscribe( new Action1() { @Override public void call(String line) { System.out.println("you got line: " + line); } }, new Action1() { @Override public void call(Throwable e) { System.out.println("you got error: " + e); e.printStackTrace(); } } ); // this solution operates threaded, so something // is required that prevents premature termination Thread.sleep(120000); } 

Aquí hay una historia corta que puedes usar como un puntero:

He codificado TailingInputStream en el trabajo por la misma razón. Básicamente, utiliza File y actualiza sus contenidos según demanda y se compara con el búfer interno si ha cambiado significativamente (4 KB de memoria de sello IIRC) y luego hizo lo que hace el tail -f. Un poco hacky, sí, pero funciona perfectamente y no se mezcla con Threads o algo así de sofisticado; es compatible desde 1.426 al menos.

Dicho esto, fue mucho más fácil de hacer que ReverseInputStream, que pasó del final del archivo al inicio y no murió si el archivo se actualizó sobre la marcha …

Si su código solo tendrá que ejecutarse en los sistemas Unix, podrá salirse con la suya y llamar a tail -f directamente.

Como una alternativa más complicada, podría echar un vistazo a la implementación de la cola y el puerto de GNU que se extienden a Java. (No estoy seguro de si esto ya no haría que tu código fuera un trabajo derivado, sin embargo).

Solo me enfrenté al mismo problema: encontré la implementación más simple aquí: Java Tail .

* Gran material * – listo para producción;)

Espero que la cita del código no deje caer una licencia.

  import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; /** * Java implementation of the Unix tail command * * @param args[0] File name * @param args[1] Update time (seconds). Optional. Default value is 1 second * * @author Luigi Viggiano (original author) http://it.newinstance.it/2005/11/19/listening-changes-on-a-text-file-unix-tail-implementation-with-java/ * @author Alessandro Melandri (modified by) * */ public class Tail { static long sleepTime = 1000; public static void main(String[] args) throws IOException { if (args.length > 0){ if (args.length > 1) sleepTime = Long.parseLong(args[1]) * 1000; BufferedReader input = new BufferedReader(new FileReader(args[0])); String currentLine = null; while (true) { if ((currentLine = input.readLine()) != null) { System.out.println(currentLine); continue; } try { Thread.sleep(sleepTime); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } input.close(); } else { System.out.println("Missing parameter!\nUsage: java JavaTail fileName [updateTime (Seconds. default to 1 second)]"); } } } 

Encontré esta buena implementación de cola.

Autor: amelandri

Souce desde: https://gist.github.com/amelandri/1376896

 import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; /** * Java implementation of the Unix tail command * * @param args[0] File name * @param args[1] Update time (seconds). Optional. Default value is 1 second * * @author Luigi Viggiano (original author) http://it.newinstance.it/2005/11/19/listening-changes-on-a-text-file-unix-tail-implementation-with-java/ * @author Alessandro Melandri (modified by) * */ public class Tail { static long sleepTime = 1000; public static void main(String[] args) throws IOException { if (args.length > 0){ if (args.length > 1) sleepTime = Long.parseLong(args[1]) * 1000; BufferedReader input = new BufferedReader(new FileReader(args[0])); String currentLine = null; while (true) { if ((currentLine = input.readLine()) != null) { System.out.println(currentLine); continue; } try { Thread.sleep(sleepTime); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } input.close(); } else { System.out.println("Missing parameter!\nUsage: java JavaTail fileName [updateTime (Seconds. default to 1 second)]"); } } }