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
, Он должен быть в состоянии прочитать части файла, которые существуют до открытия файла, а также добавленные строки.
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()
или что-то подобное.
Взгляните на реализацию Apache Commons класса Tailer. Кажется, он также обрабатывает ротацию бревен.
Проверьте 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();
}
Некоторое время назад я построил короткую реализацию "tail -f" в Scala: tailf. Он также заботится о ротации файлов, и вы можете определить свою собственную логику, что делать, когда он достигает EOF или обнаруживает, что файл был переименован.
Вы можете взглянуть и перенести его на Java, так как на самом деле там нет ничего сложного. Несколько замечаний: основным файлом является Tail.scala, и в основном он определяет FollowingInputStream
который заботится о EOF/ переименовании и follow
метод, который оборачивает FollowingInputStream
в неограниченное перечисление в SequenceInputStream
, Итак, как только FollowingInputStream
заканчивается, SequenceInputStream
запрашивает следующий элемент из Enumeration
и другой FollowingInputStream
создается.
Я недавно наткнулся на rxjava-файл, это расширение 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);
}
Если ваш код только когда-либо будет работать на Unix-системах, вам, возможно, удастся сойти с рук, просто выложив и вызвав tail -f
непосредственно.
В качестве более сложной альтернативы вы можете взглянуть на реализацию хвоста GNU и перенести его на Java. (Однако я не уверен, что это не сделает ваш код производным.)
Просто столкнулся с той же проблемой - нашел здесь "простейшую" реализацию: 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)]");
}
}
}
Я нашел эту хорошую реализацию хвоста.
Автор: Амеландри
Соус из: 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)]");
}
}
}
Вот небольшой рассказ, который вы можете использовать в качестве указателя:
Я кодировал TailingInputStream на работе по той же причине. Он в основном использует File и обновляет его содержимое по требованию и проверяет его по внутреннему буферу, если он значительно изменился (4 КБ, метка памяти IIRC), а затем сделал то, что делает tail -f. Немного хакерский, да, но он работает отлично и не связывается с Threads или чем-то подобным - он по крайней мере совместим с версией 1.4.2.
Тем не менее, это было намного проще, чем ReverseInputStream, который шел от конца файла к началу и не умирал, если файл обновлялся на лету...