SLF4J 源码阅读

Posted stone365

tags:

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

一、写在开头

SLF4J 是各类流行日志框架的抽象接口层,它为 logbacklog4j 等日志框架提供了统一的接口,可以方便的在工程内进行日志框架的更换,这个更换的前提是工程首先使用了 SLF4J 的日志接口打印信息,在更换时,只需引入新的日志框架和适配层即可完成更换,而不需要到处去修改打印接口。

在工程中已经直接使用日志框架,如何切换到 SLF4J 接口?

SLF4J 官方有详细的介绍,这里只做简要的概括,更加详细的情况可以参考官网。

这里分为两种情况。第一种,工程使用了 SLF4J 的原生实现,如 logbacklogback 直接实现了 SLL4JAPI,所以使用 logback 日志框架的工程无需额外操作;

第二种,使用 log4jSLF4J 的出现晚于 log4j ,为了也能统一、兼容先前出现的日志框架,SLF4J 引入了适配层,每个日志框架都有对应的适配层,SLF4J 通过适配层接口间接的调用具体的日志框架。这种情况切换到 SLF4J 框架时会麻烦一点,1)更改工程中的打印 API,2)加入对应的适配层。

除了适配层,还有桥接层。

关于桥接层,在 SLF4J 官网也有具体的介绍,迫于篇幅,这里只做简要介绍。

考虑一种情况,X 工程正在使用 SLF4J 搭配 log4j 2 构建日志打印框架,在后续对工程的改动中,需要引入一个基础模块 Y,这个 Y 模块里使用 commons-logging 日志框架进行日志的打印工作,如何让 Y 模块也统一使用 X 工程的 SLF4J 框架呢?这时需要引入桥接层,桥接层对 commons-logging 日志框架进行改造,在 commons-loggingAPI 中,将具体的调用改为对 SLF4J API 的调用,完成桥接,这个对应的包就是 jcl-over-slf4jSLF4J 官方还支持特定的几种桥接模式,对应的包为 xxx-over-xxx.jar,若未按照官方推荐的桥接模式来实现,可能会引起错误。

SLF4J 在版本上的区别。SLF4J1.8 版本之前,使用静态绑定的方式去寻找日志的实现,所有的适配层都会在这个路径上 org/slf4j/impl/ 实现 StaticLoggerBinder.class 类,SLF4J 会找到这个类并完成实例化;

SLF4J1.8 版本之后,使用 ServiceLoader 在适配层来加载对应的 Provider,再由 Provider 完成具体日志的实例化。

本次的源码阅读基于 1.8 版本之前,通过静态绑定的方式完成日志框架的实例化,阅读版本为 slf4j-api-1.7.26log4j-slf4j-impl-2.11.2log4j-core-2.11.2log4j-api-2.11.2

二、SLF4J 查找适配层的静态绑定类

slf4j 的一般使用 API 如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// 获取 logger
Logger logger = LoggerFactory.getLogger(AController.class);
logger.info("test content:{}","test");

// slf4j-api-1.7.26 LoggerFactory.java
public static Logger getLogger(Class<?> clazz) {
    // 继续静态绑定逻辑
    Logger logger = getLogger(clazz.getName());
    // 若获取 logger 所在的类和参数传进来的类不一致,则会给出警报,一般不会进入这段逻辑
    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;
}

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

// 获取日志工厂
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:
            return SUBST_FACTORY;
    }
    throw new IllegalStateException("Unreachable code");
}

private final static void performInitialization() {
    // 绑定逻辑
    bind();
    // 当成功完成日志实例化后,比较 slf4j 与适配层版本是否符合要求
    if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
        versionSanityCheck();
    }
}

private final static void bind() {
    try {
        Set<URL> staticLoggerBinderPathSet = null;
        if (!isandroid()) {
            // 用类加载器找出这个类 org/slf4j/impl/StaticLoggerBinder.class 
            staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
            // 检查是否找到多个 StaticLoggerBinder 类,若找到大于 1 个静态绑定类,则给出警告
            reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
        }
        // 完成绑定
        StaticLoggerBinder.getSingleton();
        // 更新绑定状态
        INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
        // 报告绑定的类
        reportActualBinding(staticLoggerBinderPathSet);
        // SubstituteLoggers 是 Logger 的一个实现,下面对它进行一些设置
        fixSubstituteLoggers();
        replayEvents();
        SUBST_FACTORY.clear();
    } catch (NoClassDefFoundError ncde) {
        // 打印异常信息,找不到静态绑定类(找不到对应的适配层)
        String msg = ncde.getMessage();
        if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
            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) {
        // 打印异常问题,版本不对,静态绑定类没有 getSingleton 方法
        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);
    }
}
// log4j-slf4j-impl-2.11.2 StaticLoggerBinder.java
// 静态绑定类会对 log4j 2 日志框架进行实例化
private StaticLoggerBinder() {
    loggerFactory = new Log4jLoggerFactory();
}

以上就是寻找静态绑定类和实例化静态绑定类的过程,StaticLoggerBinder 使用了单例模式来实例化自己,以及实例化 log4j-api 中的日志工厂类 LogManager

三、StaticLoggerBinder 单例模式

来看看 StaticLoggerBinder 的单例模式,只看看单例部分,其他代码省略。

public final class StaticLoggerBinder implements LoggerFactoryBinder {
    // 单例模式,在类内进行实例化
    private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
    // 声明抽象类
    private final ILoggerFactory loggerFactory;
    // 将构造方法设置为私有 private,防止被外部实例化,保证整个环境中只有一个
    private StaticLoggerBinder() {
        // 实例化具体日志框架工厂
        loggerFactory = new Log4jLoggerFactory();
    }

    public static StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }
    // ...
}

四、Log4jLoggerFactory

Log4jLoggerFactory 位于 log4j-slf4j-impl 包中,是 slf4j-apiILoggerFactory 的实现。

public class Log4jLoggerFactory extends AbstractLoggerAdapter<Logger> implements ILoggerFactory {
    private static final String FQCN = Log4jLoggerFactory.class.getName();
    private static final String PACKAGE = "org.slf4j"; 
    @Override
    protected LoggerContext getContext() {
        final Class<?> anchor = StackLocatorUtil.getCallerClass(FQCN, PACKAGE);
        // 调用 log4j-api 中的 LogManager 进行 log4j 2 的初始化
        return anchor == null ? LogManager.getContext() : getContext(StackLocatorUtil.getCallerClass(anchor));
    }
    // 省略部分源码
    // ...
}

五、获取 logger

加入适配层之后,获取 logger 的位置在 log4j-apiAbstractLoggerAdapter 类。

public L getLogger(String name) {
    // 拿到 log4j 2 的 context
    LoggerContext context = this.getContext();
    // 从 context 中拿到存放所有 logger 的 map
    ConcurrentMap<String, L> loggers = this.getLoggersInContext(context);
    // 根据名字取 logger
    L logger = loggers.get(name);
    // 若存在,直接返回
    if (logger != null) {
        return logger;
    } else {
        // 不存在,实例化后放入 map 后取出返回
        loggers.putIfAbsent(name, this.newLogger(name, context));
        return loggers.get(name);
    }
}

六、调用logger.info("hello,{}","world")

打印日志要经过如下流程:

// 抽象接口调用:slf4j-api Logger.java
public void info(String format, Object arg);
// 适配层:log4j-slf4j-impl Log4jjLogger.java
public void info(final String format, final Object o) {
    logger.logIfEnabled(FQCN, Level.INFO, null, format, o);
}
// 日志框架层:log4j-api ExtendedLogger.java
// 将走入 log4j 2 的核心打印逻辑
void logIfEnabled(String var1, Level var2, Marker var3, String var4, Object var5);

七、小结

SLF4J 统一了大部分的日志框架,提供了通用的接口,通过这个框架,用户可以在不同的日志框架之间轻松的完成框架转换。

八、相关资料

  1. SLF4J 官方介绍
  2. SLF4J 源码

以上是关于SLF4J 源码阅读的主要内容,如果未能解决你的问题,请参考以下文章

Python代码阅读(第19篇):合并多个字典

Python代码阅读(第26篇):将列表映射成字典

如何进行 Java 代码阅读分析?

Python代码阅读(第41篇):矩阵转置

Python代码阅读(第25篇):将多行字符串拆分成列表

JDK源码阅读之 HashMap