Filebeat 收集日志的过程中,默认是按行收取的,也就是每一行都会默认是一个单独的事件并添加时间戳。但是在收集一些特殊日志的时候,往往一个事件包含有多行,例如 Java 的堆栈跟踪日志:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 20-09-25 09:09:01.866 ERROR - {<!-- -->"traceId":"","where":{<!-- -->"methodName":"doFilter","className":"com.sohu.smc.channel.news.filter.ResponseTimeFilter","lineNumber":47},"message":"ResponseTimeFilter error","error":"org.apache.catalina.connector.ClientAbortException: java.io.IOException: Connection reset by peer at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:309) at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:272) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745) Caused by: java.io.IOException: Connection reset by peer at sun.nio.ch.FileDispatcherImpl.write0(Native Method) at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:47) at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93) at sun.nio.ch.IOUtil.write(IOUtil.java:65) "} |
所以针对多行日志需要对 filebeat 进行特殊的配置。
官方文档:https://www.elastic.co/guide/en/beats/filebeat/current/multiline-examples.html
首先对部分配置项进行说明:
- multiline.pattern:希望匹配到的结果(正则表达式)
- multiline.negate:值为 true 或 false。使用 false 代表匹配到的行合并到上一行;使用 true 代表不匹配的行合并到上一行
- multiline.match:值为 after 或 before。after 代表合并到上一行的末尾;before 代表合并到下一行的开头
- multiline.max_lines:合并的最大行数,默认 500
- multiline.timeout:一次合并事件的超时时间,默认为 5s,防止合并消耗太多时间导致 filebeat 进程卡死
一、Java 堆栈跟踪日志
1. 示例一
堆栈跟踪日志由多行组成,每一行在初始行之后以空格开头,示例如下:
1 2 3 4 | Exception in thread "main" java.lang.NullPointerException at com.example.myproject.Book.getTitle(Book.java:16) at com.example.myproject.Author.getBookTitles(Author.java:25) at com.example.myproject.Bootstrap.main(Bootstrap.java:14) |
要将这些行整合到 Filebeat 的单个事件中,可以在 Filebeat 中添加如下配置:
1 2 3 | multiline.pattern: '^[[:space:]]' multiline.negate: false multiline.match: after |
这个配置会将以空格开头的行合并到上一行内容。
2. 示例二
接下来使用一个比较复杂的堆栈日志格式:
1 2 3 4 5 6 7 8 9 10 11 | 20-09-25 09:09:01.866 ERROR - {<!-- -->"traceId":"","where":{<!-- -->"methodName":"doFilter","className":"com.sohu.smc.channel.news.filter.ResponseTimeFilter","lineNumber":47},"message":"ResponseTimeFilter error","error":"org.apache.catalina.connector.ClientAbortException: java.io.IOException: Connection reset by peer at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:309) at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:272) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745) Caused by: java.io.IOException: Connection reset by peer at sun.nio.ch.FileDispatcherImpl.write0(Native Method) at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:47) ... 72 more "} |
使用如下配置可以将这些行整合到一个事件中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | multiline.pattern: '^[[:space:]]+(at|\.{3})\b|^Caused by:|^"}' multiline.negate: false multiline.match: after # 或者如下格式 multiline: pattern: '^[[:space:]]+(at|\.{3})\b|^Caused by:' negate: false match: after # 如果日志中有 ... 72 more 这样的结尾,可以用如下匹配方式: multiline.type: pattern multiline.pattern: '^[[:space:]]+(at|\.{3})[[:space:]]+\b|^Caused by:|^"}' multiline.negate: false multiline.match: after |
这个配置会将以空格开头的行和以 Caused by 开头的行合并到上一行内容中。
二、C 风格日志
一些编程语言在一行末尾使用反斜杠()字符,表示该行仍在继续,如下所示:
1 2 | printf ("%10.10ld \t %10.10ld \t %s\ %f", w, x, y, z ); |
使用如下配置可以将这些行整合到单个事件中:
1 2 3 | multiline.pattern: '\\$' multiline.negate: false multiline.match: before |
这个配置会将以 \ 字符结尾的行与后面的行合并。
三、带有时间戳的日志
一般的日志中单个事件都会带有时间戳,但是有时单个事件的日志会被截成两行或者多行,这样就需要匹配时间戳来合并行。示例如下:
例如一个有关于新闻内容的日志,如果新闻的正文中包含换行符,那么在默认情况下,日志中记录的事件会由于换行符的存在被截断
1 2 | [2015-08-24 11:49:14,389][INFO ][env ] [Letha] using [1] data paths, mounts [[/ (/dev/disk1)]], net usable_space [34.5gb], net total_space [118.9gb], types [hfs] |
使用如下配置可以将这些行整合到一个事件中:
1 2 3 | multiline.pattern: '^\[[0-9]{4}-[0-9]{2}-[0-9]{2}' multiline.negate: true multiline.match: after |
此配置使用negate: true和match: after设置来指定任何不符合指定模式的行都属于上一行。
四、应用程序事件
有时应用程序日志包含以自定义标记开始和结束的事件,示例如下:
1 2 3 | [2015-08-24 11:49:14,389] Start new event [2015-08-24 11:49:14,395] Content of processing something [2015-08-24 11:49:14,399] End event |
使用如下配置可以将这些行整合到一个事件中:
1 2 3 4 | multiline.pattern: 'Start new event' multiline.negate: true multiline.match: after multiline.flush_pattern: 'End event' |
此配置把指定字符串开头,指定字符串结尾的多行合并为一个事件。