1、Java 日志框架
问题:
- 控制日志输出的内容和格式
- 控制日志输出的位置
- 面向接口开发 —— 日志的门面(类似 JDBC)
1.1、现有的日志框架
1.1.1、日志门面
JCL、slf4j
1.1.2、日志实现
JUL、logback、log4j、log4j2
2、JUL
JUL 全称是 java util logging ,是 java 原生的日志框架,所以不需要导入任何依赖。
2.1、架构介绍
- Logger:记录器,应用程序通过获取 Logger 对象,调用它的 API 来发布日志信息。
- Appender:也叫 Handler,每个 Logger 都会关联一组 Handler,Handler 负责对日志做记录,它的实现决定了日志保存的位置(控制台、文件、网络上的其他日志服务)。
- Layout:负责日志的格式化
2.2、快速入门
package com.lyh;import org.junit.Test;import java.util.logging.Level;
import java.util.logging.Logger;public class JULTest {@Testpublic void testQuick(){// 1. 获取日志记录器对象Logger logger = Logger.getLogger("com.lyh.JULTest");// 2. 日志记录的输出logger.info("hello jul"); // 信息: hello jul// 或者可以使用通用方法进行日志记录logger.log(Level.INFO,"info msg"); // 信息: info msg// 通过占位符输出变量值String name = "jul";logger.log(Level.INFO,"hello,{0}",name); // 信息: hello,jul}}
2.3、JUL日志级别
jul 默认的日志级别是 info,只有 >= info 的才会输出
@Testpublic void testLogLevel(){// 1. 获取诶之记录器对象Logger logger = Logger.getLogger("com.lyh.JULTest");// 2. 日志记录输出logger.severe("severe");logger.warning("warn");logger.info("info"); // jul 默认的日志级别是 info,只有 >= info 的才会输出logger.config("config");logger.fine("fine");logger.finer("finer");logger.finest("finest");}
运行结果:
2.4、自定义日志级别
自定义一个日志级别,要求输出所有级别的日志记录,而不只是 >= info 级别的
@Testpublic void testLogConfig(){// 1. 获取日志记录器对象Logger logger = Logger.getLogger("com.lyh.JULTest");// 关闭系统默认配置logger.setUseParentHandlers(false);// 创建 ConsoleHandlerConsoleHandler ch = new ConsoleHandler();// 创建简单格式转换对象SimpleFormatter sf = new SimpleFormatter();// 进行关联ch.setFormatter(sf);logger.addHandler(ch);// 配置日志记录的具体级别ch.setLevel(Level.ALL);logger.setLevel(Level.ALL);// 2. 日志记录输出logger.severe("severe");logger.warning("warn");logger.info("info");logger.config("config");logger.fine("fine");logger.finer("finer");logger.finest("finest");}
可以看到,除了 >= info 的,< info 级别的日志信息也被输出出来了
2.5、Logger 对象父子关系
所有日志记录器的顶级父元素都是 LogManager$RootLogger name="",所有日志记录器都会默认继承父元素的配置。所以,除非子类显示声明setUseParentHandlers(false);否则使用父级元素的配置!
区分父子记录器是通过包名来区分的:
@Testpublic void testLogParent(){Logger logger1 = Logger.getLogger("a");Logger logger2 = Logger.getLogger("a.b");// 所有日志记录器的顶级父元素都是 LogManager$RootLogger name=""System.out.println(logger1.getParent().getName()); // ""System.out.println(logger2.getParent().getName()); // a}
2.6、JUL 配置文件
从源码中我们可以看到, LogManager 会尝试读取配置文件,默认读取 jdk 下面的 lib 目录(毕竟 JUL 是 java 内置的)
在 resources 目录下创建 logging.properties:
// 设置顶级父元素(rootLogger)指定的默认处理器(不配置则不生效)
handlers= java.util.logging.ConsoleHandler
// 设置顶级父元素(rootLogger)指定的默认日志级别
.level= ALL// 定义文件处理器的文件名格式(必须在上面的 handlers=xx后面添加该处理器java.util.logging.FileHandler,否则不生效)
java.util.logging.FileHandler.pattern = /logs/java%u.log // 定义控制台处理器日志级别
java.util.logging.ConsoleHandler.level=ALL
// 定义控制台处理器输出格式对象
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
// 定义控制台处理器的日志编码
java.util.logging.ConsoleHandler.encoding=UTF-8
还是那句话:除非子类显示声明setUseParentHandlers(false);否则使用父级元素的配置!
测试:
@Testpublic void testLogProperties() throws IOException {// 读取配置文件InputStream ins = JULTest.class.getClassLoader().getResourceAsStream("logging.properties");// 获得 LogManagerLogManager logManager = LogManager.getLogManager();// 通过 LogManager 加载配置文件logManager.readConfiguration(ins);Logger logger = Logger.getLogger("com.lyh");logger.severe("severe");logger.warning("warn");logger.info("info");logger.config("config");logger.fine("fine");logger.finer("finer");logger.finest("finest");}
可以看到,本该默认是 info 级别的日志记录变成了我们指定的 all :
自定义 Logger
上面我们使用的是默认的 rootLogger (我们代码在 getLogger 的时候虽然因为找不到名为 com.lyh 的配置,所以使用默认的配置,也就是父类元素 rootLogger 的配置),这里我们自定义一个 Logger 名为 "com.lyh"
# rootLogger 配置
handlers= java.util.logging.ConsoleHandler
.level = ALL
# 自定义 Logger
com.lyh.level=WARNING
com.lyh.handlers = java.util.logging.ConsoleHandler
# 不使用 rootLogger 的配置
com.lyh.useParentHandlers = falsejava.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.ConsoleHandler.encoding=UTF-8
java.util.logging.ConsoleHandler.formatter.format=%4$s: %5$s [%1$tc]%n
@Testpublic void testLogProperties() throws IOException {// 读取配置文件InputStream ins = JULTest.class.getClassLoader().getResourceAsStream("logging.properties");// 获得 LogManagerLogManager logManager = LogManager.getLogManager();// 通过 LogManager 加载配置文件logManager.readConfiguration(ins);Logger logger = Logger.getLogger("com.lyh");logger.severe("severe");logger.warning("warn");logger.info("info");logger.config("config");logger.fine("fine");logger.finer("finer");logger.finest("finest");}
可以看到,只输出了级别为 warning 的日志:
3、Log4j
导入依赖:
<dependencies><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency></dependencies>
3.1、快速入门
@Testpublic void TestQuick(){// 获取日志记录器对象Logger logger = Logger.getLogger(Log4jTest.class);// 日志记录输出logger.info("hello log4j");}
log4j 并不会去读取默认的配置,而是需要我们创建配置文件,或者手动使用自带的默认配置:
@Testpublic void TestQuick(){// 初始化配置信息(使用log4j自带的配置文件)BasicConfigurator.configure();// 获取日志记录器对象Logger logger = Logger.getLogger(Log4jTest.class);// 日志记录输出logger.trace("trace"); // 用于追踪程序所有的流程信息logger.debug("debug"); // 开发中使用,用于调试logger.info("info"); // 运行信息,数据连接、网络、IOlogger.warn("warn");logger.error("error");logger.fatal("fatal"); // 严重问题,会导致系统故障}
可以看到,log4j 默认的日志级别是 debug,所以 trace 没有输出出来。
3.2、Log4j 组件和配置文件
Loggers
日志记录器,负责手机处理日志记录,Logger 的名字大小写敏感,并且有继承机制:比如 com.lyh.app 的 logger 会继承 com.lyh 的 logger 配置。
log4j 中有一个特殊的 logger 叫做 root,也就是所有的 logger 都会直接或者间接继承它,可以通过 Logger.getRootLogger 方法来获得。
Appenders
Appender | 作用 |
---|---|
ConsoleAppender | 输出到控制台 |
FileAppender | 输出到文件 |
DailyRollingFileAppender | 每天输出到一个新的文件 |
RollingFileAppender | 细粒度输出到一个文件 |
JDBCAppender | 输出到数据库 |
Layouts
日志的输出格式:
Layouts | 作用 |
---|---|
HTMLLayout | 输出位 html 表格格式 |
SImpleLayout | 简单的格式化,info - message |
PatternLayout | 最强大的格式化 |
配置文件
log4j 支持两种配置文件格式:
编写配置文件(log4j.properties):
log4j.rootLogger = INFO,console
// log4j.appender.logger名称
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout = org.apache.log4j.SimpleLayout
测试:
@Testpublic void TestProperties(){// 获取日志记录器对象Logger logger = Logger.getLogger(Log4jTest.class);// 日志记录输出logger.trace("trace"); // 用于追踪程序所有的流程信息logger.debug("debug"); // 开发中使用,用于调试logger.info("info"); // 运行信息,数据连接、网络、IOlogger.warn("warn");logger.error("error");logger.fatal("fatal"); // 严重问题,会导致系统故障}
运行结果:
可以看到,默认 rootLogger 日志级别本该是 debug ,但是这里被我们的配置文件覆盖为 info:
配置文件解析原理
内置日志记录
我们可以开启 log4j 内置的日志记录来查看它是怎么解析我们的配置文件的:
这里我们先修改我配置文件中的 appender 名称为 console1,防止误解以为 console 是一个特殊的名称,其实它完全可以自定义!
log4j.rootLogger = INFO,console1
log4j.appender.console1=org.apache.log4j.ConsoleAppender
log4j.appender.console1.layout = org.apache.log4j.SimpleLayout
@Testpublic void TestProperties(){// 开启 log4j 内置日志记录LogLog.setInternalDebugging(true);// 获取日志记录器对象Logger logger = Logger.getLogger(Log4jTest.class);// 日志记录输出logger.trace("trace"); // 用于追踪程序所有的流程信息logger.debug("debug"); // 开发中使用,用于调试logger.info("info"); // 运行信息,数据连接、网络、IOlogger.warn("warn");logger.error("error");logger.fatal("fatal"); // 严重问题,会导致系统故障}
运行结果:
使用 PatternLayout 自定义日志输出
PatternLayout 有自己默认的格式:
我们也可以自己指定它的格式:
# 指定了 RootLogger 顶级父元素默认配置信息
# 指定日志级别为 info, appender 为 console1
log4j.rootLogger = INFO,console1
# 指定了我们自定义的 appender 实现类
log4j.appender.console1=org.apache.log4j.ConsoleAppender
# 指定了日志格式
log4j.appender.console1.layout = org.apache.log4j.PatternLayout
测试输出:
可以看到,默认的输出格式非常简单。其实从源码中我们还会发现另一种内置的但默认没有启用的格式:
要指定也很简单,配置文件最终其实调用的是这个方法,我们只需要在配置文件中指定它的参数名即可,它会自动找到该参数的 setter 方法:
测试:
# 指定了 RootLogger 顶级父元素默认配置信息
# 指定日志级别为 info, appender 为 console1
log4j.rootLogger = INFO,console1
# 指定了我们自定义的 appender 实现类
log4j.appender.console1=org.apache.log4j.ConsoleAppender
# 指定了日志格式
log4j.appender.console1.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.console1.layout.conversionPattern = %r [%t] %p %c %x - %m%n
运行结果:
下面我们看一下配置文件的参数分别是什么意思:
配置参数解析
- %m:也就是 message,是我们指定的日志信息(比如 log.info("hello"); 那么 hello 就是这里的 %m)
- %p:代表大写日志级别
- %n:换行符(一般放到最后)
- %r:当前日志输出耗费的毫秒数 ms
- %c:当前所处类的全限定名
- %t:产生该日志的线程全名
- %d:当前服务器的时间,我们可以指定它的格式,比如 %d{yyyy年MM月dd日 HH:mm:ss}
- %l:输出日志时间发生的位置,包括类名、线程以及在代码中的行数。比如 Test.main(Test.java:10)
- %F:输出日志消息产生时所在的文件名称
- %L:输出代码的行号
- %%:输出一个 %
其实,这里的 %c %t %F %L 就等同于 %l
此外,可以在 % 与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对齐方式,比如:
- %5c 输出的最小宽度是5,默认向右对齐
- %-5c 输出的最小宽度是5,并向左对齐
3.3、FileAppender
FileAppender 同样是内置的一个 Appender ,我们查看源码可以看到,它默认有 4 个属性:
log4j.rootLogger = INFO,filelog4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.conversionPattern = %r [%t] [%p] %c %x - %m%n
那怎么指定日志文件位置呢?其实同样是使用 ognl 表达式:
只需要:
log4j.appender.file.file = ./logs/my-log.log
同样,我们可以指定它的编码格式(同样是 ognl 表达式,只不过这个 set 方法在 FileAppender 的父类 WriteAppender 里面):
只需要添加:
log4j.appender.file.encoding = utf-8
3.4、JDBCAppender
用到了再查
3.5、自定义 Logger
在 log4j 中是使用 "log4j.prefix" 注册自定义 Logger 的:
而一般我们使用包名最为自定义 logger 的名称,也就是跟在 log4j.logger 后面的名称:
所以,一般在开发中,我们可以更灵活的指定日志的配置方式,比如下面,我们把默认的配置输出到控制台,把使用我们自定义logger 的日志输出到文件中:
# 默认的 logger 输出到控制台
log4j.rootLogger = INFO,console1# 自定义 logger 输出到文件
log4j.logger.com.lyh = info,file
或者,我们还可以指定某个包下的日志级别的输出位置:
log4j.rootLogger = INFO,console1log4j.logger.com.lyh = info,file# 只配置日志级别,不配置 appender 会使它使用 rootLogger 的 appender
# 使得 org.apache 包下的错误日志使用默认的 logger 输出到控制台
log4j.logger.org.apache = error
4、日志门面
作用:日志框架的实现变了(比如从 log4j 换成了 logback),但是代码不需要修改!
常见的日志门面:JCL(java commons logging,仅支持 JUL 和 loog4j)、Slf4j
常见的日志框架:JUL、log4j、logback(springboot 内部默认的日志框架)、log4j2(功能和 logback 相似,但性能更好)
5、Slf4j
对于一般的 java 项目,一般都会使用 slf4j-api 作为日志门面,配上具体的视线框架(log4j、logback等),中间使用桥接器完成桥接。
下面 pom 中的日志门面依赖 slf4j-api 是必须导入的(所有日志框架要想通过 slf4j 来作为日志门面,必须导入该依赖)!而下面的 slf4j-simple 则是一个 slf4j 自己的一个简单实现。
<!--日志门面--><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.36</version></dependency><!--具体的实现 slf4j 内置的--><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.36</version></dependency>
测试:
package com.lyh;import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class Slf4jTest {public static final Logger log = LoggerFactory.getLogger(Slf4jTest.class);@Testpublic void testQuick(){log.trace("trace");log.debug("debug");log.info("info");log.warn("warn");log.error("error");}
}
输出结果:
5.1、绑定日志
上面我们绑定的只是 slf4j 自己的一个简单实现,现在我们希望绑定主流的日志框架:
5.1.1、绑定 logback
只需要根据上面图片中的,导入对应依赖 logback-classic 和 logback-core 两个 jar 包即可:
在 Maven 中只需要导入 logback-classic ,因为它会传递自动依赖:
<!-- 绑定 logback 的日志实现--><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.4.14</version></dependency>
测试:把 slf4j-simple 的依赖去掉,直接运行上面的测试方法:
5.1.2、绑定 log4j
<!--导入 log4j 日志实现,需要导入适配器--><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.36</version></dependency><!--再导入 log4j --><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency>
5.1.3、绑定 JUL
<!--导入 jul 日志实现--><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-jdk14</artifactId><version>1.5.6</version></dependency>
6、Logback
<!--日志门面--><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.36</version></dependency><!-- 绑定 logback 的日志实现--><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency>
测试:logback 不需要配置文件就可以使用:
package com.lyh;import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class LogbackTest {private static final Logger log = LoggerFactory.getLogger(LogbackTest.class);@Testpublic void testQuick(){log.trace("trace");log.debug("debug");log.info("info");log.warn("warn");log.error("error");}
}
可以看到,logback 默认的日志级别是 debug:
6.1、logback 基础配置文件
如果 logback 均不存在下面的配置文件则使用默认的配置:
- logback.groovy
- logback-test.xml
- logback.xml
下面,我们在 property 标签中指定日志的格式,并在 appender 标签中引用,最后再通过 root 标签引用 appender:(在 logback 中,encoder 只是把 pattern 包装了一层而已)
<?xml version="1.0" encoding="UTF-8" ?>
<configuration><!--配置一些公共属性,比如日志输出的pattern、appender。下面就可以通过 ${name} 的方式直接使用该属性的value值--><property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %L:%c.%M %m%n"/><!--日志输出格式:%-5level%d{yyyy-MM-dd HH:mm:ss.SSS}%c 类的完整名称%M 方法名称%L 行号%thread%m 信息%n 换行--><!--控制台输出日志的 appender --><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><!--控制输出流对象 默认 System.out 改为 System.err--><target>System.err</target><!--指定日志消息的格式为上面我们自定义的格式--><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>${pattern}</pattern></encoder></appender><!--配置 rootLogger--><root level="INFO"><appender-ref ref="console"/></root></configuration>
依然是上面的测试方法,运行结果:
6.2、FileAppender
要查看一个标签中有哪些子标签可以设置属性很简单,比如如果你的 appender 标签中的 class 指定的属性是下面的 FileAppender 类,那么直接看它有哪些属性即可:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration><!--配置一些公共属性,比如日志输出的pattern、appender。下面就可以通过 ${name} 的方式直接使用该属性的value值--><property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %L:%c.%M %m%n"/><!--定义日志文件保存的位置--><property name="log_dir" value="./log"/><!--日志输出格式:%-5level%d{yyyy-MM-dd HH:mm:ss.SSS}%c 类的完整名称%M 方法名称%L 行号%thread%m 信息%n 换行--><!--控制台输出日志的 appender --><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><!--控制输出流对象 默认 System.out 改为 System.err--><target>System.err</target><!--指定日志消息的格式为上面我们自定义的格式--><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>${pattern}</pattern></encoder></appender><!--日志文件输出 appender--><appender name="file" class="ch.qos.logback.core.FileAppender"><!--日志文件的保存位置--><file>${log_dir}/logback.log</file><!--日志消息格式--><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>${pattern}</pattern></encoder></appender><!--配置 rootLogger--><root level="INFO"><appender-ref ref="console"/><appender-ref ref="file"/></root></configuration>
6.3、拆分归档压缩 Appender
<appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${log_file}/roll_logback.log</file><encoder class="ch.qos.logback.classic.PatternLayout"><pattern>${pattern}</pattern><!--指定拆分规则--><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!--按照时间和压缩格式来拆分文件名--><!--%i 代表文件顺序 1、2、3...--><FileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd-HH-mm-ss}.log%i.gz</FileNamePattern><!--按照文件大小拆分--><maxFileSize>1MB</maxFileSize></rollingPolicy></encoder></appender><!--配置 rootLogger--><root level="INFO"><appender-ref ref="console"/><appender-ref ref="rollFile"/></root>
最终生成的文件:
- rolling.2024-11-03.log0
- rolling.2024-11-03.log0.gz
- ...
6.4、过滤器
只输出 error 级别以上的日志:
<!--控制台输出日志的 appender --><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><!--控制输出流对象 默认 System.out 改为 System.err--><target>System.err</target><!--指定日志消息的格式为上面我们自定义的格式--><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>${pattern}</pattern></encoder><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>error</level><onMatch>ACCEPT</onMatch><onMissmatch>DENY</onMissmatch></filter></appender>
6.5、异步日志
默认日志输出是在主线程的,日志太多会影响效率,所以我们一般会选择异步日志:
<!--异步日志--><appender name="async" class="ch.qos.logback.classic.AsyncAppender"><!--指定某个具体的 appender--><appender-ref ref="rollFile"/></appender>
这样,在滚动文件时就不会影响主线程。
6.6、自定义 Logger
这样我们就可以只把自己的日志输出到控制台,而系统的日志额外指定输出到文件:
<!--rootLogger--><root level="INFO"><appender-ref ref="file"/></root><!--自定义logger--><!--com.lyh 包下的所有类都默认使用该 loggeradditivity 表示自定义 Logger 对象是否继承 rootLogger--><logger name="com.lyh" level="info" additivity=""><appender-ref ref="console"/></logger>
7、Log4j2
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.26</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>2.11.1</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.11.1</version></dependency>
package com.lyh;import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;public class Log4j2Test {public static final Logger log = LogManager.getLogger(Log4j2Test.class);@Testpublic void quick(){log.debug("debug");log.info("info");log.warn("warn");log.error("error");log.fatal("fatal");}
}
注意:这里我们使用的 Logger 对象是 log4j2 提供的:
7.1、配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<configuration><properties><property name="log_dir">./</property></properties><Appenders><Console name="console" target="System.out"><PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n"/></Console><File name="file" fileName="${log_dir}/log4j2.log"><PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n"/></File></Appenders><Loggers><Root level="trace"><AppenderRef ref="console"/><AppenderRef ref="file"/></Root></Loggers>
</configuration>
添加适配器:
<dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j-impl</artifactId><version>2.9.1</version></dependency>
使用 Slf4j 测试:
package com.lyh;import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class Slf4jTest {public static final Logger log = LoggerFactory.getLogger(Slf4jTest.class);@Testpublic void testQuick(){log.trace("trace");log.debug("debug");log.info("info");log.warn("warn");log.error("error");}
}
可以看到,Slf4j 自动调用了 log4j2 的实现:
总结:这几个依赖必须都有
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.26</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j-impl</artifactId><version>2.9.1</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>2.11.1</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.11.1</version></dependency>