前言:由于篇幅所限,关于日志的基础内容请看我的这篇文章 【Java杂记】日志:Java日志体系,从应用场景到发展历史…
Spring 底层选择的是 JCL做为日志门面 + JUL 作为日志实现,而 SpringBoot 选择的是 SLF4J 做为日志门面 + Logback 作为日志实现。那我们本篇就来看看在 Spring 中到底可以如何组合使用各种日志框架+日志实现。
1.spring 底层日志框架 JCL+ spring 底层的日志实现 JUL
首先,引入 spring-context 依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
然后我们简单写一个测试类,目的是观察系统日志和应用日志
public class MainClass {
// import java.util.logging.Logger;
private static Logger logger = Logger.getLogger(MainClass.class.getName());
public static void main(String[] args) {
// 打印系统日志
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
// 打印应用日志
logger.info("let's go");
context.start();
}
}
下图是控制台打印的日志:
![](https://i-blog.csdnimg.cn/blog_migrate/7d0ee6f50ec1f0d331cc7a731e28a649.png)
2.spring 底层日志框架 JCL + 引入并配置 Log4j
首先引入 log4j 的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
<!-- 引入log4j的依赖后,日志框架在寻找实现时就会优先使用log4j而不是jul -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
测试的代码不用变,因为这里我们使用的日志框架还是 JCL,只不过 Logger 的变成了 log4j 的
public class MainClass {
// import org.apache.log4j.Logger;
// 如果这里还是用 JUL 的话,打印出来的用户应用日志还是上图红色的
private static Logger logger = Logger.getLogger(MainClass.class.getName());
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
logger.info("let's go");
context.start();
}
}
这里还一定注意,我们必须在resource目录下创建一个 log4j.properties 去对 log4j 进行初始配置,如果没有的话,Log4j 就无法正常打印日志。
PS:Log4j 启动时会自动去resource目录寻找名为 log4j 的配置文件并加载,相当于一种约定。
#定义LOG输出级别
#CONSOLE 和 FILE 是日志输出地方(也可以取别的名字,但必须大写)
log4j.rootLogger=INFO, CONSOLE, FILE
#定义日志输出目的地为控制台(显示方式:控制台,普通方式)
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
#可以灵活的指定日志输出格式,下面一行是指定具体的格式
log4j.appender.CONSOLE.layout.ConversionPattern=[%p] %d{yyyy-MM-dd HH:mm:ss} - %m%n
log4j.appender.FILE = org.apache.log4j.FileAppender
log4j.appender.FILE.layout = org.apache.log4j.PatternLayout
log4j.appender.FILE.Append = true
log4j.appender.FILE.File=log/info.log
log4j.appender.FILE.layout.ConversionPattern=[%p] %d{yyyy-MM-dd HH:mm:ss} - %m%n
#显示SQL语句日志配置(info不显示,debug显示执行的sql语句)
log4j.logger.com.xupt.yzh=info
日志打印结果如下
![](https://i-blog.csdnimg.cn/blog_migrate/6cc7a0cf500033a9407d1f1d990cc7c4.png)
3.引入日志框架 Slf4j + 引入并配置 Log4j
首先引入 slf4j 及 log4j 的相关依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
<!-- log4j的日志实现 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- slf4j日志门面 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.28</version>
</dependency>
<!-- 该包是转换包,实现slf4j-api接口,调用log4j的实现 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
因为早期 log4j 压根不知道有一slf4j 的日志门面,所以加入适配器 slf4j-log412.jar 实现 slf4j 的接口,真正实现功能调用。
我们的 log4j.jar 测试代码需要在创建日志 Logger 那变一下,因为日志门面这个中间层从 jcl 变成 slf4j 了
public class MainClass {
// import org.slf4j.Logger;
// import org.slf4j.LoggerFactory;
// slf4j 是通过 LoggerFactory 去获取 Logger
private static Logger logger = LoggerFactory.getLogger(MainClass.class.getName());
// private static Logger logger = Logger.getLogger(MainClass.class.getName());
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
logger.info("let's go");
context.start();
}
}
因为引入了 Log4j,所以底层调用的还是 log4j,只不过用户使用的日志门面变了
![](https://i-blog.csdnimg.cn/blog_migrate/f647fc3af9c4373333a0783f96512c34.png)
4.引入日志框架 Slf4j + 引入并配置 Logback
还是首先引入 slf4j 和 logback 的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.28</version>
</dependency>
测试代码同上面第三种情况,因为使用的日志门面都是 slf4j
public class MainClass {
// import org.slf4j.Logger;
// import org.slf4j.LoggerFactory;
// slf4j 是通过 LoggerFactory 去获取 Logger
private static Logger logger = LoggerFactory.getLogger(MainClass.class.getName());
// private static Logger logger = Logger.getLogger(MainClass.class.getName());
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
logger.info("let's go");
context.start();
}
}
同 Log4j,这里的 Logback 必须配置。我们在 resource 目下新建一个 logback.xml 去配置日志格式,级别等
同 Log4j,Logback 启动时也会自动在 resource 目录下寻找名为 logback 的配置文件去加载。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 设置如何输出到控制台 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 日志级别 -->
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
日志输出结果如下
问题:如果我同时引入了 Log4j 和 Logback,那 slf4j 到底会调用哪个日志实现?log4j > logback > jul
5.Spring4.x 底层使用的日志技术
我们从这里可以看出来,spring4.x 获取的日志对象中,Logger 对象是 JCL 的 ,而他底层搭配的技术点就是先去找 Log4j的日志实现,若没有找到,底层去找 jdk 的日志框架,压根不支持 Logback,Log4j2 的日志技术。
6.Spring5.x 底层使用的日志技术
private static LogApi logApi = LogApi.JUL;
static {
ClassLoader cl = LogFactory.class.getClassLoader();
try {
// 第一步:先尝试去加载 log4j2的日志框架
cl.loadClass("org.apache.logging.log4j.spi.ExtendedLogger");
logApi = LogApi.LOG4J;
}
catch (ClassNotFoundException ex1) {
try {
// 第二步:尝试去加载 LogApi.SLF4J_LAL
cl.loadClass("org.slf4j.spi.LocationAwareLogger");
logApi = LogApi.SLF4J_LAL;
}
catch (ClassNotFoundException ex2) {
try {
// 尝试去加载slf4j的日志实现
cl.loadClass("org.slf4j.Logger");
logApi = LogApi.SLF4J;
}
catch (ClassNotFoundException ex3) {
// 使用原生的JUL
}
}
}
}