Java 日志框架(一)

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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/15638.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

i春秋-GetFlag(HTTP请求方法使用,XXF伪造ip)

练习平台地址 竞赛中心 题目描述 题目内容 一打开就是一Not Found&#xff0c;以为是服务器挂了 解题 在页面中发现了404.php&#xff0c;尝试访问 在页面源码和headers中没有发现有用信息 返回去看首页的headers有没有信息 发现首页的响应头中有个奇怪的X-Method&#xf…

pgSQL-timescaledb复制表出现的问题

今日在工作中&#xff0c;需要复制一张timescaledb表&#xff0c;pgAdmin上复制一直未成功&#xff0c;或者我找错位置了。 1.我使用Navicate连接pgSQL&#xff0c;连上后选中相应表&#xff0c;右键复制结构即可 2.复制结构后&#xff0c;到pgAdmin中&#xff0c;将对应表下的…

无人机应用场景:石油管道巡检技术详解

无人机在石油管道巡检中的应用&#xff0c;以其高效、便捷、灵活的特点&#xff0c;为石油管道的安全管理提供了有力支持。以下是对无人机在石油管道巡检技术方面的详细解析&#xff1a; 一、无人机巡检技术的概述 无人机巡检技术是指利用无人机搭载各种传感器和检测设备&…

vue3+ant design vue实现日期等选择器点击右上角叉号默认将值变为null,此时会影响查询等操作~

1、效果图 2、思路&#xff1a;通过监听操作时间绑定的值是否存在&#xff0c;若存在将其改为空数组即可 3、代码&#xff1a; <a-form-item name"beginDate" label"操作日期" labelAlign"left"><a-range-picker v-model:value"…

java版Spring Cloud+Mybatis+Oauth2+分布式+微服务+实现工程管理系统

工程项目管理软件&#xff08;工程项目管理系统&#xff09;对建设工程项目管理组织建设、项目策划决策、规划设计、施工建设到竣工交付、总结评估、运维运营&#xff0c;全过程、全方位的对项目进行综合管理 工程项目各模块及其功能点清单 一、系统管理 1、数据字典&am…

AutoSar AP简单多绑定总结

文章主要介绍了多绑定相关内容&#xff0c;具体如下&#xff1a; 多绑定概念 某个代理类 / 骨架类不同实例间的技术传输存在差异&#xff0c;多绑定用于解决该情况&#xff0c;其产生可能源于代理类与不同骨架通信采用不同传输 / IPC&#xff0c;或同一骨架实例的不同代理实例…

软件测试面试大全(含答案+文档)

1、你的测试职业发展是什么&#xff1f; 测试经验越多&#xff0c;测试能力越高。所以我的职业发展是需要时间积累的&#xff0c;一步步向着高级测试工程师奔去。而且我也有初步的职业规划&#xff0c;前3年积累测试经验&#xff0c;按如何做好测试工程师的要点去要求自己&…

Windows docker下载minio出现“Using default tag: latestError response from daemon”

Windows docker下载minio出现 Using default tag: latest Error response from daemon: Get "https://registry-1.docker.io/v2/": context deadline exceeded 此类情况&#xff0c;一般为镜像地址问题。 {"registry-mirrors": ["https://docker.re…

Chapter 15 组件通信

1 组件通信方式 在 Vue 中&#xff0c;组件通信是指多个组件之间传递数据或消息的方式。 由于每个组件的数据是独立的&#xff0c;它们不能直接访问其他组件的数据&#xff0c;因此需要使用一些特定的方式来实现数据传递和通信。 【组件通信方式】 1. 父子组件通信&#xff1…

不仅能够实现前后场的简单互动,而且能够实现人机结合,最终实现整个巡检流程的标准化的智慧园区开源了

智慧园区场景视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;从而大大减少企业级应用约95%的开发成本。充分利用现有…

利用正则表达式批量修改文件名

首先&#xff0c; 我们需要稍微学习一下正则表达式的使用方式&#xff0c;可以看这里&#xff1a;Notepad正则表达式使用方法_notepad正则匹配-CSDN博客 经过初步学习之后&#xff0c;比较重要的内容我做如下转载&#xff1a; 元字符是正则表达式的基本构成单位&#xff0c;它们…

【 LLM论文日更|检索增强:大型语言模型是强大的零样本检索器 】

论文&#xff1a;https://aclanthology.org/2024.findings-acl.943.pdf代码&#xff1a;GitHub - taoshen58/LameR机构&#xff1a;悉尼科技大学 & 微软 & 阿姆斯特丹大学 & 马里兰大学领域&#xff1a;retrieval & llm发表&#xff1a;ACL2024 研究背景 研究…

驾校增加无人机培训项目可行性技术分析

驾校增加无人机培训项目的可行性技术分析&#xff0c;需要从市场需求、技术基础、政策支持、培训体系构建及运营等多个维度进行综合考量。以下是对这些方面的详细分析&#xff1a; 一、市场需求分析 1. 行业应用广泛&#xff1a;无人机在航拍、农业、环境监测、地理测绘、电力…

MFC1(note)

引言 在学习SDK后我们发现&#xff0c;写消息好麻烦&#xff0c;处理消息更麻烦 处理消息效率低发送消息效率低 所以把SDK中这些消息全部封装好 MFC封装了windows 的大部分API 这里说一下QT架构跨平台 MFC用得如何取决于你SDK的水平 创建 如果打开没有MFC 一般勾选以下…

OpenCV3.4.0 添加contrib模块过程记录

一 准备文件 下载这些文件&#xff0c;可以从GitHub/Gitee很容易找到&#xff1a; source-3.4.0.zip 源文件opencv_3rdparty-ffmpeg-master_20171009 opencv_3rdparty-ippicv-master_20170822 opencv_contrib-3.4.0.zip …

APT 参与者将恶意软件嵌入 macOS Flutter 应用程序中

发现了一些恶意软件样本&#xff0c;这些样本据信与朝鲜民主主义人民共和国 (DPRK)&#xff08;又称北朝鲜&#xff09;有关&#xff0c;这些样本使用 Flutter 构建&#xff0c;Flutter 的设计可以对恶意代码进行混淆。JTL 深入研究了恶意代码的工作原理&#xff0c;以帮助保护…

论文学习——一种基于决策变量分类的动态约束多目标进化算法

论文题目&#xff1a; A dynamic constrained multiobjective evolutionary algorithm based on decision variable classification 一种基于决策变量分类的动态约束多目标进化算法&#xff08;Yinan Guo a,b, Mingyi Huang a, Guoyu Chen a,*, Dunwei Gong c, Jing Liang d, …

基于微信小程序的高校实习管理系统设计与实现,LW+源码+讲解

摘 要 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代才发现能补上自…

【UE5】在材质Custom写函数的方法

UE材质的Custom本身会构建为函数&#xff0c;所以并不能在Custom定义函数&#xff0c;但当然还是有办法的 总结一些在custom写函数的方法 常规办法 常规办法就是使用结构体作为函数使用 以一个Lerp功能函数演示 让我们看看写法&#xff1a; struct VolBlendFunc //定义结…

Springboot如何打包部署服务器

文章目的&#xff1a;java项目打包成jar包或war包&#xff0c; 放在服务器上去运行 一、编写打包配置 1. pom.xml 在项目中的pom.xml文件里面修改<build>...</build>的代码 >> 简单打包成Jar形式&#xff0c;参考示例&#xff1a; <build><fina…