日志门面概述

门面模式(外观模式)

GoF23 种设计模式其中之一。
门面模式(Facade Pattern),也称之为外观模式,其核心为:外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用。
外观模式主要是体现了Java中的一种好的封装性。更简单的说,就是对外提供的接口要尽可能的简单。

日志门面

前面介绍的几种日志框架,每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的 API,这就大大的增加应用程序代码对于日志框架的耦合性。

为了解决这个问题,就是在日志框架和应用程序之间架设一个沟通的桥梁,对于应用程序来说,无论底层的日志框架如何变,都不需要有任何感知。只要门面服务做的足够好,随意换另外一个日志框架,应用程序不需要修改任意一行代码,就可以直接运行。

常见日志框架及日志门面

常见的日志实现:JUL、log4j、logback、log4j2。
常见的日志门面 :JCL、slf4j

出现顺序 :log4j -> JUL -> JCL -> slf4j -> logback -> log4j2

SLF4J

SLF4J简介

简单日志门面(Simpl4 Logging Facade For Java)SLF4J主要是为了给Java 日志访问提供套标准、规范的 API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如 log4j 和 logback等。当然 slf4j 自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。所以我们可以得出SLF4J最重要的两个功能就是对于日志框架的绑定以及日志框架的桥接。

官方网站:https://www.slf4j.org/

SLF4J桥接技术

通常,我们依赖的某些组件依赖于SLF4JI以外的日志API。我们可能还假设这些组件在不久的将来不会切换到SLF4J。例如log4j、JUL、JCL是在SLF4J出现之间就出现了,为了处理这种情况,SLF4J附带了几个桥接模块,这些模块会将对 log4j,JCL和java.util.logging API的调用重定向为行为,就好像是对SLF4JAPI进行的操作一样。
出现顺序 :log4j -> JUL -> JCL -> slf4j -> logback -> log4j2

案例

源代码

https://github.com/gitaxin/JavaLog/tree/master/SLF4J

引入依赖

  1. <dependency>
  2. <groupId>junit</groupId>
  3. <artifactId>junit</artifactId>
  4. <version>4.13.2</version>
  5. </dependency>
  6. <!--slf4j日志门面 核心依赖-->
  7. <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
  8. <dependency>
  9. <groupId>org.slf4j</groupId>
  10. <artifactId>slf4j-api</artifactId>
  11. <version>1.7.32</version>
  12. </dependency>
  13. <!--slf4j自带的简单日志实现-->
  14. <!--
  15. 在没有任何其他日志实现框架集成的基础之上, slf4j使用的就是自带的框架slf4j-simple
  16. slf4j-simple也必须以单独依赖的形式导入进来,否则无法打印日志
  17. -->
  18. <dependency>
  19. <groupId>org.slf4j</groupId>
  20. <artifactId>slf4j-simple</artifactId>
  21. <version>1.7.32</version>
  22. </dependency>

入门

  1. @Test
  2. public void test(){
  3. Logger logger = LoggerFactory.getLogger(SLF4JTest.class);
  4. logger.info("========= INFO信息 test slf4j =========");
  5. /**
  6. * 使用SLF4J自带的日志实现
  7. * [main] INFO cn.giteasy.slf4j.test.SLF4JTest - ========= INFO信息 test slf4j =========
  8. */
  9. }

在没有任何其他日志实现框架集成的基础之上, slf4j使用的slf4j-simple,slf4j-simple也必须以单独依赖的形式导入进来,如果未引入任何实现,将会打印以下信息

  1. SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
  2. SLF4J: Defaulting to no-operation (NOP) logger implementation
  3. SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

日志级别

  1. @Test
  2. public void test(){
  3. Logger logger = LoggerFactory.getLogger(SLF4JTest.class);
  4. logger.error("========= ERROR信息 test slf4j =========");
  5. logger.warn("========= WARN信息 test slf4j =========");
  6. logger.info("========= INFO信息 test slf4j =========");
  7. logger.debug("========= DEBUG信息 test slf4j =========");//默认INFO级别,所以debug未打印
  8. logger.trace("========= TRACE信息 test slf4j =========");//默认INFO级别,所以trace未打印
  9. /**
  10. * 使用SLF4J自带的日志实现
  11. * [main] ERROR cn.giteasy.slf4j.test.SLF4JTest - ========= ERROR信息 test slf4j =========
  12. * [main] WARN cn.giteasy.slf4j.test.SLF4JTest - ========= WARN信息 test slf4j =========
  13. * [main] INFO cn.giteasy.slf4j.test.SLF4JTest - ========= INFO信息 test slf4j =========
  14. */
  15. }
级别 说明
error 日志错误信息
warn 日志警告信息
info 日志关键信息(默认级别)
debug 日志祥细信息
trace 日志跟踪信息

打印动态日志信息

如果是通过拼接字符串的形式,不仅麻烦,而且更重要的是可读性差,我们的日志打印是支持以替代符的形式做日志信息拼接的。
一般情况下,几乎所有的日志实现产品,都会提供这种基础功能。

  1. @Test
  2. public void test02(){
  3. Logger logger = LoggerFactory.getLogger(SLF4JTest.class);
  4. String name = "giteasy";
  5. int age = 18;
  6. logger.info("my name is {}, I am {} years old.",new Object[]{name,age});
  7. logger.info("my name is {}, I am {} years old.",name,age);//常用
  8. }

打印异常信息

  1. @Test
  2. public void test03(){
  3. Logger logger = LoggerFactory.getLogger(SLF4JTest.class);
  4. try {
  5. Class.forName("abc");
  6. } catch (ClassNotFoundException e) {
  7. //打印堆栈跟踪信息
  8. //e.printStackTrace();
  9. logger.info("错误信息:",e);
  10. }
  11. }

通过适配器绑定其他日志实现

观察SLF4J官网提供的图
concrete-bindings.png
图片来源:https://www.slf4j.org/manual.html

SLF4J日志门面,共有3种情况对日志实现进行绑定

  1. 在没有绑定任何日志实现的基础之上,日志是不能够绑定实现任何功能的;

    注意:slf4j-simple是虽然是slf4j官方提供的,使用的时候,也是需要导入依赖,自动绑定到slf4j门面上,如果不导入,slf4j核心依赖是不提供任何实现的。

  2. logback和simple(包括nop)(图中蓝色部分)
    都是slf4j门面时间线后面出现的日志实现框架,所以API完全遵循slf4j进行的设计,那么我们只需要导入想要使用的日志实现依赖,即可与slf4j无缝衔接

    注意:nop虽然也划分到了实现中,但是他是指不实现日志记录,不记录日志

  1. log4j和JUL
    都是slf4j门面时间线前面出现的日志实现框架,所以API没有遵循slf4j进行设计,通过适配桥接的技术,完成与日志门面的衔接


添加logback依赖,试着集成logback日志框架

  1. <!--slf4j日志门面 核心依赖-->
  2. <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
  3. <dependency>
  4. <groupId>org.slf4j</groupId>
  5. <artifactId>slf4j-api</artifactId>
  6. <version>1.7.32</version>
  7. </dependency>
  8. <!--slf4j自带的简单日志实现-->
  9. <!--
  10. 在没有任何其他日志实现框架集成的基础之上, slf4j使用的就是自带的框架slf4j-simple
  11. slf4j-simple也必须以单独依赖的形式导入进来
  12. -->
  13. <dependency>
  14. <groupId>org.slf4j</groupId>
  15. <artifactId>slf4j-simple</artifactId>
  16. <version>1.7.32</version>
  17. </dependency>
  18. <!--集成logback-->
  19. <!--
  20. logback-core是logback-classic的基础模块
  21. logback-classic已经涵盖了 logback-core,
  22. Maven有依赖传递性,会自动依赖logback-core
  23. -->
  24. <dependency>
  25. <groupId>ch.qos.logback</groupId>
  26. <artifactId>logback-classic</artifactId>
  27. <version>1.2.10</version>
  28. </dependency>


在集成了slf4j-simple依赖基础上,添加logback依赖后:
控制台打印:

  1. SLF4J: Class path contains multiple SLF4J bindings.//说明当前项目存在多个日志实现
  2. ...
  3. ...
  4. SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]//当出现多个日志实现时,实际绑定的依赖


说明在当前项目中有多个实现信赖,但实际使用的还是slf4j-simple

原因:因为是先导入的slf4j-simple依赖,所以默认还是使用slf4j-simple

如果希望使用指定的日志实现,需要在pom文件中修改引入的顺序,将指定的依赖放到前面。在实际应用的情况下,一般只集成一种日志实现。

使用slf4j-nop 禁止打印日志

slf4j-nop 是slf4j实现的,可以起到不打印日志的作用,虽然是slf4j实现的,如果要使用它,还是需要引入依赖的。
在项目中如果存在多个日志实现的话,slf4j-simple,logback,jul日志实现,slf4j-nop与前面这几个日志实现是同一类(图中蓝色)。如果让slf4j-nop起作用,根据slf4j是通过引入顺序进行绑定的,我们要将slf4j-nop依赖放到其他日志实现的前面

  1. <!--slf4j日志门面 核心依赖-->
  2. <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
  3. <dependency>
  4. <groupId>org.slf4j</groupId>
  5. <artifactId>slf4j-api</artifactId>
  6. <version>1.7.32</version>
  7. </dependency>
  8. <!--导入slf4j-nop-->
  9. <!-- no print-->
  10. <dependency>
  11. <groupId>org.slf4j</groupId>
  12. <artifactId>slf4j-nop</artifactId>
  13. <version>1.7.32</version>
  14. </dependency>
  15. <!--集成logback-->
  16. <!--
  17. logback-core是logback-classic的基础模块
  18. logback-classic已经涵盖了 logback-core,
  19. Maven有依赖传递性,会自动依赖logback-core
  20. -->
  21. <dependency>
  22. <groupId>ch.qos.logback</groupId>
  23. <artifactId>logback-classic</artifactId>
  24. <version>1.2.10</version>
  25. </dependency>

多个日志实现

当项目中存在多个日志实现时,会出现警告信息,但不影响使用,按照引入依赖的顺序默认使用的是引入依赖的第1个日志实现

例如项目中引入了logback 和 slf4j-simple
控制台打印:

  1. SLF4J: Class path contains multiple SLF4J bindings.
  2. SLF4J: Found binding in [jar:file:/C:/Users/Administrator/.m2/repository/org/slf4j/slf4j-simple/1.7.32/slf4j-simple-1.7.32.jar!/org/slf4j/impl/StaticLoggerBinder.class]
  3. SLF4J: Found binding in [jar:file:/C:/Users/Administrator/.m2/repository/ch/qos/logback/logback-classic/1.2.10/logback-classic-1.2.10.jar!/org/slf4j/impl/StaticLoggerBinder.class]
  4. SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
  5. SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]

源码分析
通过源码查看其原理(看看slf4j的执行原理)
进入到getLogger()方法,可看到:

  1. Logger logger = getLogger(clazz.getName());

继续进入getLogger。可看到:

  1. ILoggerFactory iLoggerFactory = getILoggerFactory();//用来取得Logger工厂实现的方法

进入getILoggerFactory,看到以双重检查锁的方式去做判断
执行performInitialization();工厂的初始化方法
进入performInitialization()
bind()就是用来绑定具体日志实现的方法
进入bind(),看到Set集合

  1. Set<URL> staticLoggerBinderPathSet = null;

因为当前有可能会有N多个日志框架的实现
看到

  1. staticLoggerBinderPathset = findPossibleStaticLoggerBinderPathSet();


进入findPossibleStaticLoggerBinderPathSet()
看到创建了一个有序不可重复的集合对象

  1. LinkedHashset staticLoggerBinderPathSet = new LinkedHashSet();



声明了枚举类的路径,经过if else判断,以获取系统中都有哪些日志实现
看到

  1. Enumeration paths
  2. if (loggerFactoryClassLoader == null) {
  3. paths = classLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
  4. } else {
  5. paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
  6. }


我们主要观察常量STATIC_LOGGER_BINDER_PATH
通过常量我们会找到类org/slf4j/impl/StaticLoggerBinder.class
这个类是以静态的方式绑定Logger实现的类

  1. StaticLoggerBinder来自slf4j-simple,如果当前项目引入了slf4j-simple的适配器.(slf4j-simple中自带适配器)
    进入StaticLoggerBinder看到:
    1. private final ILoggerFactory loggerFactory = new SimpleLoggerFactory();

进入SimpleLoggerFactory类,看到: getLogger()方法

看到

  1. Logger newInstance = new SimpleLogger(name);


使用的就是slf4j-simple的Logger

  1. StaticLoggerBinder来自slf4j-jdk14,如果当前项目引入了slf4j-jdk14(JUL的适配器)的适配器.(当前项目如何没有引入任何日志实现,则使用的是JUL)
    进入StaticLoggerBinder看到:
  1. new JDK14LoggerFactory();


进入JDK14LoggerFactory类的无参构造方法,看到:

  1. java.util.logging.Logger.getLogger("");


使用的就是jul的Logger
接着观察findPossiblestaticLoggerBinderPathset
看到以下代码,表示如果还有其他的日志实现

  1. while(paths.hasMoreElements()){
  2. URL path = (URL)paths.nextElement();
  3. //将路径添加进入staticLoggerBinderPathset
  4. staticLoggerBinderPathset.add(path);
  5. }

回到bind方法

  1. //表示对于绑定多实现的处理
  2. reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);

如果出现多日志实现的情况,则会打印

  1. Util.report("Class path contains multiple SLF4J bindings.");

总结:
在真实生产环境中,slf4j只绑定一个日志实现框架就可以了,绑定多个,默认使用导入依赖的第一个,而且会产生没有必要的警告信息

SLF4J集成LOG4J

源代码

https://github.com/gitaxin/JavaLog/tree/master/SLF4J_LOG4J

引主依赖

由于log4j是在slf4j之前出品的日志框架实现,所以并没有遵循slf4j的API规范,如果想要使用log4j,需要绑定一个适配器,叫做slf4j-log4j12

之前集成的logback,是slf4j之后出品的日志框架实现, logback就是按照slf4j的标准制定的API, 所以我们导入依赖就能用

  1. <dependency>
  2. <groupId>junit</groupId>
  3. <artifactId>junit</artifactId>
  4. <version>4.13.2</version>
  5. </dependency>
  6. <!--slf4j日志门面 核心依赖-->
  7. <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
  8. <dependency>
  9. <groupId>org.slf4j</groupId>
  10. <artifactId>slf4j-api</artifactId>
  11. <version>1.7.32</version>
  12. </dependency>
  13. <!--
  14. 由于log4j是在slf4j之前出品的日志框架实现,所以并没有遵循slf4j的API规范
  15. 如果想要使用log4j,需要绑定一个适配器,叫做slf4j-log4j12
  16. -->
  17. <!--导入log4j适配器依赖-->
  18. <dependency>
  19. <groupId>org.slf4j</groupId>
  20. <artifactId>slf4j-log4j12</artifactId>
  21. <version>1.7.32</version>
  22. </dependency>
  23. <!--导入log4j依赖-->
  24. <dependency>
  25. <groupId>log4j</groupId>
  26. <artifactId>log4j</artifactId>
  27. <version>1.2.17</version>
  28. </dependency>

测试

  1. @Test
  2. public void test01(){
  3. Logger logger = LoggerFactory.getLogger(SLF4J_LOG4J_TEST.class);
  4. logger.error("========= ERROR信息 test slf4j =========");
  5. logger.warn("========= WARN信息 test slf4j =========");
  6. logger.info("========= INFO信息 test slf4j =========");
  7. logger.debug("========= DEBUG信息 test slf4j =========");
  8. logger.trace("========= TRACE信息 test slf4j =========");
  9. /**
  10. * 输出结果:
  11. * log4j:WARN No appenders could be found for logger (cn.giteasy.slf4j.log4j.test.SLF4J_LOG4J_TEST).
  12. * log4j:WARN Please initialize the log4j system properly.
  13. * log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
  14. *
  15. * 很熟悉的打印结果,这是已经绑定了log4j日志实现,之所以打印这些信息,是因为没有配置Appender
  16. * 配置了Appender或添加了配置文件后,就可以使用了
  17. *
  18. */
  19. }

添加配置文件

  1. log4j.rootLogger = trace,consoleAppender
  2. #*********************************************************************************************
  3. #配置appender输出方式为控制台输出
  4. log4j.appender.consoleAppender = org.apache.log4j.ConsoleAppender
  5. #配置appender控制台输出的格式
  6. #log4j.appender.consoleAppender.layout = org.apache.log4j.SimpleLayout
  7. #以html格式输出
  8. #log4j.appender.consoleAppender.layout = org.apache.log4j.HTMLLayout
  9. log4j.appender.consoleAppender.layout = org.apache.log4j.PatternLayout
  10. log4j.appender.consoleAppender.layout.conversionPattern = %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5p %l %m%n
  1. /**
  2. * 添加配置文件log4j.properties后测试
  3. */
  4. @Test
  5. public void test02(){
  6. Logger logger = LoggerFactory.getLogger(SLF4J_LOG4J_TEST.class);
  7. logger.error("========= ERROR信息 test slf4j =========");
  8. logger.warn("========= WARN信息 test slf4j =========");
  9. logger.info("========= INFO信息 test slf4j =========");
  10. logger.debug("========= DEBUG信息 test slf4j =========");
  11. logger.trace("========= TRACE信息 test slf4j =========");
  12. /**
  13. * 输出结果:
  14. * 2022-01-22 21:55:27.908 [main] ERROR cn.giteasy.slf4j.log4j.test.SLF4J_LOG4J_TEST.test02(SLF4J_LOG4J_TEST.java:54) ========= ERROR信息 test slf4j =========
  15. * 2022-01-22 21:55:27.910 [main] WARN cn.giteasy.slf4j.log4j.test.SLF4J_LOG4J_TEST.test02(SLF4J_LOG4J_TEST.java:55) ========= WARN信息 test slf4j =========
  16. * 2022-01-22 21:55:27.910 [main] INFO cn.giteasy.slf4j.log4j.test.SLF4J_LOG4J_TEST.test02(SLF4J_LOG4J_TEST.java:56) ========= INFO信息 test slf4j =========
  17. * 2022-01-22 21:55:27.910 [main] DEBUG cn.giteasy.slf4j.log4j.test.SLF4J_LOG4J_TEST.test02(SLF4J_LOG4J_TEST.java:57) ========= DEBUG信息 test slf4j =========
  18. * 2022-01-22 21:55:27.910 [main] TRACE cn.giteasy.slf4j.log4j.test.SLF4J_LOG4J_TEST.test02(SLF4J_LOG4J_TEST.java:58) ========= TRACE信息 test slf4j =========
  19. *
  20. * 看到以上日志输出格式,完全是我们在配置文件中定义的格式,说明配置成功。
  21. *
  22. *
  23. *
  24. */
  25. }

总结:
通过这个集成测试,我们会发现虽然底层的日志实现变了,但是源代码没有改变。这就是日志门面给我们带来最大的好处, 在底层真实记录日志的时候,不需要去做任何的了解,只需要去记slf4j的API就可以了
我们虽然底层使用的是log4j做的打印,但是从当前代码使用来看,我们其实使用的仍然是slf4j日志门面,(导包时只导入了slf4j的API接口)
至于日志是log4j打印的(或者是logback打印的)都是由slf4j进行操作的,我们无需关心

SLF4J集成JUL

源代码

https://github.com/gitaxin/JavaLog/tree/master/SLF4J-JUL

引入依赖

SLF4J 使用JUL作为日志实现
与log4j一样,JUL是在slf4j之前出品的日志框架实现,所以并没有遵循slf4j的API规范,如果想要使用JUL作为slf4j的实现,需要导入slf4j-jdk14适配器依赖

  1. <dependency>
  2. <groupId>junit</groupId>
  3. <artifactId>junit</artifactId>
  4. <version>4.13.2</version>
  5. </dependency>
  6. <!--slf4j日志门面 核心依赖-->
  7. <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
  8. <dependency>
  9. <groupId>org.slf4j</groupId>
  10. <artifactId>slf4j-api</artifactId>
  11. <version>1.7.32</version>
  12. </dependency>
  13. <!--
  14. 与log4j一样,JUL也是在slf4j之前出品的日志框架实现,所以并没有遵循slf4j的API规范
  15. 如果想要使用JUL作为slf4j的实现,需要导入slf4j-jdk14适配器
  16. -->
  17. <dependency>
  18. <groupId>org.slf4j</groupId>
  19. <artifactId>slf4j-jdk14</artifactId>
  20. <version>1.7.32</version>
  21. </dependency>

测试

  1. @Test
  2. public void test01(){
  3. Logger logger = LoggerFactory.getLogger(SLF4J_JUL_TEST.class);
  4. logger.error("========= ERROR信息 test slf4j-JUL =========");
  5. logger.warn("========= WARN信息 test slf4j-JUL =========");
  6. logger.info("========= INFO信息 test slf4j-JUL =========");
  7. logger.debug("========= DEBUG信息 test slf4j-JUL =========");
  8. logger.trace("========= TRACE信息 test slf4j-JUL =========");
  9. /**
  10. * 打印结果:
  11. *
  12. *
  13. * 一月 23, 2022 2:58:36 下午 cn.giteasy.slf4j.jul.test.SLF4J_JUL_TEST test01
  14. * 严重: ========= ERROR信息 test slf4j-JUL =========
  15. * 一月 23, 2022 2:58:36 下午 cn.giteasy.slf4j.jul.test.SLF4J_JUL_TEST test01
  16. * 警告: ========= WARN信息 test slf4j-JUL =========
  17. * 一月 23, 2022 2:58:36 下午 cn.giteasy.slf4j.jul.test.SLF4J_JUL_TEST test01
  18. * 信息: ========= INFO信息 test slf4j-JUL =========
  19. *
  20. *
  21. * 从控制台打印的结果,可以看出这就是JUL的日志默认打印风格
  22. */
  23. }

SLF4J实战

源代码

https://github.com/gitaxin/JavaLog/tree/master/SLF4J_ACTION

需求

  1. 假设我们项目一直以来使用的是log4j日志框架,但是随着技术和需求的更新换代。 log4j已然不能够满足我们系统的需求. 我们现在就需要将系统中的日志实现重构为 slf4j+logback的组合,在不触碰java源代码的情况下,如何解决这个问题?

模拟当前我们使用的Log4j打印日志

引入依赖

  1. <dependency>
  2. <groupId>junit</groupId>
  3. <artifactId>junit</artifactId>
  4. <version>4.13.2</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>log4j</groupId>
  8. <artifactId>log4j</artifactId>
  9. <version>1.2.17</version>
  10. </dependency>

添加log4j配置文件

log4j.properties

  1. log4j.rootLogger = trace,consoleAppender
  2. #*********************************************************************************************
  3. #配置consoleAppender输出方式为控制台输出
  4. log4j.appender.consoleAppender = org.apache.log4j.ConsoleAppender
  5. log4j.appender.consoleAppender.layout = org.apache.log4j.PatternLayout
  6. log4j.appender.consoleAppender.layout.conversionPattern = %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5p %l %m%n

使用Log4j打印日志

  1. @Test
  2. public void test01(){
  3. Logger logger = Logger.getLogger(SLF4J_ACTION.class);
  4. logger.fatal("FATAL信息 =========testLog4j========= ");
  5. logger.error("ERROR信息 =========testLog4j========= ");
  6. logger.warn("WARN信息 =========testLog4j========= ");
  7. logger.info("INFO信息 =========testLog4j========= ");
  8. logger.debug("DEBUG信息 =========testLog4j========= ");
  9. logger.trace("TRACE信息 =========testLog4j========= ");
  10. /***
  11. * 输出结果
  12. *
  13. * 2022-01-23 16:02:52.881 [main] FATAL cn.giteasy.slf4j.action.test.SLF4J_ACTION.test01(SLF4J_ACTION.java:26) FATAL信息 =========testLog4j=========
  14. * 2022-01-23 16:02:52.883 [main] ERROR cn.giteasy.slf4j.action.test.SLF4J_ACTION.test01(SLF4J_ACTION.java:27) ERROR信息 =========testLog4j=========
  15. * 2022-01-23 16:02:52.883 [main] WARN cn.giteasy.slf4j.action.test.SLF4J_ACTION.test01(SLF4J_ACTION.java:28) WARN信息 =========testLog4j=========
  16. * 2022-01-23 16:02:52.883 [main] INFO cn.giteasy.slf4j.action.test.SLF4J_ACTION.test01(SLF4J_ACTION.java:29) INFO信息 =========testLog4j=========
  17. * 2022-01-23 16:02:52.883 [main] DEBUG cn.giteasy.slf4j.action.test.SLF4J_ACTION.test01(SLF4J_ACTION.java:30) DEBUG信息 =========testLog4j=========
  18. * 2022-01-23 16:02:52.883 [main] TRACE cn.giteasy.slf4j.action.test.SLF4J_ACTION.test01(SLF4J_ACTION.java:31) TRACE信息 =========testLog4j=========
  19. */
  20. }

使用logback替换log4j

SLF4J桥接器

legacy.png
图片来源:https://www.slf4j.org/legacy.html

桥接器解决的是项目中日志的重构问题,当前系统中存在之前的日志API,可以通过桥接转换到slf4j的实现。

使用桥接器

  1. 去除之前旧的日志框架依赖

    1. <dependency>
    2. <groupId>log4j</groupId>
    3. <artifactId>log4j</artifactId>
    4. <version>1.2.17</version>
    5. </dependency>

    此时项目编译会报错,不用担心,下一步引入桥接器就可以了。

  2. 添加slf4j核心依赖和SLF4J提供的桥接组件 ```xml

    org.slf4j slf4j-api 1.7.32 org.slf4j log4j-over-slf4j 1.7.32
  1. 此时,我们发现项目编译的报错,已恢复正常,此时我们并没有改变一行源代码.
  2. 3. 添加我们需求中要使用的日志实现依赖logback
  3. ```xml
  4. <dependency>
  5. <groupId>ch.qos.logback</groupId>
  6. <artifactId>logback-classic</artifactId>
  7. <version>1.2.10</version>
  8. </dependency>

测试打印

  1. @Test
  2. public void test01(){
  3. Logger logger = Logger.getLogger(SLF4J_ACTION.class);
  4. logger.fatal("FATAL信息 =========testLog4j========= ");
  5. logger.error("ERROR信息 =========testLog4j========= ");
  6. logger.warn("WARN信息 =========testLog4j========= ");
  7. logger.info("INFO信息 =========testLog4j========= ");
  8. logger.debug("DEBUG信息 =========testLog4j========= ");
  9. logger.trace("TRACE信息 =========testLog4j========= ");
  10. /***
  11. * 输出结果:引入slf4j、SLF4J桥接器,以及logback后,打印结果,已经是logback的默认风格了,此时我们只是替换了几个依赖,并没有改变任何低码
  12. *
  13. * 16:49:35.462 [main] ERROR cn.giteasy.slf4j.action.test.SLF4J_ACTION - FATAL信息 =========testLog4j=========
  14. * 16:49:35.464 [main] ERROR cn.giteasy.slf4j.action.test.SLF4J_ACTION - ERROR信息 =========testLog4j=========
  15. * 16:49:35.464 [main] WARN cn.giteasy.slf4j.action.test.SLF4J_ACTION - WARN信息 =========testLog4j=========
  16. * 16:49:35.464 [main] INFO cn.giteasy.slf4j.action.test.SLF4J_ACTION - INFO信息 =========testLog4j=========
  17. * 16:49:35.464 [main] DEBUG cn.giteasy.slf4j.action.test.SLF4J_ACTION - DEBUG信息 =========testLog4j=========
  18. *
  19. */
  20. }

在重构之后,就会为我们造成这样一种假象,使用的是log4j包下的日志组件,但是真正日志的实现,却是slf4j门面+logback实现,这就是桥接器给我们带来的效果。

注意:
在桥接器加入之后,log4j适配器就没有必要加入了,
桥接器和适配器不能同时导入依赖,假如导入了,会存在两种情况:
1. 桥接器如果配置在适配器的上方,则运行时会报错(栈溢出异常),不同同时出现。(slf4j->适配器slf4j-log4j12->桥接器log4j-over-slf4j->适配器slf4j-log4j12->…… 出现死循环)
2. 桥接器如果配置在适配器的下方,则不会执行桥接器,对于我们来说就没有任何意义。(我们的需求是要将log4j的打印,桥接到slf4j+logback实现上)

源码分析

在配置了桥接器之后,底层就是使用slf4j实现的日志

  1. @Test
  2. public void test03(){
  3. Logger logger = Logger.getLogger(SLF4J_ACTION.class);
  4. logger.info("INFO信息 =========testLog4j========= ");
  5. }

通过getLogger,进入Log4jLoggerFactory

  1. Logger newInstance = new Logger(name);//新建logger对象



进入构造方法

  1. protected Logger(string name) {
  2. super(name);
  3. }



点击进入父类的构造方法

  1. Category(String name){
  2. this name = name;
  3. this.slf4jLogger = LoggerFactory getLogger(name);
  4. if (this.slf4jLogger instanceof LocationAwareLogger) {
  5. this.locationAwareLogger = (LocationAwareLogger)this.slf4jLogger;
  6. }
  7. }


在这个Category构造方法中:

  1. this.slf4jLogger = LoggerFactory.getLogger(name);

LoggerFactory来自于org.slf4j,这就证明了getLogger是来自slf4j的。