Реализация Java IO для unix/linux tail -f

Мне интересно, какие методы и/или библиотеки использовать для реализации функциональности команды linux «tail -f». По сути, я ищу дополнение/замену для java.io.FileReader. Код клиента может выглядеть примерно так:

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
}

Недостающая часть — разумная реализация TailFileReader. Он должен иметь возможность читать части файла, которые существуют до открытия файла, а также добавленные строки.


person Gary    schedule 17.02.2009    source источник


Ответы (9)


Возможность продолжать читать файл и ждать, пока в файле появится еще несколько обновлений, для вас не должно быть так сложно реализовать в коде самостоятельно. Вот некоторый псевдокод:

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

Я предполагаю, что вы хотели бы поместить этот тип функциональности в свой собственный поток, чтобы вы могли его спать и не затрагивать какие-либо другие области вашего приложения. Вы хотели бы выставить keepReading в установщике, чтобы ваш основной класс/другие части приложения могли безопасно закрыть поток без каких-либо других головных болей, просто вызвав stopReading() или что-то подобное.

person matt b    schedule 17.02.2009
comment
Примечание. Если вы хотите использовать хвост, используйте br.skip (file.length ()); Я экспериментировал с RandomAccessReader(), но это очень медленно. - person Aaron Digulla; 06.03.2009
comment
Это не учитывает усечение файлов; этот код дает сбой, если файл журнала перезаписывается... что является важной особенностью tail! - person ; 27.10.2010
comment
Это не заботится о переносе файлов журнала. - person sheki; 01.12.2010
comment
Это работает для моего варианта использования, я сломал голову более 2 часов, чтобы найти чистое решение. - person Sumit Kumar Saha; 22.01.2020

Взгляните на реализацию Apache Commons Tailer класс. Кажется, он также обрабатывает ротацию журнала.

person Chetan S    schedule 08.02.2011
comment
Большое спасибо! Кстати: если ротация журнала выполнена правильно ('cp logfile oldfile; › logfile'), то решение Мэтта должно по-прежнему работать, потому что ссылка на файл не потеряна! - person Karussell; 30.03.2011
comment
Имейте в виду: если вы хотите использовать хвост только с конца файла, у Tailer есть некоторые проблемы даже в версии 2.4 (последняя на момент написания этой статьи). См.: Issues.apache.org/jira/browse/ - person Joe Casadonte; 05.10.2014

Проверьте JLogTailer, который выполняет эту логику.

Главное в коде:

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();
}
person aldrinleal    schedule 17.02.2009
comment
JLogTailer, похоже, не имеет библиотеки. - person sheki; 01.12.2010
comment
@sheki просто использовать банку? @aldrinleal Я не хотел создавать новый ответ ... просто вставил сюда код. Мне больше нравится более простая (+быстрая?) версия Мэтта :) - person Karussell; 30.03.2011
comment
В качестве точки обзора кода вы не указали кодировку для чтения этой строки, но вы каким-то образом предполагаете, что прочитали строку. - person Trejkaz; 26.04.2020

Некоторое время назад я создал короткую реализацию «tail -f» в Scala: tailf. Он также заботится о ротации файлов, и вы можете определить свою собственную логику, что делать, когда он достигает EOF или обнаруживает, что файл был переименован.

Можете посмотреть и портировать на Java, так как там на самом деле ничего сложного нет. Несколько замечаний: основной файл — Tail.scala и в основном определяет FollowingInputStream, который берет на себя EOF/переименование и follow, который оборачивает FollowingInputStream в неограниченное перечисление в SequenceInputStream. Таким образом, как только FollowingInputStream заканчивается, SequenceInputStream запрашивает следующий элемент из Enumeration, и создается еще один FollowingInputStream.

person Alexander Azarov    schedule 04.12.2010

Недавно я наткнулся на rxjava-file, это расширение RxJava. В отличие от других решений, здесь используется Java NIO.

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);
}
person cheffe    schedule 03.07.2015
comment
Для меня вызовы подписки просто блокируются на неопределенный срок, никогда не возвращаясь? - person PlexQ; 31.03.2016
comment
@PlexQ ты просто скопировал и вставил? Не могли бы вы указать свой код? - person cheffe; 31.03.2016

Если ваш код когда-либо должен будет работать только в системах Unix, вы можете уйти, просто выбрав шелл и вызвав tail -f напрямую.

В качестве более сложной альтернативы вы можете взглянуть на реализацию хвоста GNU и перенести его на Java. (Однако я не уверен, что это уже не сделало бы ваш код производной работой.)

person Daniel Werner    schedule 02.12.2010
comment
Я не знаком с тем, как Java обрабатывает выполнение команд оболочки; учитывая, что tail -f никогда не завершается, приведет ли это к зависанию приложения Java? - person ; 15.08.2011
comment
Нет, это не приведет к зависанию Java. Я написал похожее приложение и скоро выпущу его на sourceforge. - person Makky; 10.04.2012

Только что столкнулся с той же проблемой - нашел "самую простую" реализацию здесь: Java Tail.

*Отличный материал * — готов к производству ;)

Надеюсь, кодовое цитирование не приведет к потере какой-либо лицензии.

    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)]");
        }
      }
    }
person ViPup    schedule 14.09.2012

Я нашел эту красивую реализацию хвоста.

автор: amelandri

Источник из: 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)]");
        }
      }

}
person Mahesh K    schedule 25.10.2016

Вот небольшой рассказ, который вы можете использовать в качестве указателя:

Я кодировал TailingInputStream на работе по той же причине. В основном он использует файл и обновляет его содержимое по запросу и проверяет внутренний буфер, если он значительно изменился (метка памяти 4 КБ IIRC), а затем делает то, что делает хвост -f. Немного хакерский, да, но он работает отлично и не связывается с потоками или чем-то подобным — он совместим, по крайней мере, до 1.4.2.

Тем не менее, это было намного проще сделать, чем ReverseInputStream, который шел от конца файла к началу и не умирал, если файл обновлялся на лету...

person Esko    schedule 17.02.2009