java日志的那些事
logback or log4j or slf4j
很多人对这些组件感到很熟悉,但是又有点陌生,这里主要介绍这几个组件的来历.
在javase1.4的时候,sun公司发布了一个JUL(Java Util Logging)日志组件,这个组件存在一些问题,比如配置缺乏灵活性,功能有限.甚至还有性能问题,俄罗斯程序员Ceki Gülcü设计了一款日志组件log4j,它完美解决了JUL的上述问题,后来这哥们就把log4j捐给了apache,在这段时间里面Ceki Gülcü对apache的管理方式颇有言辞,就另起炉灶创造出来了logback,同时logback和log4j之间的api有差异性,又创造出了slf4j,所以slf4j最早是为了解决logback和log4j兼容性而开发的,再后来apache因为logback受到冲击,停止维护log4j,转而开发新的日志组件log4j2,至此故事告一段落.
logback和log4j可以看成是一个项目组件中真正在干活的,而slf4j则是他们的接口,假设一下,抽象出这一层可以屏蔽掉底层接口的细节,从而提供统一接口规范,也就是所谓的facade模式.
logback的配置
这里以logback为例,解释下logback.xml中常见的配置概念:
- layout:
layout的作用就是把输出的日志按照指定格式pattern来输出,pattern里面会有很多占位符,比如%d{yyyy-MM-dd HH:mm:ss.SSS},所以layout第一件事情要做的事情就是解析设置的pattern按照'分割,将他们变成一个个的token,在找到相应的converter或者从MDC从获取值把它填充进去. - MDC:
MDC就是一个threadlocal的map,主要作用就是塞值,然后配置layout,输出相应的日志. - encoder:日志再经过
layout之后.会返回一个String,也就是最终输出的日志,encoder作用就是把它变成byte,方便后续的传输 - converter:这个就是一个接口,
ClassicConverter用于获取token的值,比如pattern是这样的,%d{yyyy-MM-dd HH:mm:ss.SSS}'${appDev}'${appName}'%level'%hostName'%thread'%logger{50}'%tid'%X{TRACE-ID}'%msg%n,这里面有个hostname的token,他的值会从这个定义的类中获取<conversionRule conversionWord="hostName" converterClass="com.pantheon.logback.IpConvert"/> - appender:
appender其实就是决定了encoder之后的byte要输出到哪里,可以输出到kafka,也可以输出到console,也可以输出到磁盘上.
日志采集和入库
输出到磁盘

流程如下
- 应用输出日志到磁盘
- filebeat实时采集日志将日志输出到kafka
- 在有logstah监听kafka,将日志转换成json格式,写入到es
这里加了一个kafka的组件,主要用作写es的缓冲层,避免因为日志量过大,而将es打垮的可能性.
输出到磁盘的好处在于:日志有多份,防止因为采集丢失日志而无法排查问题,当然缺点也很明显,因为磁盘有容量大小,一旦磁盘满了就会导致应用宕机.此外,如果应用是部署在k8s里面,磁盘就不能是pod里面的磁盘,要做pv,还要用deamonset来采集pv里面的日志,比较麻烦.
输出到kafka

上面这个流程去掉了输出到磁盘这一部分,而由应用直接将日志输出kafka,避免了上述可能出现的问题,但是它也有缺点,一旦项目输出到日志不能被kafka收集,比如网络问题,程序假死等,那对排查问题带来很多不便.
schema on read or schema on write
这个概念来源于splunk,splunk是一家著名的日志解决方案供应商,同名产品splunk提供了日志收集以及索引,实时监控告警,数据分析和可视化等一些列强大的功能,可以理解为是elk的增强版.
首先schema这个概念,其实就是说日志格式问题,比如我们用elk来采集,输出的日志是这样:
2023-08-17 10:48:27.349'test'test-order'INFO'10.2.11.15'host.docker.internal'main'com.alibaba.nacos.client.config.impl.ClientWorker'N/A''这里是输出的日志
但是我们在写入es的时候会变成这样
{
"message": "这里是输出的日志",
"@timestamp": "2023-08-17 10:48:27.349",
"appname": "test-order",
"ip": "172.25.5.152",
"hostname": "test-order-java-77b6cc8cc5-gp9vp",
"tid": "3008dc5972434bf6a5a5401e970c69e9",
"skyid": "TID:N/A",
"env": "test",
"thead": "http-nio-8080-exec-5",
"class": "com.alibaba.nacos.client.config.impl.ClientWorker",
"loglevel": "INFO",
"time": "2023-08-17 13:27:42.296"
}
在使用elk的时候,elk对整个log进行了清洗,,而对message本身却无能为力,因为它非结构化,只是一段文本,所以想要对message做很强大的聚合和分析,这个时候elk就捉襟见肘,这种在写入就将数据清洗搞定的方式就叫做schema on write,这种方式类似于跑hadoop任务,事先需要将想要的数据按照格式进行整理,再来跑hive脚本,缺点很明显,很不灵活.
而splunk则对数据分析提供了更多的功能,比如我有一个日志是这样的login success,userId:8u1123mmuujaa,想要根据这一类日志找出一天重复登录成功的用户,用es来实现会变得极其复杂,而splunk提供了类似pipline的语法,去抽取其中的数据,将其转化成结构化的结构,在将编写的语句变成job,分发到不同的节点上运行,这种在运行时去确定数据结构的做法叫做schema on read,这种做法在实际运用中非常方便,因为不需要事先对数据进行清洗,只需要调整我们的脚本,就能得到想要的数据.
除了splunk还有类似的产品,比如炎凰数据,sls,Azure data explore,日志易等等,当然他们也有一个缺点:价格很昂贵.
dashboard
我们再对日志进行聚合分析后,需要将其展示出来,常见的展示图有这几种:
- 饼状图
- 折现图
- 99线,95线:这个在对系统做性能分析的时候很有用,一眼就能看出长尾数据
- 环比图:可以是同一个事务在不同一个时期对比,也可以是不同的事物在同一时期之间的对比,也很直观
ELK中提供了kibana作为日志分析聚合展示的dashboard,当然也有其他的方案比如granfa.
alert
基于日志聚合的结果作为告警的方式,原理就是给定一个固定的时间窗口,在时间窗口中运行脚本最终得到一个值,把这个值和阈值对比,大于或者小于则发出告警,更多关于alert的可以参考这篇文章:https://blog.pantheon.press/?p=135 ,在elk的解决方案中alert属于白金版的功能,所以到目前为止,很少看到用kibana来做alert的.