【问题标题】:Filtering logs with regex in java在java中使用正则表达式过滤日志
【发布时间】:2017-11-24 01:39:24
【问题描述】:

描述很长,请多多包涵:
我的日志文件大小从 300 mb 到 1.5 Gb 不等,需要根据搜索键对其进行过滤。

日志的格式是这样的:

24 May 2017 17:00:06,827 [INFO] 123456 (Blah : Blah1) Service-name:: Single line content
24 May 2017 17:00:06,828 [INFO] 567890 (Blah : Blah1) Service-name:: Content( May span multiple lines)
24 May 2017 17:00:06,829 [INFO] 123456 (Blah : Blah2) Service-name: Multiple line content. Printing Object[ ID1=fac-adasd ID2=123231
ID3=123108 Status=Unknown
Code=530007 Dest=CA
]
24 May 2017 17:00:06,830 [INFO] 123456 (Blah : Blah1) Service-name:: Single line content
4 May 2017 17:00:06,831 [INFO] 567890 (Blah : Blah2) Service-name:: Content( May span multiple lines)

给定搜索键 123456,我需要获取以下内容:

24 May 2017 17:00:06,827 [INFO] 123456 (Blah : Blah1) Service-name:: Single line content
24 May 2017 17:00:06,829 [INFO] 123456 (Blah : Blah2) Service-name: Multiple line content. Printing Object[ ID1=fac-adasd ID2=123231
ID3=123108 Status=Unknown
Code=530007 Dest=CA
]
24 May 2017 17:00:06,830 [INFO] 123456 (Blah : Blah1) Service-name:: Single line content

以下 awk 脚本完成了我的工作(非常缓慢):

gawk '/([0-9]{1}|[0-9]{2})\s\w+\s[0-9]{4}/{n=0}/123456/{n=1} n'

搜索 1 GB 大小的日志文件大约需要 8 分钟。我需要为许多此类文件执行此操作。最重要的是,我有多个这样的搜索键,这使得整个任务变得不可能。

我最初的解决方案是使用多线程。我使用了一个fixedThreadPoolExecutor,为每个需要过滤的文件提交了一个任务。在任务描述中,我使用 java 的 Runtime() 生成了新进程,该进程将使用 bash 执行 gawk 脚本并将输出写入文件,然后合并所有文件。

虽然这似乎是一种糟糕的方法,但由于过滤依赖于 I/O 而不是 CPU,与按顺序在每个文件上执行脚本相比,它确实给了我一个加速。

但这仍然不够,因为整个过程需要 2 小时,对于单个搜索键,以及 27gb 的日志文件。平均而言,我有 4 个这样的搜索键,需要获取它们的所有结果并将它们放在一起。

我的方法效率不高,因为:

A) 当给定多个搜索键时,它会多次访问每个日志文件,并导致更多的 I/O 开销。
B) 它会产生在每个线程内创建进程的开销。

解决所有这些问题的一个简单方法是使用一些正则表达式库,从 awk 转移到 java 中完成所有事情。这里的问题是,可以为我提供所需输出的正则表达式库是什么?
使用 awk 我有 /filter/{action} 属性,它可以让我指定要捕获的多行范围(如上所示)。我怎样才能在 java 中做同样的事情?

我愿意接受各种建议。例如,一个极端的选择是将日志文件存储在像 S3 这样的共享文件系统中,并使用多台计算机处理输出。

我是 stackoverflow 的新手,我什至不知道我是否可以在这里发布。但过去一周我一直在做这件事,我需要有专业知识的人来指导我做这件事。提前致谢。

【问题讨论】:

  • logcheck 或 logwatch 或 graylog 有什么问题?您不需要自己实施此解决方案。
  • grep 对您没有帮助。如果您需要分布式系统即服务,那么您可以使用 lucene 等内容搜索技术。
  • 读取整个 27gb 的日志文件需要多少时间?这将为您提供所需的最少时间,并据此决定是否进一步优化。 +1 清楚地写出一个好问题,顺便说一句
  • 尝试添加锚点:gawk '/^([0-9]{1}|[0-9]{2})\s\w+\s[0-9]{4}/{n=0}/123456/{n=1} n'
  • @Michael 我目前正在研究logstash。我也需要看看你的建议。我不太确定是否能够解析日志文件。谷歌告诉我,使用 logwatch,我们可以自定义解析。如果成功了,我会告诉你的。

标签: java regex algorithm logging awk


【解决方案1】:

您有几个选择。

最好的 imo 是使用倒置字典。这意味着对于至少一个日志中存在的每个关键字 x,您存储对包含它的所有日志的引用。但是,由于您已经在此任务上花费了一周的时间,我建议您使用已经存在的东西并且确实做到了:Elasticsearch。您实际上可以使用完整的 ELK 堆栈(elasticsearch、logstash、kibana - 主要为日志设计)来解析日志,因为您只需在配置文件中放置一个正则表达式即可。您只需要索引文件一次,搜索速度最快只需几毫秒。

如果你真的想浪费精力而不去寻求最好的解决方案,你可以在 hadoop 上使用 map-reduce 来过滤日志。但这不是 map-reduce 最佳的任务,它更像是一个 hack。

【讨论】:

    【解决方案2】:

    如果您希望加快执行时间,切换到 Java 可能不是最佳选择,但如果您正在考虑,我编写了一个 Java 类可能会有所帮助。

    您可以使用它同时搜索文件中的一个或多个键。由于您正在阅读日志文件,因此可以安全地假设所有行都遵循正确的格式而没有错误。因此,它不是对整行进行正则表达式格式检查,而是简单地跳到键应该在的位置(第一个 ] 之后的数字),并将其与所需的值进行比较(假设它始终是一个数字)。

    这样使用:

    Set<Integer> keys = new HashSet();
    keys.add(123456);
    keys.add(314159);
    /* synchronously (omitting 3rd argument prints to stdout) */
    new KeySearch('path/to/file.log', keys).run();
    
    /* asynchronously!!! (to use PrintStream, create the output file first) */
    PrintStream ps1 = new PrintStream('lines-found1.log');
    PrintStream ps2 = new PrintStream('lines-found2.log');
    new Thread(new KeySearch('path/to/1.log', keys, ps1::println)).start();
    new Thread(new KeySearch('path/to/2.log', keys, ps2::println)).start();
    

    第三个参数是一个自定义接口KeySearch.Callback,它接收找到的行。我以方法引用为例,但它可以是你想要的任何东西。这是类(至少需要 Java 8)。

    import java.io.*;
    import java.util.*;
    
    public class KeySearch implements Runnable {
        public interface Callback { 
            void lineFound(String line); 
        }
    
        private final Set<Integer> keys;
        private final Callback callback;
        private final String name;
    
        public KeySearch(String fileName, Collection<Integer> keys) {
            this(fileName, keys, System.out::println);
        }
    
        public KeySearch(String fileName, Collection<Integer> keys, Callback call) {
            this.keys = new HashSet<>(keys);
            this.name = fileName;
            this.callback = call;
        }
    
        @Override
        public void run() {
            String s;
            try(FileReader fr = new FileReader(name); 
                    BufferedReader br = new BufferedReader(fr)) {
                while ((s = readLine(br)) != null)
                    if (matches(s)) callback.lineFound(s);
            } catch (IOException e) {
                System.err.println("Error reading " + name);
                throw new RuntimeException(e);
            }
        }
    
        private boolean matches(String line) {
            return keys.contains(getKeyOf(line));
        }
    
        private String readLine(BufferedReader reader) throws IOException {
            StringBuilder line = new StringBuilder();
            String next;
    
            do {
                next = reader.readLine();
                if (next == null) return null;
                line.append(next).append(System.lineSeparator());
            } while (next.lastIndexOf('[') > next.lastIndexOf(']'));
    
            return line.toString();
        }
    
        private boolean isDigit(CharSequence s, int i) {
            char c = s.charAt(i);
            return c >= '0' && c <= '9';
        }
    
        private int getKeyOf(String line) {
            // find the first ] (e.g. at the end of [INFO])
            // and read the first number after it
            int start = line.indexOf(']');
            while (!isDigit(line, start)) start++;
    
            int end = start;
            while (isDigit(line, end)) end++;
    
            return Integer.parseInt(line.substring(start, end));
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-03
      • 2018-06-23
      • 2018-08-11
      • 2016-07-06
      • 1970-01-01
      相关资源
      最近更新 更多