Slf4j类与接口

类与接口 用途
org.slf4j.LoggerFactory(class) 给调用方提供的创建Logger的工厂类,在编译时绑定具体的日志实现组件
org.slf4j.Logger(interface) 给调用方提供的日志记录抽象方法,例如debug(String msg),info(String msg)等方法
org.slf4j.ILoggerFactory(interface) 获取的Logger的工厂接口,具体的日志组件实现此接口
org.slf4j.helpers.NOPLogger(class) 对org.slf4j.Logger接口的一个没有任何操作的实现,也是Slf4j的默认日志实现
org.slf4j.impl.StaticLoggerBinder(class) 与具体的日志实现组件实现的桥接类,具体的日志实现组件需要定义org.slf4j.impl包,并在org.slf4j.impl包下提供此类,注意在slf4j-api-version.jar中不存在org.slf4j.impl.StaticLoggerBinder,在源码包slf4j-api-version-source.jar中才存在此类

Slf4j调用过程源码分析

只加入slf4j-api-version.jar,不加入任何实现包, pom配置

1
2
3
4
5
6
<!--只有slf4j-api依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.35</version>
</dependency>

程序入口类

1
2
3
4
5
6
7
8
9
10
11
package pers.fulsun;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class App {
final static Logger logger = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
logger.info("Hello World");
}
}

源码追踪分析

slf4j的用法就是LoggerFactory去拿slf4j提供的一个Logger接口的具体实现。

Logger logger = LoggerFactory.getLogger(Object.class);

  1. 调用LoggerFactory的getLogger()方法创建Logger

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static Logger getLogger(Class<?> clazz) {
    Logger logger = getLogger(clazz.getName());
    if (DETECT_LOGGER_NAME_MISMATCH) {
    Class<?> autoComputedCallingClass = Util.getCallingClass();
    if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
    Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
    autoComputedCallingClass.getName()));
    Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
    }
    }
    return logger;
    }
  2. 调用LoggerFactory的getILoggerFactory方法来创建ILoggerFactory

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /**
    * Return a logger named according to the name parameter using the
    * statically bound {@link ILoggerFactory} instance.
    *
    * @param name
    * The name of the logger.
    * @return logger
    */
    public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
    }

  3. 调用LoggerFactory的performInitialization方法来进行初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public static ILoggerFactory getILoggerFactory() {
    if (INITIALIZATION_STATE == UNINITIALIZED) {
    synchronized (LoggerFactory.class) {
    if (INITIALIZATION_STATE == UNINITIALIZED) {
    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
    performInitialization();
    }
    }
    }
    switch (INITIALIZATION_STATE) {
    case SUCCESSFUL_INITIALIZATION:
    return StaticLoggerBinder.getSingleton().getLoggerFactory();
    case NOP_FALLBACK_INITIALIZATION:
    return NOP_FALLBACK_FACTORY;
    case FAILED_INITIALIZATION:
    throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
    case ONGOING_INITIALIZATION:
    // support re-entrant behavior.
    // See also http://jira.qos.ch/browse/SLF4J-97
    return SUBST_FACTORY;
    }
    throw new IllegalStateException("Unreachable code");
    }
  4. 进行绑定

    1
    2
    3
    4
    5
    6
    private final static void performInitialization() {
    bind();
    if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
    versionSanityCheck();
    }
    }

  5. 调用LoggerFactory的findPossibleStaticLoggerBinderPathSet()方法获取StaticLoggerBinderPath集合

  6. 回到第四步,Classpath下没有StaticLoggerBinderPath,集合元素为空,调用LoggerFactory的reportMultipleBindingAmbiguity()方法,记录绑定的StaticLoggerBinder信息

  7. LoggerFactory的bind()方法找不到StaticLoggerBinder,抛出NoClassDefFoundError异常

  8. LoggerFactory的bind()方法捕获NoClassDefFoundError异常,匹配到StaticLoggerBinder关键词记录信息到控制台

  9. LoggerFactory的performInitialization()方法内部调用bind()方法结束, 此时INITIALIZATION_STATE 不等于SUCCESSFUL_INITIALIZATION

  10. LoggerFactory的getLogger()方法内部getILoggerFactory()方法调用完成,创建出NOPLoggerFactory,然后由NOPLoggerFactory调用内部的getLogger()方法,创建出NOPLogger

  11. 调用NOPLoggerFactory的 getLogger() 创建NOPLogger

  12. App类内部的logger实际为NOPLogger,调用logger.info()方法实际调用的是NOPLogger的info方法

加入Logback组件源码分析

Slf4j作为门面采用Logback作为实现或者采用其它上面提到过的组件作为实现类似,这里只分析采用Logback组件作为实现

pom配置如下,程序入口类同上

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.35</version>
</dependency>
<!--logback-classic依赖logback-core,会自动级联引入-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
  • 1、2、3、4步同上

  • 调用LoggerFactory的findPossibleStaticLoggerBinderPathSet()方法获取StaticLoggerBinderPath集合,此时staticLoggerBinderPathSet 集合有一个元素

  • reportMultipleBindingAmbiguity 判断元素是否>1,不满足

  • 在LoggerFactory的bind()方法中调用loback包下的StaticLoggerBinder创建单例对象

  • 在LoggerFactory的bind()方法中调用reportActualBinding()记录日志加载的真实绑定信息

  • LoggerFactory中INITIALIZATION_STATE的值为SUCCESSFUL_INITIALIZATION,调用StaticLoggerBinder的单例对象获取ILoggerFactory

  • 此时LoggerFactory中的getLogger()方法中获取到的ILoggerFactory实际上是logback jar下的LoggerContext

  • 此时LoggerFactory调用getLogger()方法获取到的Logger实际上是logback jar下的Logger

  • 最后输出

    1
    21:58:15.868 [main] INFO pers.fulsun.App - Hello World

加入多种日志实现组件

pom.xml 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.35</version>
</dependency>
<!--logback-classic依赖logback-core,会自动级联引入-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.35</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jcl</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.30</version>
</dependency>

基本步骤同上,这里只追踪主要不同点

  • 追踪LoggerFactory的bind()方法内部调用findPossibleStaticLoggerBinderPathSet()方法后,从classpath下4个jar包内找到StaticLoggerBinder

  • 此时LoggerFactory的bind()方法内部调用reportMultipleBindingAmbiguity()方法,给出警告信息classpath下同时存在多个StaticLoggerBinder,JVM会随机选择一个StaticLoggerBinder

  • 此时LoggerFactory的bind()方法内部调用reportMultipleBindingAmbiguity()方法,给出警告信息classpath下同时存在多个StaticLoggerBinder,

  • JVM会随机选择一个StaticLoggerBinder,这个地方sfl4j也在reportActualBinding方法中报告了绑定的是哪个日志框架:

    1
    2
    3
    4
    5
    6
    private static void reportActualBinding(Set<URL> binderPathSet) {
    // binderPathSet can be null under Android
    if (binderPathSet != null && isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {
    Util.report("Actual binding is of type [" + StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr() + "]");
    }
    }

StaticLoggerBinder

  • getLogger的时候会去classpath下找STATIC_LOGGER_BINDER_PATHSTATIC_LOGGER_BINDER_PATH值为”org/slf4j/impl/StaticLoggerBinder.class“,即所有slf4j的实现,在提供的jar包路径下,一定是有”org/slf4j/impl/StaticLoggerBinder.class”存在的,我们可以看一下:

Slf4j时如何桥接遗留的api

在实际环境中我们经常会遇到不同的组件使用的日志框架不同的情况,例如Spring Framework使用的是日志组件是Commons Logging,XSocket依赖的则是Java Util Logging。当我们在同一项目中使用不同的组件时应该如果解决不同组件依赖的日志组件不一致的情况呢?现在我们需要统一日志方案,统一使用Slf4j,把他们的日志输出重定向到Slf4j,然后Slf4j又会根据绑定器把日志交给具体的日志实现工具。Slf4j带有几个桥接模块,可以重定向Log4j,JCL和java.util.logging中的Api到Slf4j。

log4j-over-slf4j-version.jar 将Log4j重定向到Slf4j
jcl-over-slf4j-version.jar 将Commons Logging里的Simple Logger重定向到slf4j
jul-to-slf4j-version.jar 将Java Util Logging重定向到Slf4j