当前位置: 首页 > 科技观察

如何优雅的收集和管理应用的多行日志

时间:2023-03-14 12:33:01 科技观察

多行日志(比如异常信息)为调试应用问题提供了很多非常有价值的信息。在分布式微服务流行的今天,日志基本都是统一采集的。比如常见的ELK、EFK等方案,但是如果这些方案配置不当,它们不会将多行日志作为一个整体来处理,而是将每一行都当作独立的一行日志来处理。说是不能接受。在本文中,我们将介绍一些常见的日志收集工具处理多行日志的策略。1JSON确保多行日志作为单个事件处理。最简单的方式是记录JSON格式的日志。例如,以下是常规Java日常日志的示例:#javaApp.log2019-08-1414:51:22,299ERROR[http-nio-8080-exec-8]classOne:Indexoutofrangejava.lang.StringIndexOutOfBoundsException:Stringindexoutofrange:18atjava.lang.String.charAt(String.java:658)atcom.example.app.loggingApp.classOne.getResult(classOne.java:15)atcom.example.app.loggingApp.AppController.tester(AppController.java:27)atsun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethod)atsun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.invoke):43)atjava.lang.reflect.Method.invoke(Method.java:498)atorg.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)atorg.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)[…]如果上面的日志是直接采集,会被识别为多行日志。如果我们把这些日志记录成JSON格式,那么引入JSON数据就会简单很多,比如使用Log4J2来记录,改变下面的格式:{"@timestamp":"2019-08-14T18:46:04.449+00:00","@version":"1","message":"Indexoutofrange","logger_name":"com.example.app.loggingApp.classOne","thread_name":"http-nio-5000-exec-6","level":"错误","level_value":40000,"stack_trace":"java.lang.StringIndexOutOfBoundsException:Stringindexoutofrange:18\n\tatjava.lang.String.charAt(String.java:658)\n\tatcom.example.app.loggingApp.classOne.getResult(classOne.java:15)\n\tatcom.example.app.loggingApp.AppController.tester(AppController.java:27)\n\tatsun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethod)\n\tatsun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tatsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tatjava.lang.reflect.Method.invoke(Method.java:498)\n\tatorg.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)\n\tatorg.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)\n\tat[...]}这样,整个日志消息包含在一个JSON对象中,其中包含完整的异常堆栈信息。大多数工具都支持直接解析JSON日志数据。这是最简单的方式,对于运维同学来说也是最省心的,但是大部分开发者是反对使用JSON格式来记录日志的~~~2Logstash对于使用Logstash的用户来说,支持多端并不难行日志。Logstash可以使用插件来解析多行日志。插件配置在日志管道的输入部分。比如下面的配置,意思是让Logstash匹配你日志文件中ISO8601格式的时间戳。当匹配到这个时间戳时,它会替换之前所有不以某个时间戳开头的时间戳的内容,并折叠到之前的日志条目中。input{file{path=>"/var/app/current/logs/javaApp.log"mode=>"tail"codec=>multiline{pattern=>"^%{TIMESTAMP_ISO8601}"negate=>truewhat=>"previous"}}}3Fluentd类似于Logstash。Fluentd还允许我们使用插件来处理多行日志。我们可以配置插件来接收一个或多个正则表达式。以下面的Python多行日志为例:2019-08-0118:58:05,898ERROR:ExceptiononmainhandlerTraceback(mostrecentcalllast):File"python-logger.py",line9,inmake_logreturnword[13]IndexError:stringindexoutofrange如果没有多行multilineparser,Fluentd会将每一行都当做一个完整的日志,我们可以在模块中添加一个multiline解析规则,其中必须包含一个format_firstline参数来指定一个新的日志条目以什么开头。另外,可以使用常规的分组抓取来解析日志中的属性,如下配置示例:@typetailpath/path/to/pythonApp.logtagsample.tag@typemultilineformat_firstline/\d{4}-\d{1,2}-\d{1,2}/format1/(?<时间戳>[^]*[^]*)(?<级别>[^\s]+:)(?[\s\S]*)/在解析部分我们使用@typemultiline指定多行解析器,然后在我们的开头使用format_firstline指定规则多行日志。这里我们使用简单的正则匹配日期,然后指定其他部分的匹配模式,并为其分配标签。这里我们将日志拆分为时间戳、级别和消息字段。在解析上述规则后,Fluentd现在将记录每个回溯日志,将其视为单个日志:{"timestamp":"2019-08-0119:22:14,196","level":"ERROR:","message":"Exceptiononmainhandler\nTraceback(mostrecentcalllast):\nFile\"python-logger.py\",line9,inmake_log\nreturnword[13]\nIndexError:stringindexoutofrange"}日志已经格式化为JSON,我们匹配的tag也设置好了作为关键。Fluentd官方文档中也有几个例子:Rails日志比如输入Rails日志如下:StartedGET"/users/123/"for127.0.0.1at2013-06-1412:00:11+0900ProcessingbyUsersController#showasHTMLParameters:{"user_id"=>"123"}Renderedusers/show.html.erbwithinlayouts/application(0.3ms)Completed200OKin4ms(Views:3.2ms|ActiveRecord:0.0ms)我们可以使用如下解析配置进行多行匹配:@typemultilineformat_firstline/^Started/format1/Started(?[^]+)"(?[^"]+)"for(?[^]+)at(?