Unix / Linux“ tail -f”的Java IO实现


72

我想知道使用什么技术和/或库来实现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。它应该能够读取文件打开之前存在的部分以及添加的行。

Answers:


39

能够继续读取文件并等待文件有更多更新的能力,自己编写代码并不难。这是一些伪代码:

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一个setter,以便您的主类/应用程序的其他部分可以安全地关闭线程而不会造成其他麻烦,只需调用stopReading()或类似方法即可。


5
注意:如果要拖尾,请使用br.skip(file.length()); 我尝试过RandomAccessReader(),但是速度慢。
亚伦·迪古拉

12
这没有考虑文件截断;如果日志文件被覆盖,此代码将失败...这是tail的基本功能!

这并不涉及日志文件的翻转。
sheki 2010年

这适用于我的用例,我的头

60

看一下Tailer类的Apache Commons实现。它似乎也可以处理日志旋转。


非常感谢!顺便说一句:如果logrotation正确完成('cp logfile oldfile;> logfile'),那么matt的解决方案应该仍然有效,因为文件引用不会丢失!
卡苏尔

2
请注意:如果您只想从文件末尾开始,那么即使在2.4版本(撰写本文时为最新版本)中,Taail也存在一些问题。请参阅:issues.apache.org/jira/browse/…–
乔·

13

检查一下 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();
}

JLogTailer似乎没有库。
sheki 2010年

@sheki只是用罐子?@aldrinleal我不想创建一个新答案……只是将代码粘贴到此处。我更喜欢马特的简单(+ faster?)版本:)
Karussell

在代码审查时,您尚未指定用于读取该行的编码,但是以某种方式假设您已读取字符串。
Trejkaz

9

前一段时间,我在Scala中构建了一个简短的“ tail -f”实现:tailf。它也负责文件的轮换,您可以定义自己的逻辑,当文件到达EOF或发现文件已重命名时该怎么做。

您可以看一下并将其移植到Java,因为实际上那里没有什么复杂的东西。一些注意事项:主文件是Tail.scala,基本上它定义FollowingInputStream了EOF /重命名和follow方法,将其包装FollowingInputStream到中的无限制枚举中SequenceInputStream。因此,一旦FollowingInputStream结束,就会SequenceInputStreamEnumeration和另一个请求FollowingInputStream创建下一个元素。


5

我最近偶然发现了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);
}

对我来说,订阅呼叫似乎无限期地阻塞,永不返回?
PlexQ '16

@PlexQ您是否只是复制粘贴?您可以输入代码吗?
Cheffe

1

如果您的代码只必须在Unix系统上运行,那么您只需脱壳并tail -f直接调用就可以摆脱困境。

作为更复杂的替代方案,您可以看看GNU尾部的实现并将其移植到Java。(不过,我不确定这是否还不能使您的代码成为派生作品。)


1
我不熟悉Java如何处理执行Shell命令的方法。鉴于tail -f永不退出,是否会导致Java应用程序挂起?

否,它不会导致Java挂起..我已经编写了类似的应用程序,并将很快在sourceforge上发布
Makky 2012年

0

刚遇到同样的问题-在这里找到“最简单”的实现:Java尾巴

*好东西* -准备生产;)

我希望引用代码不会丢掉一些许可证。

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

0

我发现这个不错的尾巴实现。

作者: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)]");
        }
      }

}

-1

这是一个短故事,您可以将其用作指针:

出于相同的原因,我在工作中对TailingInputStream进行了编码。它基本上使用File并按需刷新其内容,并检查内部缓冲区是否发生了重大变化(4kB内存标记IIRC),然后执行尾-f的操作。有点hacky,是的,但是它可以完美工作,并且不会与Threads或类似的东西混为一谈-至少一直兼容到1.4.2。

就是说,这比ReverseInputStream容易得多,后者从文件的末尾开始,并且如果文件是动态更新的,它也不会死...

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.