Slf4j源码解析 - 无底层日志实现框架时的执行流程

一. 前言

  • commons-logging 和 slf4j 都是日志的接口,供用户使用,而没有提供实现
  • commons-logging 和 slf4j 作用如下图所示

image

二. slf4j 源码类结构图

image

三. slf4j-api 主要类介绍

  • org.slf4j.Logger 接口
    • client 使用日志的主要入口
    • 日志行为的最顶层接口,定义各种日志打印方法,如 log.info()
    • 第三方日志实现框架会实现该接口
  • org.slf4j.ILoggerFactory 接口
    • 获取各种 Logger 的接口,定义了 getLogger() 方法
    • 第三方日志实现框架会实现该接口
  • org.slf4j.LoggerFactory 类
    • slf4j 包中最主要的类
    • 作用是编译时绑定具体日志实现框架,对 client 提供具体 Logger 对象
  • org.slf4j.spi.LoggerFactoryBinder
    • 内部接口,用来帮助 org.slf4j.LoggerFactory 来绑定合适的 ILoggerFactory 实例
    • ch.qos.logback:logback-core.jar 中的 org.slf4j.impl.StaticLoggerBinder 实现了该接口
    • StaticLoggerBinder是 slf4j 与 底层日志实现框架间沟通的桥梁,后续会详细讲解
  • NOPLogger 类
    • slf4j 包自带的 Logger 实现类
    • 实现 Logger 接口,实现了其中的 info/warn 各种方法, 但方法体为空
  • NOPLoggerFactory 类
    • slf4j 包自带的 ILoggerFactory 实现类
    • 实现了 ILoggerFactory 接口,getLogger() 方法 返回一个 NOPLogger 实例,NOPLogger 实例的单例

四. slf4j 日志执行过程大致分为两个步骤

  1. slf4j 通过 LoggerFactory 获取 Logger
  2. Logger 记录日志

下面分别来介绍

五. slf4j 通过 LoggerFactory 获取 Logger 的过程

  • 使用 slf4j 的方式如下面代码所示

    • 其中 Logger 是定义在 slf4j-api 中的接口,其中声明了各种日志打印方法如 log.info() 等
    • 获取 Logger 只需通过 LoggerFactory 的静态方法 getLogger() 来实现
      1
      2
      3
      4
      5
      6
      7
      8
      public class LogDemo {
      public static void main(String[] args) {
      //调用 LoggerFactory.getLogger() 方法获取 Logger
      Logger logger = LoggerFactory.getLogger(LogDemo.class); //断点处
      logger.info("log");
      System.out.println("log test");
      }
      }
  • LoggerFactory 获取Logger 的时序图如下

image

  • LoggerFactory 定义了若干静态变量,代码如下
    • 其中定义了 5个 静态不可变 int 类型的变量,用于表示 LoggerFactory 初始化过程的不同状态值
      • UNINITIALIZED 未初始化
      • ONGOING_INITIALIZATION 正在被初始化
      • FAILED_INITIALIZATION 初始化失败
      • SUCCESSFUL_INITIALIZATION 初始化成功
      • NOP_FALLBACK_INITIALIZATION 空状态,表示无底层日志实现框架时的结果
    • volatile 变量 INITIALIZATION_STATE,用于标记 ILoggerFactory 实例初始化的结果,且初始化为 UNINITIALIZED
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public final class LoggerFactory {

//INITIALIZATION_STATE 的可能结果:
static final int UNINITIALIZED = 0;
static final int ONGOING_INITIALIZATION = 1;
static final int FAILED_INITIALIZATION = 2;
static final int SUCCESSFUL_INITIALIZATION = 3;
static final int NOP_FALLBACK_INITIALIZATION = 4;

//类加载时初始化该变量
static volatile int INITIALIZATION_STATE = UNINITIALIZED;
static final SubstituteLoggerFactory SUBST_FACTORY = new SubstituteLoggerFactory();

//定义了默认的 LoggerFactory
static final NOPLoggerFactory NOP_FALLBACK_FACTORY = new NOPLoggerFactory();
  • LoggerFactory 通过静态方法 getLogger(Class clazz) 获取 Logger
    • 方法内部又通过 getLogger(String name) 方法获取 Logger
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Return a logger named corresponding to the class passed as parameter,
* using the statically bound ILoggerFactory instance.
* 根据静态绑定的 ILoggerFactory 实例 获取 logger
**/
public static Logger getLogger(Class<?> clazz) {

//核心代码 class=log.learn.LogDemo
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;
}
  • getLogger(String name) 方法使用同类中返回类型为 ILoggerFactory 的 getILoggerFactory() 方法获取 ILoggerFactory 实例
    • ILoggerFactory 是获得 Logger 的工厂接口,接口定义了获得 Logger 的方法 getLogger()
  • getILoggerFactory() 方法通过 synchronized 代码块初始化 INITIALIZATION_STATE
  • 随后通过 switch INITIALIZATION_STATE 来获取日志工厂类

    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48

    /**
    * Return a logger named according to the name parameter using the
    * statically bound ILoggerFactory instance.
    * 说明最终获取 logger 的实现是由 静态绑定的 ILoggerFactory 实例决定的
    **/
    public static Logger getLogger(String name) {
    //进入 getILoggerFactory 方法
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
    }

    /**
    * Return the ILoggerFactory instance in use.
    *
    * ILoggerFactory instance is bound with this class at compile time.
    * 在编译时刻绑定 ILoggerFactory 的具体实例
    **/
    public static ILoggerFactory getILoggerFactory() {

    //类加载时, INITIALIZATION_STATE == UNINITIALIZED,标志为未初始化
    //且对 初始化状态的更新使用了 双重校验锁(DCL,即 double-checked locking),参考单例模式初始化
    if (INITIALIZATION_STATE == UNINITIALIZED) {
    synchronized (LoggerFactory.class) {
    if (INITIALIZATION_STATE == UNINITIALIZED) {
    //将初始化状态标记为 ONGING
    INITIALIZATION_STATE = ONGOING_INITIALIZATION;ONGING
    //执行 ILoggerFactory 实例初始化,实际是开始在 classpath 寻找看看是否存在 。。。
    //我们先跳到初始化的位置,这也是Slf4j 实现 facade 的核心所在
    //了解了核心,再转回来看下面的
    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");
    }
  • 此刻 INITIALIZATION_STATE == UNINITIALIZED 未初始化 ,performInitialization() 方法进而调用 bind() 方法初始化 INITIALIZATION_STATE

    1
    2
    3
    4
    5
    6
    7
    8
    //可以看到该方法用 private 修饰,即在类内部使用
    private final static void performInitialization() {
    //核心代码 bind, 开始绑定 ILoggerFactory 具体实现
    bind();
    if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
    versionSanityCheck();
    }
    }
  • bind() 方法用于初始化 INITIALIZATION_STATE

    • bind() 方法的关键代码是 StaticLoggerBinder.getSingleton(),该行代码会加载 org.slf4j.impl.StaticLoggerBinder.class
    • INITIALIZATION_STATE 的赋值动作发生在该条代码的执行成功和发生异常时
      • 正常执行时
        • INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION
      • org.slf4j.impl.StaticLoggerBinder.class 未找到时,即进入 NoClassDefFoundError catch语句时
        • INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION
      • org.slf4j.impl.StaticLoggerBinder.class 可以找到, 但未定义 getSingleton() 方法时,即进入 java.lang.NoSuchMethodError catch 语句时
        • INITIALIZATION_STATE = FAILED_INITIALIZATION
    • 在对 INITIALIZATION_STATE 初始化前,bind 方法先会查找 org.slf4j.impl.StaticLoggerBinder.class 类路径,并先保存到集合 Set 中
      • 方法 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet) 在 Set size > 1 时告知 client 此时包括多个 bindings
      • 方法 reportActualBinding(staticLoggerBinderPathSet) Set size > 1 告知client 实际绑定的是哪一个 bindings
        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
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        private final static void bind() {
        try {
        //存放
        Set<URL> staticLoggerBinderPathSet = null;
        // skip check under android, see also
        // http://jira.qos.ch/browse/SLF4J-328
        if (!isAndroid()) {
        //绑定的过程,该方法去寻找 StaticLoggerBinder.class 文件
        staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
        reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
        }
        // the next line does the binding
        //寻找 StaticLoggerBinder 类,若找不到则抛异常
        //pom 没有添加 日志实现依赖,抛异常
        StaticLoggerBinder.getSingleton();
        INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
        reportActualBinding(staticLoggerBinderPathSet);
        fixSubstituteLoggers();
        replayEvents();
        // release all resources in SUBST_FACTORY
        SUBST_FACTORY.clear();
        } catch (NoClassDefFoundError ncde) {
        String msg = ncde.getMessage();
        if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
        //初始化状态 INITIALIZATION_STATE 为 NOP_FALLBACK_INITIALIZATION,此标记意为无底层日志实现框架,日志的打印交付给谁? 先留个悬念
        INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
        Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
        Util.report("Defaulting to no-operation (NOP) logger implementation");
        Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");

        } else {
        failedBinding(ncde);
        throw ncde;
        }
        } catch (java.lang.NoSuchMethodError nsme) {
        String msg = nsme.getMessage();
        if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
        INITIALIZATION_STATE = FAILED_INITIALIZATION;
        Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
        Util.report("Your binding is version 1.5.5 or earlier.");
        Util.report("Upgrade your binding to version 1.6.x.");
        }
        throw nsme;
        } catch (Exception e) {
        failedBinding(e);
        throw new IllegalStateException("Unexpected initialization failure", e);
        }
        }
  • findPossibleStaticLoggerBinderPathSet() 方法通过类加载器找到 StaticLoggerBinder.class

    • 这里定义了 static 变量 STATIC_LOGGER_BINDER_PATH 来定义需要加载的类资源路径
      • STATIC_LOGGER_BINDER_PATH = “org/slf4j/impl/StaticLoggerBinder.class”
      • StaticLoggerBinder.class 类是需要底层日志框架来定义的类,且该类实现 org.slf4j.impl.LoggerFactoryBinder 接口,该接口 由 slf4j-api 提供
      • StaticLoggerBinder 通过实现 LoggerFactoryBinder 接口的 getLoggerFactory 方法来获得具体 LoggerFactory
    • 若 loggerFactoryClassLoader 为 null,则调用ClassLoader.getSystemResources()
    • 否则,调用 loggerFactoryClassLoader.getResources()
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
27
28
29
30
31
32
33
34
35
36
37
38
// We need to use the name of the StaticLoggerBinder class, but we can't
// reference the class itself.
// 根据classpath 下是否存在 StaticLoggerBinder 来作为判断是否存在 具体日志实现框架的标准
// 此处也暗示出 所有日志实现框架的包路径及 所必需包含的 StaticLoggerBinder 类路径
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";

static Set<URL> findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order
// during iteration
Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration<URL> paths;


//而用户在运行期,是获取不到引导类加载器bootstrapclassloader的,因此当一个类获取它的类加载器,得到的对象时null,就说明它是由引导类加载器加载的。
//引导类加载器是负责加载系统目录下的文件,因此源码中使用getSystemresource来获取资源文件。
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
//判断能否找到 StaticLoggerBinder 的 class 文件
//pom 未添加 日志实现 依赖包的话 是找不到该 class 文件的
//因为可能存在若干个该 class 文件,故此处用 Enumeration 来迭代存储URL,Enumeration 现在被 Iteration 替代
//两者功能一致
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
while (paths.hasMoreElements()) {
URL path = paths.nextElement();
//将所有class url 存到有序set 中
staticLoggerBinderPathSet.add(path);
}
} catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
//返回保存了class URL 的有序集合,转到 step6,继续向下执行
return staticLoggerBinderPathSet;
}
  • 由于项目并未加入日志实现框架
    • classpath 下找不到StaticLoggerBinder.class
    • StaticLoggerBinder.getSingleton() 将会抛出 NoClassDefFoundError 异常
    • 初始化 INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION
    • switch 语句,当初始化状态为 NOP_FALLBACK_INITIALIZATION 时,返回 NOP_FALLBACK_FACTORY, NOP_FALLBACK_FACTORY 是 LoggerFactory 下 slf4j-api 中一个 NOPLoggerFactory 类对象
1
2
3
4
5
6
7
public static Logger getLogger(String name) {
//classpath 不存在日志实现框架的,此刻得到的是 NOPLoggerFactory 的实例
//
ILoggerFactory iLoggerFactory = getILoggerFactory();
//进入NOPLoggerFactory 的 getLogger
return iLoggerFactory.getLogger(name);
}
  • 通过 NopLoggerFactory 可以获取slf4j 自带的 NOPLogger 对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class NOPLoggerFactory implements ILoggerFactory {

    public NOPLoggerFactory() {
    // nothing to do
    }

    //获取具体日志对象
    public Logger getLogger(String name) {
    return NOPLogger.NOP_LOGGER;
    }

    }

六. 通过 Logger 打印日志

  • 由五可知,Logger 的具体实现类是 NOPLogger
  • NOPLogger 继承了 MarkerIgnoringBase 类,而 MarkerIgnoringBase 类实现了 org.slf4j.Logger 接口
  • NOPLogger 的各种日志打印方法均为空,如 log.info()

    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
    27
    28
    public class NOPLogger extends MarkerIgnoringBase {

    private static final long serialVersionUID = -517220405410904473L;

    /**
    * The unique instance of NOPLogger.
    * 获得 NOPLogger 实例
    */
    public static final NOPLogger NOP_LOGGER = new NOPLogger();

    ......

    //实现的Logger 的所有方法,方法体全为空
    /** A NOP implementation. */
    public final void debug(String format, Object arg1, Object arg2) {
    // NOP
    }

    /** A NOP implementation. */
    final public void trace(String msg) {
    // NOP
    }

    /** A NOP implementation. */
    final public void info(String msg) {
    // NOP
    }
    }
  • 重新进入main 方法

    1
    2
    3
    4
    5
    6
    7
    public class LogDemo {
    public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(LogDemo.class);
    //进入NOPLogger 的 info 方法,方法体为空
    logger.info("log");
    }
    }

七. 最终输出结果

1
2
3
4
5
Connected to the target VM, address: '127.0.0.1:64503', transport: 'socket'
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.
//此处因为 NOPLogger.info() 方法体为空,故未输出任何东西

八. 其他

  • StaticLoggerBinder 类相关事项

    • StaticLoggerBinder 是 slf4j-api 核心类,是与底层日志实现框架沟通的桥梁
    • StaticLoggerBinder.class 在 slf-api.jar 中是不存在的,并没有打包进去,但是在slf-api-source.jar 是存在 org.slf4j.impl.StaticLoggerBinder这个类的,因为需要保证 slf4j 本身编译不报错
      • 源码包中是为了给slf4j-api module 提供一个 dummy StaticLoggerBinder
      • 真正的实现是在每个 slf4j binding project中,比如 slf4j-nop, slf4j-log4j12 等,在实际运行时加入相应 jar,运行时就能调用到真实的日志包了
  • 第三方日志实现框架需要提供

    • Logger 接口实现类,用以提供具体日志记录行为,如 log.info()
    • ILoggerFactory 接口实现类,用以提供各自具体的 Logger 对象
  • 多个日志实现框架时的警告信息

1
2
3
4
5
6
7
8
# reportMultipleBindingAmbiguity(staticLoggerBinderPathSet) 方法打印
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/F:/Maven-3.3.9/apache-maven/repository/ch/qos/logback/logback-classic/1.0.13/logback-classic-1.0.13.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/F:/Maven-3.3.9/apache-maven/repository/org/slf4j/slf4j-simple/1.7.25/slf4j-simple-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.

# reportActualBinding(staticLoggerBinderPathSet) 方法打印
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
0%