我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

 多行日志(例如异常信息)为调试应用问题提供了许多非常有价值的信息,在分布式微服务流行的今天基本上都会统一将日志进行收集,比如常见的 ELK、EFK 等方案,但是这些方案如果没有适当的配置,它们是不会将多行日志看成一个整体的,而是每一行都看成独立的一行日志进行处理,这对我们来说是难以接受的。

在本文中,我们将介绍一些常用日志收集工具处理多行日志的策略。

1JSON

保证多行日志作为单个事件进行处理最简单的方法就是以 JSON 格式记录日志,比如下面是常规 Java 日常日志的示例: 

  1. # javaApp.log  
  2. 2019-08-14 14:51:22,299 ERROR [http-nio-8080-exec-8] classOne: Index out of range  
  3. java.lang.StringIndexOutOfBoundsException: String index out of range: 18  
  4.  at java.lang.String.charAt(String.java:658)  
  5.  at com.example.app.loggingApp.classOne.getResult(classOne.java:15)  
  6.  at com.example.app.loggingApp.AppController.tester(AppController.java:27)  
  7.  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  
  8.  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)  
  9.  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)  
  10.  at java.lang.reflect.Method.invoke(Method.java:498)  
  11.  at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)  
  12.  at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)  
  13. [...] 

如果直接收集上面的日志会识别为多行日志,如果我们用 JSON 格式来记录这些日志,然后介绍 JSON 的数据就简单多了,比如使用 Log4J2 来记录,变成下面的格式: 

  1. {"@timestamp":"2019-08-14T18:46:04.449+00:00","@version":"1","message":"Index out of range","logger_name":"com.example.app.loggingApp.classOne","thread_name":"http-nio-5000-exec-6","level":"ERROR","level_value":40000,"stack_trace":"java.lang.StringIndexOutOfBoundsException: String index out of range: 18\n\tat java.lang.String.charAt(String.java:658)\n\tat com.example.app.loggingApp.classOne.getResult(classOne.java:15)\n\tat com.example.app.loggingApp.AppController.tester(AppController.java:27)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)\n\tat 
  2. [...]  

 这样整个日志消息都包含在单个 JSON 对象汇总了,其中就包含完整的异常堆栈信息,绝大多数工具都支持直接解析 JSON 日志数据,这是最简单的一种方法,对于运维同学来说也是最省心的,但是大部分开发人员是抵触用 JSON 格式来记录日志的~~~

2Logstash

对于使用 Logstash 的用户来说,要支持多行日志也不困难,Logstash 可以使用插件解析多行日志,该插件在日志管道的 input 部分进行配置。例如,下面的配置表示让 Logstash 匹配你的日志文件中 ISO8601 格式的时间戳,当匹配到这个时间戳的时候,它就会将之前所有不以时间戳开头的内容折叠到之前的日志条目中去。 

  1. input {  
  2.   file {  
  3.     path => "/var/app/current/logs/javaApp.log"  
  4.     mode => "tail"  
  5.     codec => multiline {  
  6.       pattern => "^%{TIMESTAMP_ISO8601} "  
  7.       negate => true  
  8.       what => "previous"  
  9.     }  
  10.   }  

3Fluentd

和 Logstash 类似,Fluentd 也允许我们使用一个插件来处理多行日志,我们可以配置插件接收一个或多个正则表达式,以下面的 Python 多行日志为例: 

  1. 2019-08-01 18:58:05,898 ERROR:Exception on main handler  
  2. Traceback (most recent call last):  
  3.   File "python-logger.py", line 9, in make_log  
  4.     return word[13]  
  5. IndexError: string index out of range 

如果没有 multiline 多行解析器,Fluentd 会把每行当成一条完整的日志,我们可以在 模块中添加一个 multiline 的解析规则,必须包含一个 format_firstline 的参数来指定一个新的日志条目是以什么开头的,此外还可以使用正则分组和捕获来解析日志中的属性,如下配置所示: 

  1. <source>  
  2.   @type tail  
  3.   path /path/to/pythonApp.log  
  4.   tag sample.tag  
  5.   <parse>  
  6.     @type multiline  
  7.     format_firstline /\d{4}-\d{1,2}-\d{1,2}/  
  8.     format1 /(?<timestamp>[^ ]* [^ ]*) (?<level>[^\s]+:)(?<message>[\s\S]*)/  
  9.   parse>  
  10. source> 

在解析部分我们使用 @type multiline 指定了多行解析器,然后使用 format_firstline 来指定我们多行日志开头的规则,这里我们就用一个简单的正则匹配日期,然后指定了其他部分的匹配模式,并为它们分配了标签,这里我们将日志拆分成了 timestamp、level、message 这几个字段。

经过上面的规则解析过后,现在 Fluentd 会将每个 traceback 日志看成一条单一的日志了: 

  1.  
  2.   "timestamp": "2019-08-01 19:22:14,196",  
  3.   "level": "ERROR:",  
  4.   "message": "Exception on main handler\nTraceback (most recent call last):\n  File \"python-logger.py\", line 9, in make_log\n    return word[13]\nIndexError: string index out of range" 
  5.  

该日志已被格式化为 JSON,我们匹配的标签也被设置为了 Key。

在 Fluentd 官方文档中也有几个示例说明:

  •  Rails 日志

比如输入的 Rails 日志如下所示: 

  1. Started GET "/users/123/" for 127.0.0.1 at 2013-06-14 12:00:11 +0900  
  2. Processing by UsersController#show as HTML  
  3.   Parameters: {"user_id"=>"123"}  
  4.   Rendered users/show.html.erb within layouts/application (0.3ms)  
  5. Completed 200 OK in 4ms (Views: 3.2ms | ActiveRecord: 0.0ms) 

我们可以使用下面的解析配置进行多行匹配: 

  1. <parse>  
  2.   @type multiline  
  3.   format_firstline /^Started/  
  4.   format1 /Started (?<method>[^ ]+) "(?<path>[^"]+)" for (?<host>[^ ]+) at (?<time>[^ ]+ [^ ]+ [^ ]+)\n/  
  5.   format2 /Processing by (?<controller>[^\u0023]+)\u0023(?<controller_method>[^ ]+) as (?<format>[^ ]+?)\n/  
  6.   format3 /(  Parameters: (?<parameters>[^ ]+)\n)?/  
  7.   format4 /  Rendered (?<template>[^ ]+) within (?<layout>.+) \([\d\.]+ms\)\n/  
  8.   format5 /Completed (?<code>[^ ]+) [^ ]+ in (?<runtime>[\d\.]+)ms \(Views: (?<view_runtime>[\d\.]+)ms \| ActiveRecord: (?<ar_runtime>[\d\.]+)ms\)/  
  9. parse> 

解析过后得到的日志如下所示: 

  1.  
  2.   "method"           :"GET",  
  3.   "path"             :"/users/123/",  
  4.   "host"             :"127.0.0.1",  
  5.   "controller"       :"UsersController",  
  6.   "controller_method":"show",  
  7.   "format"           :"HTML",  
  8.   "parameters"       :"{ \"user_id\":\"123\"}",  
  9.   ...  
  •     Java 堆栈日志

比如现在我们要解析的日志如下所示: 

  1. 2013-3-03 14:27:33 [main] INFO  Main - Start  
  2. 2013-3-03 14:27:33 [main] ERROR Main - Exception  
  3. javax.management.RuntimeErrorException: null  
  4.     at Main.main(Main.java:16) ~[bin/:na] 
  5. 2013-3-03 14:27:33 [main] INFO  Main - End 

则我们可以使用下面的解析规则进行多行匹配: 

  1. <parse>  
  2.   @type multiline  
  3.   format_firstline /\d{4}-\d{1,2}-\d{1,2}/  
  4.   format1 /^(?<time>\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}) \[(?<thread>.*)\] (?<level>[^\s]+)(?<message>.*)/  
  5. parse> 

解析过后的日志为: 

  1.  
  2.   "thread" :"main",  
  3.   "level"  :"INFO",  
  4.   "message":"  Main - Start"  
  5.  
  6.  
  7.   "thread" :"main",  
  8.   "level"  :"ERROR",  
  9.   "message":" Main - Exception\njavax.management.RuntimeErrorException: null\n    at Main.main(Main.java:16) ~[bin/:na]"  
  10.  
  11.  
  12.   "thread" :"main",  
  13.   "level"  :"INFO",  
  14.   "message":"  Main - End"  

上面的多行解析配置中除了 format_firstline 指定多行日志的开始行匹配之外,还用到了 format1、format2…formatN 这样的配置,其中 N 的范围是 1...20,是多行日志的 Regexp 格式列表,为了便于配对,可以将 Regexp 模式分割成多个 regexpN 参数,将这些匹配模式连接起来构造出多行模式的正则匹配。

4Fluent Bit

Fluent Bit 的 tail input 插件也提供了处理多行日志的配置选项,比如现在我们还是来处理之前的 Python 多行日志: 

  1. 2019-08-01 18:58:05,898 ERROR:Exception on main handler  
  2. Traceback (most recent call last):  
  3.   File "python-logger.py", line 9, in make_log  
  4.     return word[13]  
  5. IndexError: string index out of range 

如果不用多行解析器 Fluent Bit 同样会将每一行当成一条日志进行处理,我们可以配置使用 Fluent Bit 内置的 regex 解析器插件来结构化多行日志: 

  1. [PARSER]  
  2.      Name         log_date  
  3.      Format       regex  
  4.      Regex        /\d{4}-\d{1,2}-\d{1,2}/  
  5.  [PARSER]  
  6.      Name        log_attributes  
  7.      Format      regex  
  8.      Regex       /(?<timestamp>[^ ]* [^ ]*) (?<level>[^\s]+:)(?<message>[\s\S]*)/  
  9.   [INPUT]  
  10.      Name              tail  
  11.      tag               sample.tag  
  12.      path              /path/to/pythonApp.log  
  13.      Multiline         On  
  14.      Parser_Firstline  log_date  
  15.      Parser_1          log_attributes 

和 Fluentd 类似,Parser_Firstline 参数指定了与多行日志开头相匹配的解析器的名称,当然我们也可以包含额外的解析器来进一步结构化你的日志。这里我们配置了首先使用 Parser_Firstline 参数来匹配 ISO8601 日期开头的日志行,然后使用 Parser_1 参数来指定匹配模式,以匹配日志消息的其余部分,并为它们分配了 timestamp、level、message 标签。

最终转换过后我们的日志变成了如下所示的格式: 

  1.  
  2.   "timestamp": "2019-08-01 19:22:14,196",  
  3.   "level": "ERROR:",  
  4.   "message": "Exception on main handler\nTraceback (most recent call last):\n  File \"python-logger.py\", line 9, in make_log\n    return word[13]\nIndexError: string index out of range" 
  5.  

 

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

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

在本文中,我们将介绍一些常用日志收集工具处理多行日志的策略。

Docker容器中的Nginx日志管理与分析(如何管理和分析运行在Docker中的Nginx日志?)

Docker容器中的Nginx日志管理和分析至关重要,分为收集、查看和分析三个步骤。常用日志格式包括CLF、ELF和JSON。最佳实践包括日志轮换、压缩和监控。使用Docker日志驱动程序、日志分析工具和命令行命令来管理和分析日志。DockerCompose和自定义日志级别提供了进一步的灵活性。
Docker容器中的Nginx日志管理与分析(如何管理和分析运行在Docker中的Nginx日志?)
2024-04-02

如何在SQL Server中管理和优化大量的事务日志

以下是一些建议来管理和优化大量的事务日志:确保事务日志文件大小适当:确保事务日志文件的大小足够大,以容纳大量的事务数据。可以通过监控事务日志的使用情况并相应调整日志文件大小来实现。定期备份事务日志:定期备份事务日志可以帮助释放事务日志中的空
如何在SQL Server中管理和优化大量的事务日志
2024-06-04

C#开发中如何处理异常日志和错误信息的收集与分析

C#开发中如何处理异常日志和错误信息的收集与分析引言:在软件开发过程中,我们经常会遇到各种异常和错误。为了及时发现并解决这些问题,我们需要在代码中实现异常日志和错误信息的收集与分析。本文将介绍如何在C#开发中处理异常日志和错误信息,并提供一
2023-10-22

如何利用错误日志进行PHP会话管理的调试?

以下是一些利用错误日志进行PHP会话管理调试的方法:配置PHP错误日志- 开启错误记录:在PHP的配置文件php.ini中,将`log_errors

如何利用Go语言进行内存优化和高效的垃圾回收管理

要利用Go语言进行内存优化和高效的垃圾回收管理,可以采取以下几个策略:1. 使用指针:Go语言通过指针进行内存管理,使用指针可以减少内存拷贝和内存分配的开销。尽量使用指针来传递和操作数据。2. 使用值类型:Go语言中的值类型在函数调用时会进
2023-10-08

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录