log4j slf4j实现_日志框架Log4j源码解析(1)

95d7a21c1bbcf811b96af60b7de48471.png

这篇文章笔者准备和大家介绍一下日志框架Log4j,Log4j日志框架已经很老了,但是后面的日志框架和Log4j都差不多,所以Log4j还是一个很好的学习对象。大家在写代码的过程中肯定会使用到日志框架。大家应该都会用,但是用的对不对,等这个专题完成了相信读者就心里有数了。我们先看一张图

97f6f031e4b7a3c9d6fab7b2e33da8a8.png

我们在使用的时候接触到的应该是Logger,然后调用Logger的方法输出日志。笔者先把图上出现的对象介绍一些以及捋一捋他们之间的关系。(下面很重要)

  1. 在我们打印日志的时候会输入需要打印的信息,这个信息就是我们的信息源,也就是上图中的LoggerEvent。
  2. 我们获取Logger的时候会传入对应的类的全限定名称,其实是通过这个名称去LoggerReposity中取对应的Logger(也就是说LoggerReposity是我们的一个Logger的容器)
  3. 获取到Logger之后我们要去输出日志,输出的目的地很多可能是控制台、文件、远程对象。可能一行日志我们要输出到多个地方,这个时候我们就要获取到Logger中所有的Appender的集合,然后遍历去调用输出方法。
  4. 输出的时候我们会指定日志的格式,比如说时间、线程、所属类。这里我们就需要格式化每个Appender都会有对应的格式化也就是Layout(对应一个)。
  5. 有可能在打印日志的时候需要一些自定义的处理,那我们就需要拦截需要打印的消息,根据消息做一些操作,这个就是Filter(可能有多个也可能没有)。
  6. 可以看到在我们输出日志的时候LoggerEvent实际上是在整个流程中传递的,可以看成这个是上下文。

在实际使用的时候我们更多的是使用slf4j来获取Logger对象,因为在项目中途可能需要换日志框架比如从log4j换成logback,如果我们使用的对应日志框架中的API,那就尴尬了,我的项目那么多类都得改代码。所以slf4j就出来了,它主要使用的是适配器模式。在项目中调用的API是slf4j的API,它在会根据实际使用的日志框架适配成统一的调用格式。也就是方便项目在中途换日志框架了。在log4j分析差不多之后,笔者就简单分析slf4j。

Log4j的主要源码还是比较少的。笔者就直接根据上面的图进行介绍,首先我们来看一下Logger类。

fba924dcf3098f2dd0acbda044139749.png

首先看一下Logger的结构关系,继承了Category实现了AppenderAttachable。从类名来看Category这个应该是根据不同种类打印日志,AppenderAttachable这个应该是连接Appender。我们从AppenderAttachable开始看

e2ac39a2e413b017032c7a2fcd410556.png

果然看这些方法add、get、getAll、remove这不就是在操作一个容器嘛。应该就是关联Appender的接口了。我们在看实现类Category

d863c7b00bf58ae879b654b1f93b8b91.png

确实有一个对应的实现类。这个里面还有另外一些成员变量,

  • name:Logger对应的名称
  • level:日志级别
  • parent:Logger的父亲(继承关系,如果子Logger没有指定Appender默认使用父亲的)
  • FQCN:全限名称指定了当前类
  • resourceBundle:国际化相关
  • repository:所属的容器,
  • aai:相关的Appender
  • additve:这个变量就是控制是否使用父类的Appender

成员变量介绍完了,这个类的方法太多了。Logger主要的方法也是在这个类里面,我们先看看Logger的相关方法,然后在回来介绍这个

c5875825d37cc6c20bfcdd50f241fe30.png

方法不多,主要就是获取Logger变量(从Logger容器中获取)。我们回去分析它的父类。先来介绍关联Appender类AppenderAttachableImpl实现了AppenderAttachable。这个类只有一个成员变量。

protected 

然后我们在看实现的方法,这里的方法笔者不打算全部介绍,因为就是简单的操作Vector。

public 

其他的方法也都差不多就是操作这个集合,到这里读者肯定已经发现了Logger和Appender之间的关系是一对多。我们后面通过方法来进行分析,就先分析常用的方法

public  

首先判断根日志级别可否输出,默认是debug的,然后我们在调用getEffectiveLevel方法获取Logger设置的输出日志级别,如果当前的Logger没有设置则获取父类的级别。如果日志级别大于或者等于则说明可以输出日志调用forcedLog输出日志。

protected
  

这里创建了一个LoggerEvent并调用方法获取对应的Appender进行输出,

public 

到这里我们就跟踪完了Logger.debug()的方法,后面doAppend就是Appender的方法了,后面再介绍。这样其实我们已经把Logger介绍完了,其他的输出方法都差不多。后面我们的注意点应该就是在Appender、LoggerRepository了。我们来看看

Appender先来看接口的方法

3d310fbb0a92506348891e8bf6524b0b.png

根据方法名我们可以看到有setFilter、setErrorHandler、setLayout。读者应该就可以猜到Appdener的组成了,包括Filter、Layout、以及ErrorHandler。我们来看实现类AppenderSkeleton,这个类是一个抽象类。这个类用了模板模式,将公共的方法抽出来在父类,父类调用子类的具体实现。我们先来看看对应的成员变量

protected 

成员变量应该不难理解,成员变量对应一些set、get方法这里就不做介绍。咱直接接着Logger调用的方法来分析。

public 

这里先判断对应的日志级别是否能输出,如果不能输出就直接返回,能输出就先获取拦截器。拦截器有三种状态


  • DENY:拒绝 即不处理直接return。后面的拦截器也不会处理了,就是说不符合条件直接返回了
  • ACCEPT:接受 即满足条件我直接使用Appender去处理,不会再调用后面的拦截器了
  • EUTRAL:中立 拦截器处理了一些事情,后面的拦截器继续处理。

上面拦截器应该很好理解,这里用了责任链模式,责任链模式是常用的设计模式之一,一个拦截器一个拦截器往下处理。最后调用了append方法这个方法是一个抽象方法也就是子类实现了。模板方法的设计模式就体现出来了,不关心子类怎么实现(也是多态的体现)。到这里我们就要去子类里面看对应的实现我们看WriterAppender这个子类,这个子类还有对应的子类,是我们常用的Console、File模式。我们看下成员变量。

protected 

三个成员变量,主要注意的是qw这个是实际的写者,我们可以根据不同的输出创建不同的写者,比如文件,那就是使用输出流创建一个写文件的写者,如果是Console那就是System.out创建写者

public  

首先检查变量,检查是否关闭、写者是否为空、布局是否为空、三个条件一个不满足就不写了。

protected 

这里就把日志格式化之后输出了。整个大体流程就串完了,还是比较简单的,后面就是具体的一些实现了,比如解析配置文件初始化容器、初始化Logger等一系列操作,下一篇就介绍一下初始化容器吧。如果对文章和作者感兴趣,可以关注作者的个人公众号。里面有更多的源码分析。

78cdb6cd065ef5feffa77707a2537de0.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值