本文目的
1、Spring5.x单纯使用spring-jcl打印日志是什么输出格式(JUL)
2、Spring5.x如何让spring-jcl用log4j2输出,直接加入log4j2依赖[调皮]
3、Spring5.x加入log4j2又加了slf4j+logback,都spring-jcl的api打印日志,为什么都是log4j2输出
4、Spring5.x加入log4j2又加了slf4j+logback,分别用jcl和slf4j的api打印日志,为什么日志输出格式不统一
5、如何做到和SpringBoot一样统一日志输出格式(slf4j+桥接器)
准备
Spring版本号:5.2.19.RELEASE
知识储备:1、需要了解slf4j、log4j2、logback;2、什么是slf4j的绑定器;3、什么是slf4j的桥接器
说明
该模块是对输出的日志框架进行适配,是从Apache的commons-logging改造而来
Spring更喜爱log4j(log4j2),用spring-jcl这个来表明
Spring5.x开始自己实现了日志框架的适配器在spring-jcl模块下
开始
spring5.x现象:
1、自定义工程,加入spring-context依赖,使用spring-jcl下的LogFactory
package com.csh.spring.log;
import org.apache.commons.logging.LogFactory;
//import org.slf4j.LoggerFactory;
public class Test {
public static void main(String[] args) {
//spring-jcl api
LogFactory.getLog(Test.class).info("log4j2 say hello world!!");
// //slf4j api
// LoggerFactory.getLogger(Test.class).info("logback say hello world!!");
}
}
2、没有log4j2依赖,执行代码,打印如下
3、加入log4j2依赖
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.2</version>
</dependency>
4、添加logback依赖和在resources中logback.xml配置
我在logback设置了时间-线程高亮,日志级别绿色 ,log4j2是黑底白字
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
a、都是用jcl的api打印日志
package com.csh.spring.log;
import org.apache.commons.logging.LogFactory;
import org.slf4j.LoggerFactory;
public class Test {
public static void main(String[] args) {
//spring-jcl api
LogFactory.getLog(Test.class).info("log4j2 say hello world!!");
LogFactory.getLog(Test.class).error("log4j2 2 say hello world!!");
// //slf4j api
// LoggerFactory.getLogger(Test.class).info("logback say hello world!!");
}
}
b、分别使用slf4和spring-jcl的japi打印日志
package com.csh.spring.log;
import org.apache.commons.logging.LogFactory;
import org.slf4j.LoggerFactory;
public class Test {
public static void main(String[] args) {
//spring-jcl api
LogFactory.getLog(Test.class).info("log4j2 say hello world!!");
// //slf4j api
LoggerFactory.getLogger(Test.class).info("logback say hello world!!");
}
}
刷新依赖重新执行,发现两种日志输出格式,spring-jcl使用的是log4j2而slf4j是logback
得出问题
1、spring-jcl,单纯自己默认输出框架是JUL
2、加入logback绑定器,都用spring-jcl的api打印,为什么是用log4j2输出
3、加入logback绑定器,新加入slf4j的api打印日志输出是用logback而原来用spring-jcl还是会用log4j2输出,日志不统一,怎么解决
源码分析
问题1
spring-jcl,单纯自己默认输出框架是JUL
org.apache.commons.logging.LogFactory#getLog(java.lang.String)
public static Log getLog(String name) {
return LogAdapter.createLog(name);
}
org.apache.commons.logging.LogAdapter#createLog
logApi是类LogAdapter静态成员变量,类加载时静态代码块赋值,通过下面源码分析知道,首先判断是否有log4j2,若没有则判断是否有slf4j,都没有所以走了else,也就是JUL
private static final LogApi logApi;
static {
//判断是否有log4j2
if (isPresent(LOG4J_SPI)) {
//存在log4j2判断是否引入了log4j2到slf4j的桥接器,且有Slf4j
if (isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)) {
// log4j-to-slf4j bridge -> we'll rather go with the SLF4J SPI;
// however, we still prefer Log4j over the plain SLF4J API since
// the latter does not have location awareness support.
logApi = LogApi.SLF4J_LAL;
}
else {//若是没有log4j2到slf4j的桥接器就还是使用log4j2
// Use Log4j 2.x directly, including location awareness support
logApi = LogApi.LOG4J;
}
}//使用slf4j,和下个判断都是用slf4j,但有位置感应没找到相关资料
else if (isPresent(SLF4J_SPI)) {
// Full SLF4J SPI including location awareness support
logApi = LogApi.SLF4J_LAL;
}
else if (isPresent(SLF4J_API)) {
// Minimal SLF4J API without location awareness support
logApi = LogApi.SLF4J;
}
else {//JDK自带的
// java.util.logging as default
logApi = LogApi.JUL;
}
}
/**
* Create an actual {@link Log} instance for the selected API.
* @param name the logger name
*/
public static Log createLog(String name) {
switch (logApi) {
case LOG4J:
return Log4jAdapter.createLog(name);
case SLF4J_LAL:
return Slf4jAdapter.createLocationAwareLog(name);
case SLF4J:
return Slf4jAdapter.createLog(name);
default:
// Defensively use lazy-initializing adapter class here as well since the
// java.logging module is not present by default on JDK 9. We are requiring
// its presence if neither Log4j nor SLF4J is available; however, in the
// case of Log4j or SLF4J, we are trying to prevent early initialization
// of the JavaUtilLog adapter - e.g. by a JVM in debug mode - when eagerly
// trying to parse the bytecode for all the cases of this switch clause.
return JavaUtilAdapter.createLog(name);
}
}
问题2
加入logback绑定器,都用jcl的api打印,为什么是用log4j2输出
通过问题1中分析的源码发现有了log4j2还会进入判断,isPresent(LOG4J_SLF4J_PROVIDER)必须为true才会判断是否引入了slf4j,这个要成立就需要用到log4j2到slf4j的桥接器【(开头提到的知识储备),所以加入对应桥接器即可,你会发现slf4j官网只有提到log4j而没有log4j2桥接器,这需要到log4j2官网才有提及到Log4j to SLF4J Adapter也提到绑定器和桥接不能同时存在否则会有问题】,但没有引入xxx到slf4j的桥接器所以该条件不成立,所以还是使用log4j2的实现。也就是为什么只用log4j2,由此看出来spring更偏向log4j
问题3
加入logback绑定器,新加入slf4j的api打印日志输出是用logback而原来用spring-jcl还是会用log4j2输出,日志不统一
上面问题2解答提及到的,isPresent(LOG4J_SLF4J_PROVIDER)为true时才会改变spring-jcl的日志输出框架,所以加入log4j2到slf4j的桥接器,该条件就成立了使用spring-jcl的api也能通过slf4j转到logback
<!--log4j2 到slfj的桥接器 log4j桥接器是log4j-over-slf4j-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.17.2</version>
</dependency>
刷新依赖重新执行,bingo!!这时两种日志输出格式统一了。
彩蛋
1、Spring4.x也一样会出现日志不统一,一样加桥接器即可。
2、桥接器不能和同一个日志框架的绑定器同时存在否则异常 log4j-slf4j-impl cannot be present with log4j-to-slf4j,绑定器会校验是否存在桥接器的某个类[org.apache.logging.slf4j.SLF4JLoggerContext]
3、不能同时出现多个绑定器,存在多个会提示,按所有绑定器依赖的第一作为绑定器
log4j2的依赖放在前面都是log4j2输出的,logback放前面就都是logback