一、日志门面

  当我们的系统变的更加复杂的时候,我们的日志就容易发生混乱。随着系统开发的进行,可能会更新不同的日志框架,造成当前系统中存在不同的日志依赖,让我们难以统一的管理和控制。就算我们强制要求所有的模块使用相同的日志框架,系统中也难以避免使用其他类似 Spring,MyBatis 等其它的第三方框架,它们依赖于我们规定不同的日志框架,而且他们自身的日志系统就有着不一致性,依然会造成日志体系的混乱。
  所以我们需要借鉴 JDBC 的思想,为日志系统也提供一套门面,那么我们就可以面向这些接口规范来开发,避免了直接依赖具体的日志框架。这样我们的系统在日志中,就存在了日志的门面和日志的实现。

常见的日志门面:
  JCL,SLF4J

常见的日志实现:
  JUL,log4j、logback,log4j2

日志框架出现的历史顺序:
  log4j -> JUL -> JCL -> SLF4J -> logback -> log4j2

我们为什么要使用日志门面:

  1. 面向接口开发,不再依赖具体的实现类,减少代码的耦合。
  2. 项目通过导入不同的日志实现类,可以灵活的切换日志框架。
  3. 统一 API,方便开发者学习和使用。
  4. 统一配置便于项目日志的管理。

二、JCL

1、简介

  JCL 全称为 Jakarta Commons Logging,是 Apache 提供的一个通用日志 API。它是为“所有的 Java 日志实现”提供一个统一的接口,它自身也提供一个日志的实现,但是功能非常常弱(SimpleLog),所以一般一会单独使用它。他允许开发人员使用不同的具体日志实现工具:Log4j,JDK 自带的日志(JUL)。

  JCL 有两个基本的抽象类:Log(基本记录器)和 LogFactory(负责创建 Log 实例)。

image.png

2、入门

 1)建立 maven 工程
 2)添加依赖

1<dependency>
2    <groupId>commons-logging</groupId>
3    <artifactId>commons-logging</artifactId>
4    <version>1.2</version>
5</dependency>

 3)入门代码

 1import org.apache.commons.logging.Log;
 2import org.apache.commons.logging.LogFactory;
 3
 4public class Test {
 5
 6    public static void main(String[] args) {
 7        // 创建日志对象
 8        Log log= LogFactory.getLog(Test.class);
 9        // 日志记录输出
10        log.fatal("fatal");
11        log.error("error");
12        log.warn("warn");
13        log.info("info");
14        log.debug("debug");
15    }
16}

3、JCL 整合 log4j

  直接加上 log4j 的依赖即可。

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

  如果直接运行原来的代码会报错,如下:

1log4j:WARN No appenders could be found for logger (top.zyxwmj.journal.demo.Test).
2log4j:WARN Please initialize the log4j system properly.
3log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

  原因:log4j 没有被初始化,初始化的方式有两种:

 1)使用默认的配置
  加入以下代码即可。

1// 初始化配置信息
2BasicConfigurator.configure();

 2)使用配置文件
  创建 log4j.properties 文件,并写入以下配置即可。

1# 指定 RootLogger 顶级父元素默认配置信息
2# 指定日志级别=trace,使用的 appender 为 console
3log4j.rootLogger = ALL,console
4# 指定控制台日志输出的 appender
5log4j.appender.console = org.apache.log4j.ConsoleAppender
6# 指定消息格式 layout
7log4j.appender.console.layout = org.apache.log4j.SimpleLayout

JCL 已经淘汰就不再做过多的解释,主要讲解 SLF4J。

三、SLF4J

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

1、slf4j 入门

 1)建立 maven 工程
 2)添加依赖

 1        <!-- slf4j 日志门面 -->
 2        <dependency>
 3            <groupId>org.slf4j</groupId>
 4            <artifactId>slf4j-api</artifactId>
 5            <version>2.0.0-alpha1</version>
 6        </dependency>
 7
 8        <!-- slf4j 简单的实现 -->
 9        <dependency>
10            <groupId>org.slf4j</groupId>
11            <artifactId>slf4j-simple</artifactId>
12            <version>2.0.0-alpha1</version>
13            <scope>test</scope>
14        </dependency>

 3)快速入门

 1import org.slf4j.Logger;
 2import org.slf4j.LoggerFactory;
 3
 4public class Test {
 5
 6    public static final Logger LOGGER = LoggerFactory.getLogger(Test.class);
 7
 8    public static void main(String[] args) {
 9        // 日志输出
10        LOGGER.error("error");
11        LOGGER.warn("warn");
12        LOGGER.info("info");
13        LOGGER.debug("debug");
14
15        // 参数注入
16        String name = "yi-Xing";
17        int age = 18;
18        LOGGER.info("用户:{},{}", name, age);
19        // 将系统的异常信息输出
20        try {
21            int i = 1 / 0;
22        } catch (Exception e) {
23            //e.printStackTrace();
24            LOGGER.error("出现异常", e);
25        }
26    }
27}

2、slf4j 整合其他框架

  slf4j 支持各种日志框架,slf4j 发行版附带了几个称为“slf4j 绑定”的 jar 文件称为 slf4j 的适配器,每个绑定对应一个受支持的框架。使用 slf4j 整合其他日志框架,首先导入 slf4j-api 依赖。

1        <!-- slf4j 日志门面 -->
2        <dependency>
3            <groupId>org.slf4j</groupId>
4            <artifactId>slf4j-api</artifactId>
5            <version>2.0.0-alpha1</version>
6        </dependency>

  slf4j 本身是一个日志门面,内部是不提供具体实现的。如果只导入 slf4j-api 运行代码会报以下错误,因为没有对 slf4j 进行实现。

1SLF4J: No SLF4J providers were found.
2SLF4J: Defaulting to no-operation (NOP) logger implementation
3SLF4J: See http://www.slf4j.org/codes.html#noProviders for further details.
整合 simple

  上面快速入门的例子就是整合 simple,再导入 simple 的依赖即可使用。

1        <!-- slf4j 简单的实现 -->
2        <dependency>
3            <groupId>org.slf4j</groupId>
4            <artifactId>slf4j-simple</artifactId>
5            <version>2.0.0-alpha1</version>
6            <scope>test</scope>
7        </dependency>
整合 logback

  整合 logback,导入 logback-classiclogback-core 依赖即可。

 1        <!-- slf4j 的具体实现 logback-->
 2        <dependency>
 3            <groupId>ch.qos.logback</groupId>
 4            <artifactId>logback-classic</artifactId>
 5            <version>1.3.0-alpha5</version>
 6            <exclusions>
 7                <!--用来排除传递性依赖。-->
 8                <exclusion>
 9                    <groupId>org.slf4j</groupId>
10                    <artifactId>slf4j-api</artifactId>
11                </exclusion>
12                <exclusion>
13                    <groupId>ch.qos.logback</groupId>
14                    <artifactId>logback-core</artifactId>
15                </exclusion>
16            </exclusions>
17        </dependency>
18        <!--logback的基础模块-->
19        <dependency>
20            <groupId>ch.qos.logback</groupId>
21            <artifactId>logback-core</artifactId>
22            <version>1.3.0-alpha5</version>
23        </dependency>
整合 log4j

  由于 log4j 是在 slf4j 之前开发的,并没有根据 slf4j 的规范,所以要想让 log4j 和 slf4j 进行整合需要导入 slf4j-log4j12 依赖。

 1        <dependency>
 2            <groupId>log4j</groupId>
 3            <artifactId>log4j</artifactId>
 4            <version>1.2.17</version>
 5        </dependency>
 6        <dependency>
 7            <groupId>org.slf4j</groupId>
 8            <artifactId>slf4j-log4j12</artifactId>
 9            <version>2.0.0-alpha1</version>
10        </dependency>

  如果提示如下信息,说明 log4j 没有初始化。

1log4j:WARN No appenders could be found for logger (top.zyxwmj.journal.demo.Test).
2log4j:WARN Please initialize the log4j system properly.
3log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
整合 log4j2

  log4j2 有自己的日志门面,如果要和 slf4j 进行整合,需要添加log4j-slf4j-impl依赖作为适配器。

 1        <!-- slf4j 日志门面 -->
 2        <dependency>
 3            <groupId>org.slf4j</groupId>
 4            <artifactId>slf4j-api</artifactId>
 5            <version>1.7.30</version>
 6        </dependency>
 7        <!-- 基于 log4j2 的适配器 -->
 8        <dependency>
 9            <groupId>org.apache.logging.log4j</groupId>
10            <artifactId>log4j-slf4j-impl</artifactId>
11            <version>2.13.1</version>
12        </dependency>
13        <!-- Log4j2 门面 API -->
14        <dependency>
15            <groupId>org.apache.logging.log4j</groupId>
16            <artifactId>log4j-api</artifactId>
17            <version>2.13.1</version>
18        </dependency>
19        <!-- Log4j2 日志实现 -->
20        <dependency>
21            <groupId>org.apache.logging.log4j</groupId>
22            <artifactId>log4j-core</artifactId>
23            <version>2.13.1</version>
24        </dependency>
整合 JUL

  由于 JUL 是在 slf4j 之前开发的,也没有根据 slf4j 的规范,所以要想让 JUL 和 slf4j 进行整合需要导入 slf4j-jdk14 依赖。

1        <dependency>
2            <groupId>org.slf4j</groupId>
3            <artifactId>slf4j-jdk14</artifactId>
4            <version>2.0.0-alpha1</version>
5        </dependency>
slf4j 失效

  如果不想打印日志,导入以下依赖即可,即可使 slf4j 失效

1        <!-- slf4j 的开关 -->
2        <dependency>
3            <groupId>org.slf4j</groupId>
4            <artifactId>slf4j-nop</artifactId>
5            <version>2.0.0-alpha1</version>
6            <scope>test</scope>
7        </dependency>

3、slf4j 桥接旧的日志框架

  通常,你依赖的某些组件依赖于 slf4j 以外的日志记录 API。为了解决这种情况,slf4j 附带了几个桥接模块,这些模块将对 log4j、JCL 和 Java.util.logging API 的调用重定向,就好像它们是 slf4j-api 一样。

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

  1. 先去除之前老的日志框架的依赖。
  2. 添加 slf4j 提供的桥接组件。
  3. 为项目添加 slf4j 的具体实现。
桥接 log4j

  假设我们项目现在使用的日志框架是 log4j。

 1)依赖如下:

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

 2)代码如下:

 1import org.apache.log4j.BasicConfigurator;
 2import org.apache.log4j.Logger;
 3
 4public class Test {
 5
 6    public static final Logger LOGGER = Logger.getLogger(Test.class);
 7
 8    public static void main(String[] args) {
 9        BasicConfigurator.configure();
10        // 日志输出
11        LOGGER.info("info");
12    }
13}

 3)进行升级:

  现在我们项目升级,不想再使用 log4j 而想使用 slf4j 整合 logback,由于 log4j 和 slf4j 的语法不一样,如果直接更换需要修改源代码。如果我们不想修改源代码,可以使用 slf4j 的桥接器。我们只需要两步即可,首先删除 log4j 的依赖,然后添加以下依赖即可。

 1        <!-- slf4j 日志门面 -->
 2        <dependency>
 3            <groupId>org.slf4j</groupId>
 4            <artifactId>slf4j-api</artifactId>
 5            <version>2.0.0-alpha1</version>
 6        </dependency>
 7        <!-- log4j 的桥接器 -->
 8        <dependency>
 9            <groupId>org.slf4j</groupId>
10            <artifactId>log4j-over-slf4j</artifactId>
11            <version>2.0.0-alpha1</version>
12        </dependency>
13        <!-- slf4j 的具体实现 logback-->
14        <dependency>
15            <groupId>ch.qos.logback</groupId>
16            <artifactId>logback-classic</artifactId>
17            <version>1.3.0-alpha5</version>
18        </dependency>
桥接 JUL

  桥接 JUL 和上面的方法一样,只需要把 log4j-over-slf4j 更换为 jul-to-slf4j 即可。

1<dependency>
2    <groupId>org.slf4j</groupId>
3    <artifactId>jul-to-slf4j</artifactId>
4    <version>2.0.0-alpha1</version>
5</dependency>
桥接 JCU

  桥接 JCU 和上面的方法一样,只需要把 log4j-over-slf4j 更换为 jcl-over-slf4j 即可。

1<dependency>
2    <groupId>org.slf4j</groupId>
3    <artifactId>jcl-over-slf4j</artifactId>
4    <version>2.0.0-alpha1</version>
5</dependency>

4、适配器和桥接器的应用场景

  适配器是在项目之初,帮助 slf4j 和其他框架进行整合,使用 slf4j 语法进行编写日志,方便以后更换日志。而桥接器是对于老的项目,想对项目的日志框架进行升级,但又不想更改源代码的情况下使用。

注意问题:

  1. jcl-over-slf4j.jar 和 slf4j-jcl.jar 不能同时部署。前一个 jar 文件将导致 JCL 将日志系统的选择委托给 slf4j,后一个 jar 文件将导致 slf4j 将日志系统的选择委托给 JCL,从而导致死循环。
  2. log4j-over-slf4j.jar 和 slf4j-log4j12.jar 不能同时出现。
  3. jul-to-slf4j.jar 和 slf4j-jdk14.jar 不能同时出现
  4. 所有的桥接都只对 Logger 日志记录器对象有效,如果程序中调用了内部的配置类或者是 Appender、Filter 等对象,将无法产生效果。

5、slf4j 原理解析

  1. slf4j 通过 LoggerFactory 加载日志具体的实现对象。
  2. LoggerFactory 在初始化的过程中,会通过 performInitialization() 方法绑定具体的日志实现。
  3. 在绑定具体实现的时候,通过类加载器,加载 org/slf4j/impl/StaticLoggerBinder.class
  4. 所以,只要是一个日志实现框架,在 org.slf4j.impl 包中提供了一个自己的 StaticLoggerBinder 类,在其中提供具体日志实现的 LoggerFactory 就可以被 slf4j 所加载。

标题:日志门面——JCL、SLF4J
作者:Yi-Xing
地址:http://zyxwmj.top/articles/2020/03/22/1584871218647.html
博客中若有不恰当的地方,请您一定要告诉我。前路崎岖,望我们可以互相帮助,并肩前行!