随机访问文件问题

3
我需要监听一个文件,当有新内容添加时,我会读取新行,并对新行的内容进行处理。文件长度不会减少。(实际上,这是Tomcat日志文件。)
我使用以下代码:
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

import org.apache.log4j.Logger;

import com.zjswkj.analyser.ddao.LogEntryDao;
import com.zjswkj.analyser.model.LogEntry;
import com.zjswkj.analyser.parser.LogParser;

public class ListenTest {
    private RandomAccessFile    raf;
    private long                lastPosition;
    private String              logEntryPattern = "^([\\d.]+) (\\S+) (\\S+) \\[([\\w:/]+\\s[+\\-]\\d{4})\\] \"(.+?)\" (\\d{3}) (\\S+) \"([^\"]+)\" \"([^\"]+)\"";
    private static Logger       log             = Logger.getLogger(ListenTest.class);

    public void startListenLogOfCurrentDay() {

        try {
            if (raf == null)
                raf = new RandomAccessFile(
                        "/tmp/logs/localhost_access_log.2010-12-20.txt",
                        "r");
            String line;
            while (true) {
                raf.seek(lastPosition);
                while ((line = raf.readLine()) != null) {
                    if (!line.matches(logEntryPattern)) {
                        // not a complete line,roll back
                        lastPosition = raf.getFilePointer() - line.getBytes().length;
                        log.debug("roll back:" + line.getBytes().length + " bytes");
                        if (line.equals(""))
                            continue;
                        log.warn("broken line:[" + line + "]");
                        Thread.sleep(2000);
                    } else {
                        // save it
                        LogEntry le = LogParser.parseLog(line);
                        LogEntryDao.saveLogEntry(le);
                        lastPosition = raf.getFilePointer();
                    }
                }
            }
        } catch (FileNotFoundException e) {
            log.error("can not find log file of today");
        } catch (IOException e) {
            log.error("IO Exception:" + e.getMessage());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new ListenTest().startListenLogOfCurrentDay();
    }
}

现在,我的问题是,如果正在写入文件的新行没有完成,就会发生死循环。
例如,如果Tomcat试图向文件写入一个新行:
10.33.2.45 - - [08/Dec/2010:08:44:43 +0800] "GET /poi.txt HTTP/1.1" 200 672 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8"

当只写入行的一部分时(例如:<10.33.2.45 - - [08/Dec/2010:08:44:43 +0800] "GET /poi.txt HTTP/1.1" 200 672>),现在由于它无法匹配我定义的模式,也就是说,tomcat没有完成它的写作,所以我将尝试回滚文件指针,并休眠2秒,然后再次读取。

在休眠期间,可能还没有写入行的最后一部分(实际上我进行测试时是自己写的而不是tomcat),我认为,随机访问文件将读取一个可以匹配模式的新行,但事实并非如此。

有人能检查一下代码吗?

注意:日志文件的格式是“combined”格式的:

10.33.2.45 - - [08/Dec/2010:08:44:43 +0800] "GET /poi.txt HTTP/1.1" 200 672 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8"

你可以尝试提供赏金。但是,请不要这样做。 - user1228
请发布具有多行日志的示例日志文件,并且我不清楚问题的确切情况。 - Aravind Yarram
4个回答

3
我看到(从你的代码中)你的主要目标是过滤日志条目/事件,然后将过滤后的日志写入数据库。你有两个选择。 选项1:最好也是正确的方式。但你应该能够更改随Tomcat一起提供的log4j配置文件 如果是这种情况,那么最好的方法是使用log4j的预定义扩展点。在你的情况下,触发点是Appender

Log4j已经带有DBAppender,您可能需要扩展它以使用正则表达式过滤日志,然后将其余部分委托给DBAppender,因为它经过了充分测试。以下是如何配置自定义appender的示例:

log4j.rootLogger=DEBUG, S

log4j.appender.S=com.gurock.smartinspect.log4j.MyCustomAppender

log4j.appender.S.layout=org.apache.log4j.SimpleLayout

如果要提高性能,建议您还查看使用AsyncAppender和DBAppender。

选项2:如果您无法访问tomcat的log4j配置文件,则可以选择备用选项。

不要编写自己的文件更改监听器,参考这篇SO帖子。选择最符合您需求的那个。然后,您只需要编写用于过滤和将日志持久化到数据库中的代码。您可以使用此链接作为处理RandomAccessFile的示例。


你是不是指Tomcat的日志是由Log4j生成的,这些日志被写入到localhost_access_log.2010-12-20.txt文件中,同时也可以写入到数据库中?在写入数据库之前,我可以进行一些过滤操作吗? - hguser
我猜Tomcat也使用log4j来生成日志。如果是的话,您可以按照我的帖子进行过滤并将其写入数据库。如果日志不是由log4j生成的,则只剩下选项2。 - Aravind Yarram

0

我认为检查新添加的行的方法不太好。我建议您为log4j编写自定义appender。使用自定义appender,您可以通过事件获取每个新添加的行。这里有一个示例here

并且可以在谷歌上搜索自定义appender。


0
在这种情况下,我会首先将读取不断增长的文件和处理行的问题分开。
创建一个名为GrowingFileReader的类,并使其readLine方法实现所需功能。然后,剩余代码就变得更加简单了。
如果匹配失败,为什么要更新lastPosition?难道不应该保留原样吗?

使用 raf.getFilePointer()-line.getBytes().length; 的区别是什么? - hguser

0

RAF 的 readline 方法是阻塞的,并且效率低下(逐字节读取并进行许多系统调用)。此外请注意,在您的代码中,lines.getBytes().length 不能准确地用作 readLine 方法中跳过换行符/回车符。

要在 RAF 上使用 BufferedReader,请查看我的答案 https://dev59.com/yknSa4cB1Zd3GeqPRdIh#19867481


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接