spring5和spring4 日志新特性
使用现象对比
我们看看我们经常使用的框架日志是怎么玩的? 这一段时间一直在研究这个日志相关的东西,这些比较流行的框架如何玩?
我们先看看spring4 和 spring 5运行时的区别:
spring 4
没有增加log4j
增加log4j
spring5.0.9.RELEASE
没有增加log4j
增加log4j
总结: spring4 默认实现jcl 打印日志,增加log4j后便使用log4j打印日志
spring5 默认实现jcl打印日志,但是(jcl已经修改),增加log4j后也不会使用log4j打印日志:
spring5低版本日志是开启的,高版本默认是关闭的(我使用5.3.9这个版本的时候发现的,具体哪个版本开始改的就不知道了没纠结)
if (this.logger.isDebugEnabled()) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Refreshing " + this);
} else {
this.logger.debug("Refreshing " + this.getDisplayName());
}
}
org.apache.commons.logging.LogAdapter.JavaUtilLog#isDebugEnabled
public boolean isDebugEnabled() {
这个值恒为false
return this.logger.isLoggable(Level.FINE);
}
spring4 和 5 依赖关系图
spring4 日志实现原码
org.apache.commons.logging.impl.LogFactoryImpl#discoverLogImplementation
真正的log方法创建入口:
private Log discoverLogImplementation(String logCategory) throws LogConfigurationException {
if (isDiagnosticsEnabled()) {
this.logDiagnostic("Discovering a Log implementation...");
}
this.initConfiguration();
Log result = null;
String specifiedLogClassName = this.findUserSpecifiedLogClassName();
if (specifiedLogClassName != null) {
if (isDiagnosticsEnabled()) {
this.logDiagnostic("Attempting to load user-specified log class '" + specifiedLogClassName + "'...");
}
result = this.createLogFromClass(specifiedLogClassName, logCategory, true);
if (result == null) {
StringBuffer messageBuffer = new StringBuffer("User-specified log class '");
messageBuffer.append(specifiedLogClassName);
messageBuffer.append("' cannot be found or is not useable.");
this.informUponSimilarName(messageBuffer, specifiedLogClassName, "org.apache.commons.logging.impl.Log4JLogger");
this.informUponSimilarName(messageBuffer, specifiedLogClassName, "org.apache.commons.logging.impl.Jdk14Logger");
this.informUponSimilarName(messageBuffer, specifiedLogClassName, "org.apache.commons.logging.impl.Jdk13LumberjackLogger");
this.informUponSimilarName(messageBuffer, specifiedLogClassName, "org.apache.commons.logging.impl.SimpleLog");
throw new LogConfigurationException(messageBuffer.toString());
} else {
return result;
}
} else {
if (isDiagnosticsEnabled()) {
this.logDiagnostic("No user-specified Log implementation; performing discovery using the standard supported logging implementations...");
}
for(int i = 0; i < classesToDiscover.length && result == null; ++i) {
result = this.createLogFromClass(classesToDiscover[i], logCategory, true);
}
if (result == null) {
throw new LogConfigurationException("No suitable Log implementation");
} else {
return result;
}
}
}
for(int i = 0; i < classesToDiscover.length && result == null; ++i) {
result = this.createLogFromClass(classesToDiscover[i], logCategory, true);
}
private static final String[] classesToDiscover = new String[]{"org.apache.commons.logging.impl.Log4JLogger", "org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"};
从上面的这个数组我们可以看出来,如果没有引入 org.apache.commons.logging.impl.Log4JLogger 就默认使用 org.apache.commons.logging.impl.Jdk14Logger 这个是jdk自带的,我们的java代码都是使用的jdk所以后面的基本不会使用,
由此可以看出spring4 我们引入log4i就会被加载
通过分析jcl的代码可以得到:
jcl本身不实现日志记录,但是提供了记录日志的抽象方法即接口(info,debug,error…)
底层通过一个数组存放具体的日志框架的类名,然后循环数组依次去匹配这些类名是否在项目中被依赖了,如果找到被依赖的则直接使用,所以他有先后顺序。
下图为jcl中存放日志技术类名的数组,默认有四个,后面两个可以忽略。
jcl特点:
他不直接记录日志,他是通过第三方记录日志(jul)。
jcl是一个接口,默认有4个log实现类。
spring5 源码
Spring4当中依赖jcl,即Spring4当中采用的日志技术是jcl:commons-logging,即默认使用jul;加入log4j依赖和配置,即可切换为log4j
Spring5当中也是使用了jcl:spring-jcl,是重写为了jul框架。spring5使用的spring的jcl(spring改了jcl的代码)来记录日志的,但是jcl不能直接记录日志,采用循环优先的原则。
public static Log getLog(Class<?> clazz) {
return getLog(clazz.getName());
}
org.apache.commons.logging.LogFactory#getLog(java.lang.String)
//name 是使用日志的类名,我们再找一下logApi 这个变量的初始化
public static Log getLog(String name) {
switch(logApi) {
case LOG4J:
return LogFactory.Log4jDelegate.createLog(name);
case SLF4J_LAL:
return LogFactory.Slf4jDelegate.createLocationAwareLog(name);
case SLF4J:
return LogFactory.Slf4jDelegate.createLog(name);
default:
return LogFactory.JavaUtilDelegate.createLog(name);
}
}
private static LogFactory.LogApi logApi;
从下面的代码我们可以看出,spring5开始不再支持log4j而是直接支持log4j2
static {
logApi = LogFactory.LogApi.JUL;
ClassLoader cl = LogFactory.class.getClassLoader();
//第一步找log4j2
//第二步找slf4j
//第三步使用默认的jcl
try {
cl.loadClass("org.apache.logging.log4j.spi.ExtendedLogger");
logApi = LogFactory.LogApi.LOG4J;
} catch (ClassNotFoundException var6) {
try {
cl.loadClass("org.slf4j.spi.LocationAwareLogger");
logApi = LogFactory.LogApi.SLF4J_LAL;
} catch (ClassNotFoundException var5) {
try {
cl.loadClass("org.slf4j.Logger");
logApi = LogFactory.LogApi.SLF4J;
} catch (ClassNotFoundException var4) {
}
}
}
}
从上面spring5的源码可以看到,spring5使用的日志是spring-jcl,默认是jul,然后会依次加载log4j2,slf4j。在都加载不到的情况下,就使用默认的jul日志技术了。
因为spring5使用的是log4j2,所以在加入了log4j的依赖和配置文件,是不生效的。
这样我们就可以想想一下,目前不是spring5不支持log4j,但是我们的项目就是用的log4j又怎么办,
使用桥接器:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.9.RELEASE</version>
</dependency>
<!--slf4j依赖(可省略)-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<!--slf4jbind绑定器,将slf4j绑定到log4j(已经包含了slf4j和log4j的依赖)-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.32</version>
</dependency>
</dependencies>
这样就实现了使用log4j;