SLF4J 源码阅读
Posted stone365
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SLF4J 源码阅读相关的知识,希望对你有一定的参考价值。
一、写在开头
SLF4J
是各类流行日志框架的抽象接口层,它为 logback
、log4j
等日志框架提供了统一的接口,可以方便的在工程内进行日志框架的更换,这个更换的前提是工程首先使用了 SLF4J
的日志接口打印信息,在更换时,只需引入新的日志框架和适配层即可完成更换,而不需要到处去修改打印接口。
在工程中已经直接使用日志框架,如何切换到 SLF4J
接口?
SLF4J
官方有详细的介绍,这里只做简要的概括,更加详细的情况可以参考官网。
这里分为两种情况。第一种,工程使用了 SLF4J
的原生实现,如 logback
,logback
直接实现了 SLL4J
的 API
,所以使用 logback
日志框架的工程无需额外操作;
第二种,使用 log4j
,SLF4J
的出现晚于 log4j
,为了也能统一、兼容先前出现的日志框架,SLF4J
引入了适配层,每个日志框架都有对应的适配层,SLF4J
通过适配层接口间接的调用具体的日志框架。这种情况切换到 SLF4J
框架时会麻烦一点,1)更改工程中的打印 API
,2)加入对应的适配层。
除了适配层,还有桥接层。
关于桥接层,在 SLF4J
官网也有具体的介绍,迫于篇幅,这里只做简要介绍。
考虑一种情况,X
工程正在使用 SLF4J
搭配 log4j 2
构建日志打印框架,在后续对工程的改动中,需要引入一个基础模块 Y
,这个 Y
模块里使用 commons-logging
日志框架进行日志的打印工作,如何让 Y
模块也统一使用 X
工程的 SLF4J
框架呢?这时需要引入桥接层,桥接层对 commons-logging
日志框架进行改造,在 commons-logging
的 API
中,将具体的调用改为对 SLF4J API
的调用,完成桥接,这个对应的包就是 jcl-over-slf4j
。SLF4J
官方还支持特定的几种桥接模式,对应的包为 xxx-over-xxx.jar
,若未按照官方推荐的桥接模式来实现,可能会引起错误。
SLF4J
在版本上的区别。SLF4J
在 1.8
版本之前,使用静态绑定的方式去寻找日志的实现,所有的适配层都会在这个路径上 org/slf4j/impl/
实现 StaticLoggerBinder.class
类,SLF4J
会找到这个类并完成实例化;
SLF4J
在 1.8
版本之后,使用 ServiceLoader
在适配层来加载对应的 Provider
,再由 Provider
完成具体日志的实例化。
本次的源码阅读基于 1.8
版本之前,通过静态绑定的方式完成日志框架的实例化,阅读版本为 slf4j-api-1.7.26
、log4j-slf4j-impl-2.11.2
、log4j-core-2.11.2
、log4j-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-api
里 ILoggerFactory
的实现。
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-api
包 AbstractLoggerAdapter
类。
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
统一了大部分的日志框架,提供了通用的接口,通过这个框架,用户可以在不同的日志框架之间轻松的完成框架转换。
八、相关资料
- SLF4J 官方介绍
- SLF4J 源码
以上是关于SLF4J 源码阅读的主要内容,如果未能解决你的问题,请参考以下文章