源码解读SLF4J绑定日志实现的原理
一、导读
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Users/abc/maven-repository/org/slf4j/slf4j-simple/1.7.26/slf4j-simple-1.7.26.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/abc/maven-repository/org/apache/logging/log4j/log4j-slf4j-impl/2.7/log4j-slf4j-impl-2.7.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]

也就是说,当有多个日志实现时,SLF4J会在编译期随机选择其中的一个实现。那么,它到底是如何随机选择的呢?下面我们通过源码来分析。
二、源码分析
2.1 LoggerFactory.getLogger(this.getClass())
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.2 LoggerFactory.bind()
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()) {
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); //在classpath下查找有多少个日志实现
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); //如果有多个日志实现,打印出来
}
// the next line does the binding。classpath下每个日志实现jar都会有org.slf4j.impl.StaticLoggerBinder类,这里会随机加载其中的一个。
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
fixSubstituteLoggers();
replayEvents();
// release all resources in SUBST_FACTORY
SUBST_FACTORY.clear();
} catch (NoClassDefFoundError ncde) { //如果在classpath下没有找到一个org.slf4j.impl.StaticLoggerBinder
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) {
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);
}
}
2.3 LoggerFactory.findPossibleStaticLoggerBinderPathSet()
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;
if (loggerFactoryClassLoader == null) { //用ClassLoader去查找classpath下有多少个org.slf4j.impl.StaticLoggerBinder类
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
while (paths.hasMoreElements()) {
URL path = paths.nextElement();
staticLoggerBinderPathSet.add(path);
}
} catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}
三、具体日志实现
3.1 slf4j-simple

3.2 slf4j-nop
3.3 logback-classic
3.4 log4j-slf4j-impl
四、总结
- 每个日志实现jar都会有org.slf4j.impl.StaticLoggerBinder类的实现;
- SLF4J会通过ClassLoad扫描当前classpath下有多少个org.slf4j.impl.StaticLoggerBinder类,也就找到了有多少个日志实现(通过这样,我们只需在项目中加入日志实现的jar包,编译时即可自动加载,业务代码无须显式依赖,实现解耦);
- 如果有多个org.slf4j.impl.StaticLoggerBinder类,SLF4J会在LoggerFactory.bind()里调用StaticLoggerBinder.getSingleton()随机加载一个日志实现jar的StaticLoggerBinder。
- 发现一个有趣的现象,在本地idea上运行时,是加载pom.xml里声明的第一个日志实现;但是如果打包好后通过java -jar启动时,其加载的日志实现确实是随机的(是在编译打包时随机加载一个日志实现,所以一旦编译打包好后其加载的那个日志实现就会固定不变)。所以,我们在具体使用时一定要通过排除依赖的方式来确定日志实现,不要由于日志实现的不确定性引入难以排查、不必要的坑。
源码解读SLF4J绑定日志实现的原理的更多相关文章
- Vue 源码解读(3)—— 响应式原理
前言 上一篇文章 Vue 源码解读(2)-- Vue 初始化过程 详细讲解了 Vue 的初始化过程,明白了 new Vue(options) 都做了什么,其中关于 数据响应式 的实现用一句话简单的带过 ...
- petite-vue源码剖析-双向绑定`v-model`的工作原理
前言 双向绑定v-model不仅仅是对可编辑HTML元素(select, input, textarea和附带[contenteditable=true])同时附加v-bind和v-on,而且还能利用 ...
- 源码解读 Golang 的 sync.Map 实现原理
简介 Go 的内建 map 是不支持并发写操作的,原因是 map 写操作不是并发安全的,当你尝试多个 Goroutine 操作同一个 map,会产生报错:fatal error: concurrent ...
- petite-vue源码剖析-属性绑定`v-bind`的工作原理
关于指令(directive) 属性绑定.事件绑定和v-modal底层都是通过指令(directive)实现的,那么什么是指令呢?我们一起看看Directive的定义吧. //文件 ./src/dir ...
- petite-vue源码剖析-事件绑定`v-on`的工作原理
在书写petite-vue和Vue最舒服的莫过于通过@click绑定事件,而且在移除元素时框架会帮我们自动解除绑定.省去了过去通过jQuery的累赘.而事件绑定在petite-vue中就是一个指令(d ...
- nodeJS之eventproxy源码解读
1.源码缩影 !(function (name, definition) { var hasDefine = typeof define === 'function', //检查上下文环境是否为AMD ...
- ScheduledThreadPoolExecutor源码解读
1. 背景 在之前的博文--ThreadPoolExecutor源码解读已经对ThreadPoolExecutor的实现原理与源码进行了分析.ScheduledExecutorService也是我们在 ...
- Vue 源码解读(4)—— 异步更新
前言 上一篇的 Vue 源码解读(3)-- 响应式原理 说到通过 Object.defineProperty 为对象的每个 key 设置 getter.setter,从而拦截对数据的访问和设置. 当对 ...
- Asp.NetCore源码学习[2-1]:日志
Asp.NetCore源码学习[2-1]:日志 在一个系统中,日志是不可或缺的部分.对于.net而言有许多成熟的日志框架,包括Log4Net.NLog.Serilog 等等.你可以在系统中直接使用这些 ...
随机推荐
- sitecore 如何创建一个渠道分类
您可以通过渠道跟踪联系人与您的品牌的所有互动.您可以将渠道与广告系列活动相关联,以便跟踪联系人与您的品牌互动的方式.通过比较各个渠道的目标转化率,您可以了解哪些渠道可以带来更好的联系参与度.您可以在体 ...
- 一个简单的利用 WebClient 异步下载的示例(三)
继续上一篇 一个简单的利用 WebClient 异步下载的示例(二) 后,继续优化它. 1. 直接贴代码了: DownloadEntry: public class DownloadEntry { p ...
- Neo4j 第九篇:查询数据(Match)
Cypher使用match子句查询数据,是Cypher最基本的查询子句.在查询数据时,使用Match子句指定搜索的模式,这是从Neo4j数据库查询数据的最主要的方法.match子句之后通常会跟着whe ...
- JDBC解耦案例
原始JDBC连接 package jdbc; import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sq ...
- python常用库简单使用( PyPDF2 )
PyPDF2学习 1 这个模块的名字对大小写是敏感的,所以,确保y是小写的,其他字母都是大写的
- Eureka获取服务列表源码解析
在之前的文章:EurekaClient自动装配及启动流程解析中,我们提到了在类DiscoveryClient的构造方法中存在一个刷新线程和从服务端拉取注册信息的操作 这两个就是eureka获取服务列表 ...
- 微信小程序 自定义顶部状态栏
1>项目的结构如下: 2>组件的index.wxml代码如下: <!--没有按钮的情况--> <view class="custom flex_center&q ...
- Qt发送邮件
首先下载支持库 https://download.csdn.net/download/zhangxuechao_/10598108 #ifndef MAIL_H #define MAIL_H #inc ...
- ios路线
http://www.cocoachina.com/ios/20150303/11218.html
- mybatis中的分页插件
1.Mybatis的分页plugin实现原理 2.具体步骤 第一步.导入到pom.xml文件中依赖包 第二步.配置插件(必需) 在mybatisConfig.xml文件中配置以下代码 代码位置:在en ...