Java IO Implementierung von Unix / Linux "tail -f"

72

Ich frage mich, welche Techniken und / oder Bibliotheken verwendet werden sollen, um die Funktionalität des Linux-Befehls "tail -f" zu implementieren. Ich bin im Wesentlichen auf der Suche nach einem Tropfen Add-On / Ersatz für java.io.FileReader. Der Client-Code könnte ungefähr so ​​aussehen:

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
}

Das fehlende Stück ist eine vernünftige Umsetzung von TailFileReader. Es sollte in der Lage sein, Teile der Datei zu lesen, die vor dem Öffnen der Datei vorhanden sind, sowie die hinzugefügten Zeilen.

Gary
quelle

Antworten:

39

Die Möglichkeit, eine Datei weiter zu lesen und zu warten, bis die Datei weitere Updates für Sie enthält, sollte im Code selbst nicht so schwer zu erreichen sein. Hier ist ein Pseudocode:

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
    }
}

Ich würde davon ausgehen, dass Sie diese Art von Funktionalität in einen eigenen Thread einfügen möchten, damit Sie sie in den Ruhezustand versetzen und keine anderen Bereiche Ihrer Anwendung beeinflussen können. Sie möchten keepReadingin einem Setter belichten, damit Ihre Hauptklasse / andere Teile der Anwendung den Thread ohne weitere Kopfschmerzen sicher herunterfahren können, einfach durch Aufrufen stopReading()oder ähnliches.

matt b
quelle
5
Hinweis: Wenn Sie den Vorgang beenden möchten, verwenden Sie br.skip (file.length ()). Ich habe mit RandomAccessReader () experimentiert, aber das ist sehr langsam.
Aaron Digulla
12
Dies berücksichtigt keine Dateikürzungen. Dieser Code schlägt fehl, wenn die Protokolldatei überschrieben wird ... was ein wesentliches Merkmal von tail ist!
Das Rollover von Protokolldateien wird dadurch nicht erledigt.
Sheki
Dies funktioniert für meinen Anwendungsfall und hat mir über 2 Stunden lang den Kopf gebrochen, um eine saubere Lösung zu finden
Sumit Kumar Saha
60

Schauen Sie sich die Apache Commons-Implementierung der Tailer- Klasse an. Es scheint auch die Protokollrotation zu handhaben.

Chetan S.
quelle
Danke vielmals! Übrigens: Wenn die Protokollierung ordnungsgemäß durchgeführt wird ('cp logfile oldfile;> logfile'), sollte die Lösung von matt weiterhin funktionieren, da die Dateireferenz nicht verloren geht!
Karussell
2
Seien Sie gewarnt: Wenn Sie nur vom Ende der Datei aus verfolgen möchten, hat Tailer auch in der Version 2.4 (Stand dieser Version) einige Probleme. Siehe: Issues.apache.org/jira/browse/…
Joe Casadonte
13

Überprüfen Sie JLogTailer , der diese Logik ausführt .

Der Hauptpunkt im Code ist:

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();
}
aldrinleal
quelle
JLogTailer scheint keine Bibliothek zu haben.
Sheki
@ Sheki nur das Glas benutzen? @aldrinleal Ich wollte keine neue Antwort erstellen ... habe nur den Code hier eingefügt. Ich mag Matt's einfachere (+ schnellere?) Version mehr :)
Karussell
Zur Überprüfung des Codes haben Sie die Codierung zum Lesen dieser Zeile nicht angegeben, aber Sie gehen irgendwie davon aus, dass Sie eine Zeichenfolge gelesen haben.
Trejkaz
9

Ich habe vor einiger Zeit eine kurze Implementierung von "tail -f" in Scala erstellt: tailf . Es kümmert sich auch um die Dateirotation und Sie können Ihre eigene Logik definieren, was zu tun ist, wenn EOF erreicht wird oder festgestellt wird, dass die Datei umbenannt wurde.

Sie können einen Blick darauf werfen und es nach Java portieren, da dort eigentlich nichts Komplexes ist. Einige Anmerkungen: Die Hauptdatei ist Tail.scala und definiert im Grunde genommen, FollowingInputStreamwas sich um EOF / Umbenennung und followMethode kümmert , was FollowingInputStreamin eine unbegrenzte Aufzählung in umschließtSequenceInputStream . Sobald dies FollowingInputStreamendet, werden SequenceInputStreamAnforderungen für das nächste Element von einem Enumerationund einem anderen FollowingInputStreamerstellt.

Alexander Azarov
quelle
5

Ich bin kürzlich über die rxjava-Datei gestolpert . Es ist eine Erweiterung von RxJava . Im Gegensatz zu den anderen Lösungen wird hier das NIO von Java verwendet.

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);
}
cheffe
quelle
Für mich scheinen die Abonnementanrufe nur auf unbestimmte Zeit zu blockieren und kehren nie zurück?
PlexQ
@PlexQ hast du gerade kopiert und eingefügt? Würden Sie Ihren Code angeben?
Cheffe
1

Wenn Ihr Code immer nur auf Unix-Systemen ausgeführt werden muss, können Sie möglicherweise einfach loslegen und tail -fdirekt anrufen .

Als komplexere Alternative können Sie sich die Implementierung von GNU Tail ansehen und diese auf Java übertragen. (Ich bin mir nicht sicher, ob dies Ihren Code nicht bereits zu einer abgeleiteten Arbeit machen würde.)

Daniel Werner
quelle
1
Ich bin nicht mit der Ausführung von Shell-Befehlen durch Java vertraut. tail -fWird die Java-App hängen bleiben, wenn sie niemals beendet wird?
Nein, es wird nicht dazu führen, dass Java hängt. Ich habe eine ähnliche App geschrieben und werde sie bald auf sourceforge veröffentlichen
Makky
0

Just war mit dem gleichen Problem konfrontiert - fand die "einfachste" Implementierung hier: Java Tail .

* Tolles Zeug * - fertig zur Produktion;)

Ich hoffe, dass das Code-Zitat keine Lizenz fallen lässt.

    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)]");
        }
      }
    }
ViPup
quelle
0

Ich habe diese schöne Schwanzimplementierung gefunden.

Verfasser: amelandri

Quelle von: 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)]");
        }
      }

}
Mahesh K.
quelle
-1

Hier ist eine Kurzgeschichte, die Sie als Zeiger verwenden können:

Ich habe TailingInputStream bei der Arbeit aus dem gleichen Grund codiert. Grundsätzlich verwendet es File und aktualisiert seinen Inhalt bei Bedarf und vergleicht ihn mit dem internen Puffer, wenn er sich erheblich geändert hat (4 KB Speicherstempel IIRC), und hat dann das getan, was das tail -f tut. Ein bisschen hackig, ja, aber es funktioniert perfekt und spielt nicht mit Threads oder Ähnlichem - es ist mindestens bis 1.4.2 kompatibel.

Das heißt, es war viel einfacher als ReverseInputStream, das vom Ende der Datei bis zum Start ging und nicht starb, wenn die Datei im laufenden Betrieb aktualisiert wurde ...

Esko
quelle