首先得搞清楚什么是日志框架
JUL(java.util.logging),JCL(Jakarta Commons Logging,spring框架内部使用),Log4j,Log4j2,Logback(具体框架,springboot使用)、SLF4j、jboss-logging等。
其中又可以分为
日志门面(提供日志接口) | 日志实现(具体) |
---|---|
JCLSLF4jJboss-logging | JULlog4jlog4j2logback |
Log4j,Logback,SLF4j都是同一个作者,所以logback可以不需要适配包就可以直接配合SLF4j来使用,log4j出现比较早,所以需要适配包slf4j-log4j12来搭配使用。
SLF4j作为现在最好用的日志门面当然由它的过人之处,他可以完美地通过桥接包或者适配包,解决日志冲突和快速便捷地切换日志实现,
从这张图可以看出slf4j-simple、logback都是slf4j的具体实现,log4j和jcl并不直接实现slf4j,但是有专门的一层桥接slf4j-log4j12和slf4j-jdk14来实现slf4j,最终输出log4j和jcl的日志
但是从开始就可以看到一个项目中不可能只有一种日志,势必会引起各种日志冲突这时候slf4j也有相应的解决方法
从第一个例子可以看出如果最终直接使用slf4j和logback的组合(当然这是最好的组合),就可以加入jcl-over-slf4j.jar ,log4j-over-slf4j-jar,jul-to-slf4j.jar等包来替代原来的log4j和jcl,jul的日志jar包,这就相当于狸猫换太子(有了狸猫就不需要太子了,也就是原来的log4j或者其他日志的核心包就不用加入了)
例如log4j,,按照log4j库的目录放置几个相关类(比如Logger等),这样系统中使用log4j的代码编译就不会出错,但是这些代理logger内部实现时却将日志悄悄代理到了SLF4J相关接口,最终通过logback来实现
第二,三个例子无非就是依靠slf4j整合各种日志接口,通过适配包来使用log4j或者jcl来实现日志输出,就不细说了
接下来就是看slf4j的具体实现过程
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>slf4j的日志实现类
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>这个也是slf4j的日志实现log4j形式
<version>1.7.21</version>
</dependency>
加两个实现类是为了下面的测试,不要学,一个日志实现就够了
依赖引入如上
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Test01 {
@Test
public static void test() {
Logger log= LoggerFactory.getLogger(Test01.class);
log.debug("debug消息id={},{}",1,"2emmmm");//支持占位符
log.info("info");
log.warn("warn");
log.error("error");
}
}
一段java程序,这时候有小伙伴就会问了,slf4j不是日志门面么,不是抽象么.不是没有具体的实现么,为什么日志输出的时候不用引入其他包???,(这也是当时困扰了我好久,怀疑门面日志的定义),那就先来说说门面日志是什么意思
slf4j是门面模式的典型应用,因此在讲slf4j前,我们先简单回顾一下门面模式,
门面模式,其核心为外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用。用一张图来表示门面模式的结构为:
门面模式的核心为Facade即门面对象,门面对象核心为几个点:
-
知道所有子角色的功能和责任
-
将客户端发来的请求委派到子系统中,没有实际业务逻辑
-
不参与子系统内业务逻辑的实现
说人话就是slf4j只是一个日志标准,并不是日志系统的具体实现。
所以slf4j只做两件事情
-
提供日志接口
-
提供获取具体日志实现对象的方法
既然slf4j提供获取日志实现对象的方法,那我们就跟着运行一下吧,看看具体怎么获取
运行上面的测试后
第一步 public static Logger getLogger(Class clazz) {
return getLogger(clazz.getName());
}
第二步
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
第三步
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == 0) {
INITIALIZATION_STATE = 1;
performInitialization();
}
//前面都是在初始化没什么价值bind才是关键
第四步
private static final void performInitialization() {
bind();
if (INITIALIZATION_STATE == 3) {
versionSanityCheck();
}
}
前面都是在初始化没什么价值bind才是关键
进入bind里面后
第五步
private static final void bind() {
String msg;
try {
Set staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
//首先运行findPossibleStaticLoggerBinderPathSet
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = 3;
reportActualBinding(staticLoggerBinderPathSet);
fixSubstitutedLoggers();
} catch (NoClassDefFoundError var2) {
msg = var2.getMessage();
if (!messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
failedBinding(var2);
throw var2;
}
INITIALIZATION_STATE = 4;
Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.");
} catch (NoSuchMethodError var3) {
msg = var3.getMessage();
if (msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {
INITIALIZATION_STATE = 2;
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 var3;
} catch (Exception var4) {
failedBinding(var4);
throw new IllegalStateException("Unexpected initialization failure", var4);
}
}
先运行 findPossibleStaticLoggerBinderPathSet();
第六步
findPossibleStaticLoggerBinderPathSet()方法其实就是通过classloader的getResources()方法找到所有的名为”org/slf4j/impl/StaticLoggerBinder.class”的resource。
private static Set findPossibleStaticLoggerBinderPathSet() {
LinkedHashSet staticLoggerBinderPathSet = new LinkedHashSet();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
//获取类加载器
Enumeration paths;
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
// private static String STATIC_LOGGER_BINDER_PATH =
//"org/slf4j/impl/StaticLoggerBinder.class";是当前类中已经定义的静态变量
//
} else {
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
while(paths.hasMoreElements()) {
URL path = (URL)paths.nextElement();
staticLoggerBinderPathSet.add(path);
//这里就是往set集合添加地址
}
} catch (IOException var4) {
Util.report("Error getting resources from path", var4);
}
return staticLoggerBinderPathSet;//最后返回set集合
}
findPossibleStaticLoggerBinderPathSet()方法其实就是通过classloader的getResources()方法找到静态变量名为STATIC_LOGGER_BINDER_PATH 所代表的 org/slf4j/impl/StaticLoggerBinder.class默认的logger地址
这时候回来看看slf4j的实现类
以上就是logback和log4j形式的slf4j的实现包都有这个StaticLoggerBinder这个类也就是说所有的slf4j的实现包都有这个类
所以如果项目中有多个slf4j的实现类set的集合就不止一条数据
接下来退出 findPossibleStaticLoggerBinderPathSet();
运行 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
// 第七步
private static boolean isAmbiguousStaticLoggerBinderPathSet(Set staticLoggerBinderPathSet) {
return staticLoggerBinderPathSet.size() > 1;
}
private static void reportMultipleBindingAmbiguity(Set staticLoggerBinderPathSet) {
//如果set集合大于1个以上就会打印绑定前后的信息否则不答应直接跳出
if (isAmbiguousStaticLoggerBinderPathSet(staticLoggerBinderPathSet)) {
Util.report("Class path contains multiple SLF4J bindings.");
Iterator iterator = staticLoggerBinderPathSet.iterator();
while(iterator.hasNext()) {
URL path = (URL)iterator.next();
Util.report("Found binding in [" + path + "]");
}
Util.report("See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.");
}
}
可以看出这个方法先是判断我们的set集合是否大于1如果大于就打印出这个警告信息
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/D:/mvnrepository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/D:/mvnrepository/org/slf4j/slf4j-log4j12/1.7.21/slf4j-log4j12-1.7.21.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 [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
也就是提示我们同时引入了多个slf4j的实现,然后选择其中的一个作为我们使用的日志系统(这里只是演示提示信息,最好只使用一个slf4j的日志实现,所以多个slf4j的实现类不会报错只是会提示警告而已)
private final static void bind() {
......
StaticLoggerBinder.getSingleton();
//也就是说最后执行绑定任务的是这行代码
}
但是这个StaticLoggerBinder类 slf4j-api本身是没有的,点进去就是logback包的org.slf4j.impl.StaticLoggerBinder也就是通过上面的步骤选择一个org/slf4j/impl/StaticLoggerBinder.class再实现具体的日志输出
参考博客:https://www.cnblogs.com/xrq730/p/8619156.html