涵盖Java日志系统的SLF4J

Posted 大俊Tech

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了涵盖Java日志系统的SLF4J相关的知识,希望对你有一定的参考价值。

Java程序员必备的logging技能,否则你会发现自己根本不会写代码了!

一 : slf4j是什么

     在讲解具体的实现的时候我们先简单说一下slf4j的功能,这样在看实现过程的时候就越看越知道就是这么回事儿,
     slf4j是一个门面(fecade)日志系统,设计思想主要是门面设计模式,他充当一切日志系统的门面,门面的意思可以理解为slf4j就是一个门,日志系统要通过他这道门然后才能进去真正打印日志的系统,这样做的好处就是统一了编程接口,程序中到处使用的都是org.slf4j.Logger接口打印日志,我们可以编写更符合面向对象的系统,更好的可读性和扩展性,灵活性。比如需要替换一个日志实现系统的时候我们可以不用改用程序中的代码,而只需要替换classpath中提供日志功能的包,接下来让我们看看slf4j是如何做到无感知的检测日志系统的。

二 : slf4j日志绑定过程

     org.slf4j.LoggerFactory,这个类是我们在使用sif4j的时候主要的API入口,他内部全是static方法。

private final static Logger LOGGER =LoggerFactory.getLogger(loggerName);

本质上LoggerFactory是一个 和它本身绑定的ILoggerFactory接口的包装器(wrapper),简单来说就是他提供了一个logger(loggerName)方法,和ILoggerFactory的logger方法同名,这个方法的实现就是调用ILoggerFactory的logger方法实现的生成一个Logger对象,所以叫做一个包装器。

public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
  }

那么我们只需要搞清楚LoggerFactory是如何实现ILoggerFactory接口的实例化过程的,也就理解了slf4j是如何绑定并调用不同的日志实现的了。

2.1 slf4j的绑定状态

      INITIALIZATION_STATE用于在LoggerFactory中表示日志实现类的绑定状态(初始化),主要有这么五个状态值

  • UNINITIALIZED = 0; 表示还没有初始化日志实现类

  • ONGOING_INITIALIZATION = 1; 表示正在查找实现类的过程中

  • FAILED_INITIALIZATION = 2; 表示初始化失败

  • SUCCESSFUL_INITIALIZATION = 3; 表示成功找到日志实现类

  • NOP_FALLBACK_INITIALIZATION = 4; 表示使用一个退化的日志记录器(没有任何操作的日志记录器)初始化日志实现

2.2 查找ILoggerFactory

       LoggerFactory#getILoggerFactory()方法用于在getLogger的时候查找ILoggerFactory实现的方法。如果slf4j的绑定状态(INITIALIZATION_STATE)的值还是UNINITIALIZED , 那么状态置为ONGOING_INITIALIZATION,然后执行LoggerFactory#performInitialization()查找日志实现,返回之后状态置为SUCCESSFUL_INITIALIZATION / FAILED_INITIALIZATION / NOP_FALLBACK_INITIALIZATION , 最后用一个switch(INITIALIZATION_STATE)语句根据不同的状态返回不同的ILoggerFactory的实现,之后就用这个factory对象生产Logger对象,然后返回给客户端使用,这样你就可以使用不同日志系统提供的日志实现了,你需要做的就是把slf4j-api.jar和提供了ILoggerFactory接口的实现类的包引入classthpath下就好,比如logback-classic.jar。以上就是slf4j绑定其他日志系统实现的主要逻辑。

      bind()是绑定到其他日志实现系统的主要逻辑的实现。都说slf4j是不同于commons-logging的编译期静态绑定日志框架,那为什么是编译期静态绑定的呢?原因就是这个bind方法的实现是已经预编译好的,通过查找一个静态的class路径来绑定其他日志系统的!这个class就是StaticLoggerBinder.class!,
private static String STATIC_LOGGER_BINDER_PATH="org/slf4j/impl/StaticLoggerBinder.class";
查找主要是使用ClassLoader去发定位这个类。所以所有需要支持通过slf4j提供的接口来打印日志的日志系统中都会有StaticLoggerBinder这个类的实现,并且包名必须是org.slf4j.impl,可以去看看logback-classic.jar下面有没有这个类就知道了。

classpath下可能会有多个StaticLoggerBinder.class文件,slf4j会把他们全部找出来,在之后会打印日志告诉有多个StaticLoggerBinder存在,并告诉你slf4j最终使用的是哪一个StaticLoggerBinder提供的LoggerFactory。

所以,如果你的项目在启动的时候出现几行红色的日志:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

那就是说明你的classpath下面找不到StaticLoggerBinder.class, slf4j拦截了这个异常之后处理打印的消息。

如果是以下这样的红色日志,说明你的classpath下面有很多StaticLoggerBinder 并且系统会告诉实际使用的是哪一个。

Class path contains multiple SLF4J bindings.
Found binding in /path/to/StaticLoggerBinder
Found binding in /path/to/StaticLoggerBinder
Found binding in /path/to/StaticLoggerBinder
See #multiple_bindings for an explanation.
...
Actual binding is of type [xxx.xxx.xxx.ILoggerFactory]

      StaticLoggerBinder的主要功能就是提供获取ILoggerFactory的方法,它本身是单例的,所以在查找完毕 StaticLoggerBinder之后会调用一下他们的getInstance()方法初始化这个类的一个单例对象,因为静态类的特性是只要我们第一次使用这个类的时候他就会被jvm加载,至于怎么定义第一次使用大家可以参看《Java编程思想第四版》,或者市面上一下称为java从入门到精通之类的书,当然百度也能给你答案。

三 : 各类主流日志系统是如何支持slf4j的调用的。

      理解了这点你就可以组合jar包,随意选择你真正需要的日志系统了,看起来很牛逼的样纸,而不是用猜了。
      前面说的总结一下,ILoggerFactory定了了Logger生成的接口,
org/slf4j/impl/StaticLoggerBinder定义了获取ILoggerFactory实现类对象的接口,LoggerFactory会通过ClassLoader查找org/slf4j/impl/StaticLoggerBinder,并试用其生成ILoogerFactory对象,之后通过ILoggerFactory.getLogger()生成Logger对象
      根据总结我们知道如果要提供一个日志系统能够支持slf4j的接口调用到我们的实现类,我们需要做的就是提供一个同包同名的org/slf4j/impl/StaticLoggerBinder类,一个自定义的ILoggerFactory的实现,和一个自定义的Logger的实现,StaticLoggerBinder的实现是返回我们自己的ILoggerFactory的实现,ILoggerFactory的实现是返回我们自己的Logger的实现,这样slf4j就能够通过静态绑定过程绑定我们的日志系统。如果是已经存在的日志系统我们只需要提供一个包实现以上接口,使用适配器模式将调用转接到该日志系统的api上调用即可。

      那如果应用已经使用特定的日志系统的接口编程了,有一天我发现哪个日志系统有问题,我想换成其他日志系统,那怎么办?那么就需要我们自己手动给这个日志系统增加一个桥接(桥接设计模式/代理模式/适配器模式)功能了,将其接口的调用桥接到slf4j,然后slf4j再去调用相应的实现,这样也不失为一种良好的扩展和重构。
      下面列出一些典型的日志系统,看看其实如何实现支持slf4j的绑定或者桥接的。

(1) :slf4j -> other 型,slf4j的api转到其他日志系统

  • logback-classic.jar

    logback提供了支持slf4j绑定的API,所以只要依赖logback-classic.jar就可以通过slf4j绑定调用了。
    当我们需要使用slf4j + logback 这对组合的时候,只需要依赖 slf4j-api.jar , logback-core.jar
    , logback-classic.jar(必须,否则不能绑定)。

  • slf4j-log4j12.jar

    同样的道理,当我们想用slf4j + log4j12 的时候只需要依赖 slf4j-api.jar , slf4j-log4j12.jar ,
    log4j.jar/log4j2.jar即可

  • slf4j-jdk14.jar

    当我们想用 slf4j + java日志系统的时候,需要依赖 slf4j-api.jar , slf4j-jdk14.jar

  • slf4j-simple.jar

    当我们只想要一个简单的打印功能,不需要日志系统的时候可以依赖 slf4j-api.jar 和 slf4j-simple.jar,这个jar提供了一个简单的打印功能。

  • slf4j-nop.jar

    这个jar可以让打印掉用什么都不做,相当于丢弃日志,和linux的 /dev/null类似。依赖slf4j-api.jar ,
    slf4j-nop.jar 使用slf4j的api编程,在运行时即可将日志丢弃。

(1) :other -> slf4j 型,其他日志系统的api转到slf4j

  • log4j-over-slf4j

    log4j1/2是一个经典的日志系统了,有很多系统是依赖于log4j的API接口来编程的,所以需要一个包,让log4j的api调用能够转到slf4j上,以调用其他的日志系统实现打印,那么这个包肯定需要包含log4j1/2的接口,并提供一个实现,当系统掉用log4j1/2的接口的时候,调用slf4j的接口打印。这个包就是
    log4j-over-slf4j.jar,值得注意的是系统中不能同时包含slf4j-log4j.jar 和 log4j-over-slf4j.jar,否则将造成栈溢出异常

  • jcl-over-slf4j

    同理,让依赖于org.apache.commons.logging(JCL)接口编程的项目的日志能够调用slf4j实现打印日志,需要依赖slf4j-api.jar 和 jcl-over-slf4j.jar即可

  • jcl-to-slf4j

    让依赖于java.util.logging(JUL)编程的应用的日志调用slf4j的接口打印,依赖slf4j-api.jar 和jcl-to-slf4j.jar即可,他的实现和其他不同,是实现一个Hadnler接口,具体的可以自己去看,很简单。


记住:当你想替换已有日志框架的实现的时候,排除原有的日志框架依赖,然后就可以用slf4j + slf4j-xxx.jar 来做这件事情,否则将会是一项灾难性的工作,如果是新开发的系统,那么强烈建议依赖于slf4j的接口来编程开发,这样会让系统更灵活,可读性强,并且OOP


以上是关于涵盖Java日志系统的SLF4J的主要内容,如果未能解决你的问题,请参考以下文章

Java日志体系 —— 日志框架切换

java日志系统 @Slf4j注解的正确使用

Java日志框架:slf4j作用及其实现原理

slf4j日志的使用

log4j2 or slf4j ? java日志框架,今天得说清楚!

#yyds干货盘点#Java日志门面之SLF4J