SkyWalking Agent端日志插件的编写历程与使用说明
概述
前一段时间顺利完成了SkyWalking Agent端logger-plugin插件的开发,在此做个总结。一方面给插件的使用方法写一中文说明,另一方面分享一下该插件开发过程中的一些考量以及收获。
logger-plugin插件,主要作用实现将将程序在调用过程中产生的日志比如错误日志信息,存入到span log中。然后可以通过web端直接查询,便于开发者排错与分析。同时为了提高使用的灵活性,我们还提供了配置文件,通过配置文件可以对需要存入到span log的日志来源(log4j2、logbak、log4j)、包名、日志级别、内容进行控制。但同时需要提醒的一点是,由于该插件直接作用于agent端,可能会造成一定程度的性能损失,请谨慎使用。
相关PR地址如下:Support collecting logs of log4j, log4j2, and logback in the tracing context,以供参考。
使用方式
基于性能的考量,该插件在设计之初定位为可选插件,因而默认情况下该插件的功能并不会启动。如需使用,需要在8.4.0发布之后,将插件从apache-skywalking-apm-bin/agent/optional-plugins/
目录下复制到apache-skywalking-apm-bin/agent/plugins/
下,方能生效。
配置文件
首先需要说明的一点是,发布时默认是没有配置文件的,插件会按照如下默认配置内容运行:
log4j.packages=*
log4j.level=error
log4j2.packages=*
log4j2.level=error
logback.packages=*
logback.level=error
上述配置文件含义如下:
- 该插件默认会对所有支持的日志框架生效,包括log4j、log4j2、logbback。
- 插件之后将高于
error
级别的日志信息存入到span log中,而对trace
,debug
,info
,warn
级别的日志信息不生效。 - 默认情况下,会收集所有包级别的日志信息。
.如果需要自定义插件的配置信息,需要在apache-skywalking-apm-bin/agent/config/logger-plugin/
目录下创建logconfig.properties
文件进行配置。
配置文件可配置内容及其含义如下:
packages
含义:指定需要转存的日志信息的包名,默认匹配所有包。
取值:
- 包名:例如org.apache。若有多个包名中间需用逗号隔开
- *:匹配所有包级别的日志信息。
Level
含义:所需转存的日志信息的级别,默认情况是error
级别的日志信息
日志级别从小到大排列如下:
trace
< debug
< info
<warn
< error
< fatal
同时我们在自定义配置信息的时候需要注意,由于logback不支持fatal
级别的日志信息,因而如果错误的进行如下配置:
logback.level=error
则会造成针对logback的配置失效,也即不会收集任何关于logback日志插件产生的日志信息,并会产生错误日志,以供用户排查。
设计思路
其实这个插件刚开始插件之初,由于个人工程经验不足,当时考虑想要实现功能很多,比如需要支持正则表达式用以过滤,支持日志的格式化配置等等。但在和其他社区成员的讨论过程中发现,需要功能其实和该插件的定位不同,比如过滤日志信息的功能,日志系统中已经支持了。如果该插件再增加该功能一方面会增加插件使用的复杂程度,另一方面,也会造成更大的性能损耗。而针对另一功能日志的格式化,这与该插件的定位也是不符合的,apm系统本身就是为了实现追踪和快速定位,对收集到的日志信息希望尽量简练,有效,许多格式化的信息,根本无需收集。因而该功能最终也被废止。
同时针对日志转存到span的格式,和社区成员也进行仔细的沟通,为了适应未来SkyWalking的日志查询功能,因此在转存日志信息时,存储到OAP端的span log日志是结构化的类似于下面的形式:
"logs": [
{
"time": 1605225365711,
"data": [
{
"key": "event",
"value": "warn"
},
{
"key": "log.kind",
"value": "org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.MultiScopesAnalysisListener"
},
{
"key": "message",
"value": "span {} has illegal status code {}"
},
{
"key": "param.1",
"value": "*span*"
},
{
"key": "param.2",
"value": "*tag.getValue()*"
}
]
}
]
便于后续Web端实现灵活的查询。关于日志格式详细的讨论过程,可以参考《What format should we have in mind for logging to spans?》
同时在设计的过程中,刚开始找到插入点也是不合理的,比如刚开始选择的直接增强sl4j.Logger接口,这回造成一个问题,一旦用户没有使用Log4j创建Logger对象就会造成日志插件失效,最终在BFergerson的帮助下,换成了一个更加合理的插入点。
总结
这篇文章更多的可能是个人在开发logger-plugin插件过程中踩过坑的总结吧,在整个开发过程中,其实收获蛮多的,比如并非一个软件功能越多越好。这个需要从该软件本身的定位出发,兼顾性能和易用性,做到一种平衡。同时简单写了写logger-plugin插件的使用方法,希望能够给感兴趣的同学以帮助。