Implementación de Java IO de Unix/Linux "tail-f"
file file-io (9)
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.
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 ...
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 Tail.scala 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
.
Eche un vistazo a la implementación Apache Commons de la clase Tailer . Parece que también maneja la rotación de registros.
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)]");
}
}
}
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.
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<String> tailer = FileObservable.tailer()
.file("application.log") // absolute path
.tailText();
tailer.subscribe(
new Action1<String>() {
@Override
public void call(String line) {
System.out.println("you got line: " + line);
}
},
new Action1<Throwable>() {
@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);
}
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)]");
}
}
}