Java日志框架(2/2)

5.SLF4J

5.1.日志门面概述

5.1.1.门面模式(外观模式)

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

5.1.2.日志门面

前面介绍的几种日志框架,每一种日志框架都有自己单独的 API,要使用对应的框架就要使用其对应的 API,这就大大的增加应用程序代码对于日志框架的耦合性。
为了解决这个问题,就是在日志框架和应用程序之间架设一个沟通的桥梁,对于应用程序来说,无论底层的日志框架如何变,都不需要有任何感知。只要门面服务做的足够好,随意换另外一个日志框架,应用程序不需要修改任意一行代码,就可以直接上线。

5.1.3.常见的日志框架及日志门面

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

常见的日志门面 :JCL、slf4j

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

5.2.SLF4J

5.2.1.SLF4J 简介

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

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

5.2.2.SLF4J桥接技术

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

5.2.3.SLF4J入门案例

5.2.3.1.引入依赖
<dependencies>
    <!--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>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>
5.2.3.2.具体实现
public void test01() {
    Logger logger = LoggerFactory.getLogger(Slf4jDemo.class);
    logger.trace("trace信息");
    logger.debug("debug信息");
    logger.info("info信息");
    logger.warn("warn信息");
    logger.error("error信息");
}

5.3.SLF4J特点

5.3.1.SLF4J对日志的级别划分

trace、debug、info、warn、error五个级别

  1. trace:日志追踪信息
  2. debug:日志详细信息
  3. info:日志的关键信息,默认打印级别
  4. warn:日志警告信息
  5. error:日志错误信息

在没有任何其他日志实现框架集成的基础之上,slf4j使用的就是自带的框架slf4j-simple。slf4j-simple也必须以单独依赖的形式导入进来

5.3.2.SLF4J动态打印信息

在输出动态的信息时,可以使用占位符的形式来代替字符串的拼接。

我们有些时候输出的日志信息,需要我们搭配动态的数据。有可能是信息,有可能是数据库表中的数据,总之我们这样做最大的好处就是能够让日志打印变得更加灵活。

如果是通过拼接字符串的形式,不仅麻烦,而且更重要的是可读性差,我们的日志打印是支持以替代符的形式做日志信息拼接的。

一般情况下,几乎所有的日志实现产品,都会提供这种基础功能

示例:

public void test02() {
    Logger logger = LoggerFactory.getLogger(Slf4jDemo.class);
    String name = "张三";
    int age = 23;
    logger.info("学生信息-姓名:{},年龄:{}",new Object[]{name,age});
    logger.info("学生信息-姓名:{},年龄:{}", name, age);
}

5.3.3.SLF4J对于异常信息的处理

一般情况下,我们在开发中的异常信息,都是记录在控制台上(我们开发环境的一种日志打印方式)。我们会根据异常信息提取出有用的线索,来调试bug。

但是在真实生产环境中(项目上线),对于服务器或者是系统相关的问题。在控制台上其实也会提供相应的异常或者错误信息的输出
但是这种错误输出方式(输出的时间,位置,格式…)都是服务器系统默认的

我们可以通过日志技术,选择将异常以日志打印的方式,进行输出查看。输出的时间,位置(控制台,文件),格式,完全由我们自己去进行定义。

public void test03() {
    Logger logger = LoggerFactory.getLogger(Slf4jDemo.class);
    try {
        Class.forName("aaa");
    } catch (ClassNotFoundException e) {
        //打印栈追踪信息
        //e.printStackTrace();
        logger.info("XXX类中的XXX方法出现了异常,请及时关注信息");
        //e是引用类型对象,不能根前面的{}做有效的字符串拼接。我们不用加{},直接后面加上异常对象e即可
        logger.error("具体错误是:{}", e.getMessage(), e);
    }
}

e是引用类型对象,不能根前面的{}做有效的字符串拼接。直接后面加上异常对象e即可。

5.4.SLF4J集成日志实现

5.4.1.SLF4J与日志集成分类

观察官网图,集成其他日志实现图

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

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

  1. 在没有绑定任何日志实现的基础之上,日志是不能够绑定实现任何功能的;值得大家注意的是,通过我们刚刚的演示,slf4j-simple是slf4j官方提供的使用的时候,也是需要导入依赖,自动绑定到slf4j门面上如果不导入,slf4j核心依赖是不提供任何实现的。

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

    值得一提的是nop虽然也划分到实现中了,但是他是指不实现日志记录(后续课程)

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

5.4.2.SLF4J同时集成多个日志实现遇到问题

引入logback-classic依赖,此时logback-classic坐标在slf4j-simple的下面。

<!--slf4j 自带的简单日志实现 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.36</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.11</version>
</dependency>

在原有slf4j - simple日志实现的基础上,又集成了logback。

通过测试,日志是打印出来了 java.lang.ClassNotFoundException:aaa

通过这一句可以发现:SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]

虽然集成了logback,但是我们现在使用的仍然是slf4j - simple

事实上只要出现了这个提示

Class path contains multiple SLF4J bindings.

在slf4j环境下,证明同时出现了多个日志实现

如果先导入logback依赖,后导入slf4j - simple依赖。那么默认使用的就是logback依赖,如果有多个日志实现的话,默认使用先导入的实现。

将slf4j - simple注释掉,只留下logback,那么slf4j门面使用的就是logback日志实现。值得一提的是,这一次没有多余的提示信息。所以在实际应用的时候,我们一般情况下,仅仅只是做一种日志实现的集成就可以了。

通过这个集成测试,我们会发现虽然底层的日志实现变了,但是源代码完全没有改变;在底层真实记录日志的时候,不需要应用去做任何的了解。应用只需要去记slf4j的API就可以了。

5.4.3.SLF4J集成slf4j-nop禁止日志打印

使用slf4j-nop,表示不记录日志。在我们使用slf4j-nop的时候,首先还是需要导入实现依赖。

这个实现依赖,根据我们之前所总结出来的日志日志实现种类的第二种,与logback和simple是属于一类的,通过集成依赖的顺序而定,所以如果想要让slf4j-nop发挥效果,禁止所有日志的打印。那么就必须要将slf4j-nop的依赖放在所有日志实现依赖的上方。

<!--slf4j 核心依赖-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.36</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-nop</artifactId>
    <version>1.7.36</version>
</dependency>
<!--slf4j 自带的简单日志实现 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.36</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.11</version>
</dependency>

5.4.4.SLF4J集成Log4j

由于log4j是在slf4j之前出品的日志框架实现,所以并没有遵循slf4j的API规范。

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

如果想要使用,需要绑定一个适配器,叫做slf4j-log4j12,再导入log4j的实现。

  1. 引入依赖

    <!--slf4j 核心依赖-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
    </dependency>
    <!--        导入log4j适配器依赖-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.32</version>
    </dependency>
    <!--        导入log4j依赖-->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    
  2. java代码

    public void test06() {
        Logger logger = LoggerFactory.getLogger(Slf4jDemo.class);
        logger.trace("trace信息");
        logger.debug("debug信息");
        logger.info("info信息");
        logger.warn("warn信息");
        logger.error("error信息");
    }
    
  3. 测试结果

    虽然日志信息没有打印出来,那么根据警告信息可以得出:

    使用了log4j日志实现框架,提示appender没有加载,需要在执行日志之前做相应的加载工作(初始化)

    我们可以将log4j的配置文件导入使用

    测试结果为log4j的日志打印,而且格式和级别完全是遵循log4j的配置文件进行的输出。因此需要配置Log4j的配置文件

  4. 配置log4j.properties

    # log4j.rootLogger=日志级别,appenderName1,appenderName2,appenderName3....
    log4j.rootLogger=trace,console
    # 配置appender输出方式
    log4j.appender.console=org.apache.log4j.ConsoleAppender
    # 配置输出的格式
    log4j.appender.console.layout=org.apache.log4j.PatternLayout
    log4j.appender.console.layout.conversionPattern=[%-10p] %r %c %t %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
    
  5. 测试

5.5.日志重构

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

5.5.1.日志重构解决方案

  1. 将所有关于其他日志实现和门面依赖全部去除

    1. 仅仅只留下log4j的依赖,测试的过程中,只能使用log4j相关的组件

      <dependencies>
          <!--        导入log4j依赖-->
          <dependency>
              <groupId>log4j</groupId>
              <artifactId>log4j</artifactId>
              <version>1.2.17</version>
          </dependency>
          <dependency>
              <groupId>junit</groupId>
              <artifactId>junit</artifactId>
              <version>4.13.2</version>
              <scope>test</scope>
          </dependency>
      </dependencies>
      
    2. log4j.properties

      # log4j.rootLogger=日志级别,appenderName1,appenderName2,appenderName3....
      log4j.rootLogger=trace,console
      # 配置appender输出方式
      log4j.appender.console=org.apache.log4j.ConsoleAppender
      # 配置输出的格式
      log4j.appender.console.layout=org.apache.log4j.PatternLayout
      log4j.appender.console.layout.conversionPattern=[%-10p] %r %c %t %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
      
    3. java代码

      public void test09() {
          Logger logger = LogManager.getLogger(Slf4jDemo1.class);
          logger.info("info信息");
      }
      
    4. 运行结果

  2. 将日志依赖替换为slf4j+logback

    我们既然不用log4j了,就将log4j去除,将slf4j日志门面和logback的日志实现依赖加入进来。此时没有了log4j环境的支持,编译报错。

    这个时候就需要使用桥接器来做这个需求了。

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

  3. 实现重构

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

      <!--        导入log4j依赖-->
      <!--        <dependency>-->
      <!--            <groupId>log4j</groupId>-->
      <!--            <artifactId>log4j</artifactId>-->
      <!--            <version>1.2.17</version>-->
      <!--        </dependency>-->
      
    2. 添加slf4j提供的桥接组件

      <!--        log4j相关的桥接器 -->
      <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>log4j-over-slf4j</artifactId>
          <version>1.7.25</version>
      </dependency>
      

      桥接器加入后,代码编译就不报错了

    3. 测试

      日志信息输出,输出格式为logback

      证明了现在使用的确实是slf4j门面+logback实现

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

      注意:

      在桥接器加入之后,适配器就没有必要加入了
      桥接器和适配器不能同时导入依赖
      桥接器如果配置在适配器的上方,则运行报错,不同同时出现
      桥接器如果配置在适配器的下方,则不会执行桥接器,没有任何的意义

5.5.2.源码解析

package com.stonebridge.slf4j;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

public class Slf4jDemo1 {
    public void test10() {
        Logger logger = LogManager.getLogger(Slf4jDemo1.class);
    }
}

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

通过getLogger,进入Log4jLoggerFactory

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

进入构造方法

protected Logger(String name) {
	super(name);
}

点击进入父类的构造方法

Category(String name) {
	this.name = name;
    this.slf4jLogger = LoggerFactory.getLogger(name);
    if (this.slf4jLogger instanceof LocationAwareLogger) {
    	this.locationAwareLogger = (LocationAwareLogger)this.slf4jLogger;
    }
}

在这个Category构造方法中,核心代码

this.slf4jLogger = LoggerFactory.getLogger(name);

LoggerFactory来自于org.slf4j

6.Logback

6.1.Logback简介

Logback 是由 log4j 创始人设计的又一个开源日志组件。

Logback 当前分成三个模块:logback-core,logback- classic 和 logback-access。

logback-core 是其它两个模块的基础模块。

logback-classic 是 log4j 的一个改良版本。此外 logback-classic 完整实现 SLF4J API。使你 可以很方便地更换成其它日志系统如 log4j 或 JDK14 Logging。

logback-access 访问模块与 Servlet 容器集成提供通过 Http 来访问日志的功能。

6.1.1.Logback中的组件

  1. Logger: 日志的记录器,主要用于存放日志对象,也可以定义日志类型、级别。

  2. Appender:用于指定日志输出的目的地,目的地可以是控制台、文件、数据库等等。

  3. Layout: 负责把事件转换成字符串,格式化的日志信息的输出。

    在 Logback 中 Layout 对象被封装在 encoder 中。

    也就是说我们未来使用的encoder其实就是Layout

6.1.2.Logback配置文件

Logback 提供了 3 种配置文件

  • logback.groovy
  • logback-test.xml
  • logback.xml (推荐)

如果都不存在则采用默认的配置

6.1.3.日志输出格式

  1. %-10level 级别 案例为设置 10 个字符,左对齐
  2. %d{yyyy-MM-dd HH:mm:ss.SSS} 日期
  3. %c 当前类全限定名
  4. %M 当前执行日志的方法
  5. %L 行号
  6. %thread 线程名称
  7. %m 或者%msg 信息
  8. %n 换行

6.1.4.Logback和slf4j依赖

<dependencies>
    <!-- slf4j日志门面 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
    </dependency>
    <!-- logback日志实现 -->
    <!--
        logback-core是logback-classic的基础模块
        logback-classic已经涵盖logback-core这个依赖了
        根据maven依赖的传递性,不需要重复导入
        只导入一个logback-classic
    -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.11</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
    </dependency>
</dependencies>

6.2.入门案例

6.2.1.业务代码

public void test01() {
    /*
        入门案例
        logback有5种级别的日志输出
        分别是
        trace < debug < info < warn < error
        通过信息打印,默认的日志级别是debug,trace信息没有打印出来
     */
    Logger logger = LoggerFactory.getLogger(LogbackTest.class);
    logger.error("error信息");
    logger.warn("warn信息");
    logger.info("info信息");
    logger.debug("debug信息");
    logger.trace("trace信息");
}

未配置样式时使用默认样式

6.2.2.运行测试

6.3.使用Logback配置文件

6.3.1.配置文件logback.xml

Logback配置文件logback.xml配置通用属性、配置控制台appender、配置rootlogger。

<?xml version="1.0" encoding="UTF-8" ?>
<!--1.根标签叫做configuration,一切配置都是在根标签中进行操作的-->
<configuration>
    <!--
    2.配置文件通用的属性
        <property name="" value=""></property>
        所谓配置文件中的通用属性是为了让接下来的配置更加方便引用
        通过以${name}的形式,方便的取得value值
        通过取得的value值可以做文件的其他配置而使用
    -->
    <!--
    3.我们在此可以先做日志输出格式相关的配置
        %-10level  级别 案例为设置10个字符,左对齐
        %d{yyyy-MM-dd HH:mm:ss.SSS} 日期
        %c  当前类全限定名
        %M  当前执行日志的方法
        %L  行号
        %thread 线程名称
        %m或者%msg    信息
        %n  换行
        以property的形式将日志输出格式配置成为文件的通用的属性
        那么下面我们配置的输出方式中,就可以重复的引用该配置(以下配置输出格式就不用配置多次了)
    -->
    <property name="pattern_1" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L %thread %m%n"/>
    <!-- 4.配置控制台appender-->
    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <!--
            5.表示对于日志输出目标的配置
            默认:System.out 表示以黑色字体输出日志(默认)
            设置:System.err 表示以红色字体输出日志
        -->
        <target>System.err</target>
        <!--
            6.配置日志输出格式
            手动配置格式的方式
            直接引入上述的通用属性即可
        -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!-- 7.格式引用通用属性配置 -->
            <pattern>${pattern_1}</pattern>
        </encoder>
    </appender>
    <!--
        8.配置日志记录器
        配置root logger
        level:配置日志级别
        可以同时配置多个appender,做日志的多方向输出
    -->
    <root level="ALL">
        <!-- 9.引入appender -->
        <!--<appender-ref ref="roll"/>-->
        <!--<appender-ref ref="consoleFilterAppender"/>-->
        <appender-ref ref="consoleAppender"/>
    </root>
</configuration>

6.3.2.业务代码

public void test02() {
    /*
        Logback配置文件的使用
            在resources下面,创建一份配置文件,命名为logback.xml
            一切配置都是在根标签中进行操作的
            <configuration>
            </configuration>
     */
    Logger logger = LoggerFactory.getLogger(LogbackTest.class);
    logger.error("error信息");
    logger.warn("warn信息");
    logger.info("info信息");
    logger.debug("debug信息");
    logger.trace("trace信息");
}

6.3.3.运行测试

6.4.Logback将日志信息保存到文件中

6.4.1.配置文件logback.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!--1.根标签叫做configuration,一切配置都是在根标签中进行操作的-->
<configuration>
    <property name="pattern_1" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L %thread %m%n"/>

    <!-- 2.配置文件的输出路径-->
    <property name="LogDir" value="D://logs"/>

    <!-- 3.配置文件的appender-->
    <appender name="fileAppender" class="ch.qos.logback.core.FileAppender">
        <!-- 3.1.引入文件的位置-->
        <file>${LogDir}/logback.log</file>
        <!-- 3.2.设置输出格式-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern_1}</pattern>
        </encoder>
    </appender>
    <!--
        4.配置日志记录器
        配置root logger
        level:配置日志级别
        可以同时配置多个appender,做日志的多方向输出
    -->
    <root level="ALL">
        <!-- 4.1.引入appender -->
        <!--<appender-ref ref="consoleFilterAppender"/>-->
        <appender-ref ref="fileAppender"/>
    </root>
</configuration>

6.4.2.业务代码

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogbackTest {
    Logger logger = LoggerFactory.getLogger(LogbackTest.class);
    public void test03() {
        /*
            在实际的生产环境中,我们更希望将日志信息保留在文件中
            在文件中,默认是以追加日志的形式做记录
         */
        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");
    }
}

6.4.3.运行测试

在文件中,默认是以追加日志的形式做记录

6.5.Logback将日志信息保存到html文件中

6.5.1.配置文件logback.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!--1.根标签叫做configuration,一切配置都是在根标签中进行操作的-->
<configuration>
    <property name="pattern_2" value="[%-5level]%d{yyyy-MM-dd HH:mm:ss.SSS}%c%M%L%thread%m%n"></property>

    <!--    配置文件的输出路径-->
    <property name="LogDir" value="D://logs"/>

    <!--    配置保存日志信息到html文件的appender-->
    <appender name="htmlFileAppender" class="ch.qos.logback.core.FileAppender">
        <!-- 配置保存日志文件的位置-->
        <file>${LogDir}/logback.html</file>
        <!--  设置输出格式-->
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <!-- 格式引用通用属性配置 -->
                <pattern>${pattern_2}</pattern>
            </layout>
        </encoder>
    </appender>

    <!--
        8.配置日志记录器
        配置root logger
        level:配置日志级别
        可以同时配置多个appender,做日志的多方向输出
    -->
    <root level="ALL">
        <appender-ref ref="htmlFileAppender"/>
    </root>
</configuration>

6.5.2.业务代码

同6.4.2.

6.5.3.运行测试

6.6.Logback日志拆分

6.6.1.配置文件logback.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!--1.根标签叫做configuration,一切配置都是在根标签中进行操作的-->
<configuration>
    <property name="pattern_1" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L %thread %m%n"/>

    <!--    配置文件的输出路径-->
    <property name="LogDir" value="D://logs"/>

    <!--    配置文件的appender,将日志文件拆分归档保存-->
    <appender name="roll" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 输出格式-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!-- 引用通用属性配置 -->
            <pattern>${pattern_1}</pattern>
        </encoder>
        <!-- 配置保存日志文件的位置-->
        <file>${LogDir}/roll_logback.log</file>
        <!-- 指定拆分规则-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 按照时间和压缩格式声明文件名 压缩格式gz -->
            <fileNamePattern>${LogDir}/roll.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
            <!-- 按照文件大小来进行拆分 -->
            <maxFileSize>10KB</maxFileSize>
        </rollingPolicy>
    </appender>
    <!--
        8.配置日志记录器
        配置root logger
        level:配置日志级别
        可以同时配置多个appender,做日志的多方向输出
    -->
    <root level="ALL">
        <!-- 9.引入appender -->
        <appender-ref ref="roll"/>
    </root>
</configuration>

6.6.2.业务代码

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogbackTest {
    Logger logger = LoggerFactory.getLogger(LogbackTest.class);

    public void test05() {
        /*
            日志拆分和归档压缩
                重要标签来源:
                查看源码
                RollingFileAppender类中找到rollingPolicy属性
                SizeAndTimeBasedRollingPolicy类中找到maxFileSize属性
                这些属性在类中都是以set方法的形式进行的赋值
                我们在配置文件中配置的信息,其实找到的都是这些属性的set方法

                在TimeBasedRollingPolicy找到
                static final String FNP_NOT_SET =
                "The FileNamePattern option must be set before using TimeBasedRollingPolicy. ";

                只要我们要使用到日志的拆分
                FileNamePattern属性是必须要使用到了
         */

        for (int i = 0; i < 1000; i++) {
            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");
        }
    }
}

6.6.3.运行结果

6.7.Logback通过过滤器更细粒度控制日志

6.7.1.配置文件logback.xml

可以在appender中添加过滤器,以此对日志进行更细粒度的打印。

<?xml version="1.0" encoding="UTF-8" ?>
<!--1.根标签叫做configuration,一切配置都是在根标签中进行操作的-->
<configuration>
    <property name="pattern_1" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L %thread %m%n"/>

    <!-- 配置控制台的appender 使用过滤器 -->
    <appender name="consoleFilterAppender" class="ch.qos.logback.core.ConsoleAppender">
        <target>
            System.err
        </target>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern_1}</pattern>
        </encoder>
        <!--        配置过滤器-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 设置日志的输出级别 -->
            <level>ERROR</level>
            <!-- 高于level中设置的级别,则打印日志 -->
            <onMatch>ACCEPT</onMatch>
            <!-- 低于level中设置的级别,则屏蔽日志 -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!--
        8.配置日志记录器
        配置root logger
        level:配置日志级别
        可以同时配置多个appender,做日志的多方向输出
    -->
    <root level="ALL">
        <!-- 9.引入appender -->
        <appender-ref ref="consoleFilterAppender"/>
    </root>
</configuration>

6.7.2.业务代码

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogbackTest {
	Logger logger = LoggerFactory.getLogger(LogbackTest.class);
    
    public void test06() {
//        我们可以在appender中添加过滤器,以此对日志进行更细粒度的打印
        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");
    }
}

6.7.3.运行测试

6.8.Logback日志异步记载

6.8.1.基础概念

按照当前的代码执行顺序,代码肯定是按照从上向下的顺序执行。上面的代码完全执行完毕后,才会执行下面的代码。

由此得出会出现的问题:

只要是在记录日志,那么系统本身的功能就处于一种停滞的状态;当日志记录完毕后,才会执行其他的代码。

如果日志记录量非常庞大的话,那么我们对于系统本身业务代码的执行效率会非常低。所以logback为我们提供了异步日志的功能。

配置方式:

  1. 配置异步日志

    在异步日志中引入我们真正需要输出的appender
    <appender name="asyncAppender" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="consoleAppender"/>
    </appender>
    
  2. 在rootlogger下引入异步日志

    <appender-ref ref="asyncAppender"/>
    

所谓异步日志的原理是:系统会为日志操作单独的分配出来一根线程,原来用来执行当前方法的主线程会继续向下执行。

线程1:系统业务代码执行

线程2:打印日志

两根线程争夺CPU的使用权

在实际项目开发中,越大的项目对于日志的记录就越庞大,为了保证项目的执行效率,异步日志是一个很好的选择。

6.8.2.测试

6.8.2.1.业务代码
public void test07() {

    Logger logger = LoggerFactory.getLogger(LogbackTest.class);
    //日志打印操作
    int num=0;
    for (int i = 0; i < 100; i++) {
        num+=i;
        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");
    }
    System.out.println(num);
    //系统本身业务相关的其他操作
    System.out.println("1----------------------");
    System.out.println("2----------------------");
    System.out.println("3----------------------");
    System.out.println("4----------------------");
    System.out.println("5----------------------");
}
6.8.2.2.非异步记录日志结果
6.8.2.3.logback.xml配置异步记载日志
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <property name="pattern_1" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L %thread %m%n"/>
    
    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <target>System.err</target>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern_1}</pattern>
        </encoder>
    </appender>

    <appender name="asyncAppender" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="consoleAppender"/>
    </appender>
    <root level="ALL">
        <appender-ref ref="asyncAppender"/>
    </root>
</configuration>
6.8.2.4.异步记载日志结果

6.10.自定义Logger

6.10.1.配置文件logback.xml

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <property name="pattern_1" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L %thread %m%n"/>

    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <target>System.err</target>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern_1}</pattern>
        </encoder>
    </appender>

    <!-- additivity="false",表示不继承rootlogger -->
    <logger name="com.stonebridge.LogbackTest" level="info" additivity="false">
        <!-- 在自定义logger中配置appender -->
        <appender-ref ref="consoleAppender"/>
    </logger>
</configuration>

6.10.2.业务代码

package com.stonebridge;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogbackTest {
	Logger logger = LoggerFactory.getLogger(LogbackTest.class);

    public void test08() {
        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");
    }
}

6.10.3.运行测试

6.11.关于logback补充

6.13.1.异步日志

可配置属性
配置了一个阈值
当队列的剩余容量小于这个阈值的时候,当前日志的级别 trace、debug、info这3个级别的日志将被丢弃
设置为0,说明永远都不会丢弃trace、debug、info这3个级别的日志
<discardingThreshold>0</discardingThreshold>
配置队列的深度,这个值会影响记录日志的性能,默认值就是256
<queueSize>256</queueSize>
关于这两个属性,一般情况下,我们使用默认值即可
不要乱配置,会影响系统性能,了解其功能即可

6.13.2.关于不同的日志实现,配置文件也是不同的

log4j经常使用的是properties属性文件
logback使用的是xml配置文件

如果我们遇到了一种情况,就是需要将以前的log4j,改造为使用logback
应该如何处理
我们可以使用工具

访问logback官网
找到log4j.properties转换器

只要是二者兼容的技术,才会被翻译
如果是log4j独立的技术,logback没有,或者是有这个技术但是并不兼容转义
那么这个工具则不会为我们进行翻译
如果是遇到简单的配置,我们可以使用工具

7.LOG4J2

7.1.LOG4J2简介

Apache Log4j 2 是对Log4j的升级,它比其前身Log4j 1.x 提供了重大改进,并提供了Logback中可用的许多改进,同时修复了 Logback 架构中的一些问题。被誉为是目前最优秀的 Java 日志框架

7.2.LOG4J2特征

  1. 性能提升

    Log4j2 包含基于 LMAX Disruptor 库的下一代异步记录器。在多线程场景中,异步记录器 的吞吐量比 Log4j 1.x 和 Logback 高 18 倍,延迟低。

  2. 自动重新加载配置

    与 Logback 一样,Log4j2可以在修改时自动重新加载其配置。与Logback不同,它会在重新配置发生时不会丢失日志事件。

  3. 高级过滤

    与 Logback 一样,Log4j2支持基于Log事件中的上下文数据,标记,正则表达式和其他组件进行过滤。 此外,过滤器还可以与记录器关联。与Logback不同,Log4j2可以在任何这些情况下使用通用的Filter类。

  4. 插件架构

    Log4j使用插件模式配置组件。因此,您无需编写代码来创建和配置 Appender,Layout, Pattern Converter 等。在配置了的情况下,Log4j 自动识别插件并使用它们。

  5. 无垃圾机制

    在稳态日志记录期间,Log4j2 在独立应用程序中是无垃圾的,在 Web 应用程序中是低垃 圾。这减少了垃圾收集器的压力,并且可以提供更好的响应性能。

目前市面上最主流的日志门面就是 SLF4J,虽然 Log4j2 也是日志门面,因为它的日志实 现功能非常强大,性能优越。所以我们一般情况下还是将 Log4j2 看作是日志的实现 SLF4j + Log4j2 的组合,是市场上最强大的日志功能实现方式,绝对是未来的主流趋势。

7.3.入门案例实现

7.3.1.引入依赖

<!-- log4j2日志门面 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.17.1</version>
</dependency>
<!-- log4j2日志实现 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.17.1</version>
</dependency>

7.3.2.业务代码

package com.stonebridge;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4j2Test {
    Logger logger = LogManager.getLogger(Log4j2Test.class);
    
    public void test01() {
       
        logger.fatal("fatal信息");
        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");
    }
}

7.3.3.运行测试

此时未引入门面技术,使用的是log4j的api。

7.4.LOG4J2配置文件log4j2.xml

7.4.1.概念解析

使用配置文件

log4j2是参考logback创作出来的,所以配置文件也是使用xml。

log4j2同样是默认加载类路径(resources)下的log4j2.xml文件中的配置

根标签,所有日志相关信息,都是在根标签中进行配置

<Configuration status="debug" monitorInterval="数值"></Configuration>

在根标签中,可以加属性

  • status=“debug” 日志框架本身的日志输出级别
  • monitorInterval=“5” 自动加载配置文件的间隔时间,不低于5秒

7.4.2.配置文件案例

7.4.2.1.log4j2.xml文件配置
<?xml version="1.0" encoding="UTF-8" ?>
<!--<Configuration status="debug">-->
<Configuration>
    <!--  配置appender-->
    <Appenders>
        <Console name="consoleAppender" target="SYSTEM_ERR"/>
    </Appenders>
    <!--  配置logger-->
    <Loggers>
        <!--  配置rootLogger-->
        <Root level="trace">
            <!--  引用Appender-->
            <AppenderRef ref="consoleAppender"/>
        </Root>
    </Loggers>
</Configuration>
7.4.2.2.业务代码
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4j2Test {
    Logger logger = LogManager.getLogger(Log4j2Test.class);
    
    public void test01() {
        logger.fatal("fatal信息");
        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");
    }
}
7.4.2.3.运行测试

7.5.LOG4J2引入SLF4J搭配使用

虽然log4j2也是日志门面,但是现在市场的主流趋势仍然是slf4j,所以我们仍然需要使用slf4j作为日志门面,搭配log4j2强大的日志实现功能,进行日志的相关操作。

接下来我们配置的就是当今市场上的最强大,最主流的日志使用搭配方式:slf4j+log4j2

7.5.1.引入SLF4J和LOG4J2依赖

<dependencies>
    <!-- slf4j日志门面 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
    </dependency>
    <!-- log4j适配器 -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.12.1</version>
    </dependency>
    <!-- log4j2日志门面 -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.17.1</version>
    </dependency>
    <!-- log4j2日志实现 -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.17.1</version>
    </dependency>
</dependencies>

执行原理:slf4j的日志门面调用的是log4j2的日志门面,再由log4j2的日志门面调用log4j2日志实现;因此slf4j的日志门面和log4j2的日志门面的依赖都需要引入。

7.5.2业务代码

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Log4j2Test {
    Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
    
    public void test03() {
        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");
    }
}

此时在代码中已经完全用slf4j日志门面技术,隐藏了实际底层log4j2的日志实现框架。

7.5.3.运行测试

7.6.LOG4J2日志文件输出

7.6.1.配置log4j2.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!--<Configuration status="debug">-->
<Configuration>
    <!--    配置全局通用属性-->
    <properties>
        <property name="logDir">D://logs</property>
    </properties>
    <!--  配置appender-->
    <Appenders>
        <!--        配置控制台输出-->
        <!--        <Console name="consoleAppender" target="SYSTEM_ERR"/>-->
        <!--        配置文件输出-->
        <File name="fileAppender" fileName="${logDir}//log4j2.log">
            <!-- 配置文件输出格式 -->
            <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n"/>
        </File>
    </Appenders>
    <!--  配置logger-->
    <Loggers>
        <!--  配置rootLogger-->
        <Root level="trace">
            <!--  引用Appender-->
            <!--            <AppenderRef ref="consoleAppender"/>-->
            <AppenderRef ref="fileAppender"/>
        </Root>
    </Loggers>
</Configuration>

7.6.2.业务代码

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Log4j2Test {
    Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
    public void test04(){
        Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");
    }
}

7.6.3.运行测试

7.7.日志的拆分

7.7.1.配置log4j2.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!--<Configuration status="debug">-->
<Configuration>
    <!--    配置全局通用属性-->
    <properties>
        <property name="logDir">D://logs</property>
    </properties>
    <!--  配置appender-->
    <Appenders>
        <!--
         按照指定规则来拆分日志文件
         1.fileName:日志文件的名字
         2.filePattern:日志文件拆分后文件的命名规则
               $${date:yyyy-MM-dd}:根据日期当天,创建一个文件夹
                    例如:2021-01-01这个文件夹中,记录当天的所有日志信息(拆分出来的日志放在这个文件夹中)
                         2021-01-02这个文件夹中,记录当天的所有日志信息(拆分出来的日志放在这个文件夹中)
               rollog-%d{yyyy-MM-dd-HH-mm}-%i.log:为文件命名的规则:
                    %i表示序号,从0开始,目的是为了让每一份文件名字不会重复
        -->
        <RollingFile name="rollingFile" fileName="${logDir}//rollog.log"
                     filePattern="${logDir}/$${date:yyyy-MM-dd}/rollog-%d{yyyy-MM-dd-HH-mm}-%i.log">
            <!-- 日志消息格式 -->
            <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n"/>
            <!-- 拆分策略-->
            <Policies>
                <!-- 在系统启动时,触发拆分规则,产生一个日志文件 -->
                <OnStartupTriggeringPolicy/>

                <!-- 按照文件的大小进行拆分 -->
                <SizeBasedTriggeringPolicy size="10KB"/>
                <!-- 按照时间节点进行拆分 拆分规则就是filePattern-->
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <!-- 在同一目录下,文件的个数限制,如果超出了设置的数值,则根据时间进行覆盖,新的覆盖旧的规则-->
            <DefaultRolloverStrategy max="30"/>
        </RollingFile>
    </Appenders>
    <!--  配置logger-->
    <Loggers>
        <!--  配置rootLogger-->
        <Root level="trace">
            <AppenderRef ref="rollingFile"/>
        </Root>
    </Loggers>
</Configuration>

按照指定规则来拆分日志文件

  1. RollingFile标签的fileNames属性:日志文件的名字

  2. RollingFile标签的filePattern属性:日志文件拆分后文件的命名规则

    • $${date:yyyy-MM-dd}:根据日期当天,创建一个文件夹

    例如:2021-01-01这个文件夹中,记录当天的所有日志信息(拆分出来的日志放在这个文件夹中)

    ​ 2021-01-02这个文件夹中,记录当天的所有日志信息(拆分出来的日志放在这个文件夹中)

    • rollog-%d{yyyy-MM-dd-HH-mm}-%i.log:为文件命名的规则:

      %i表示序号,从0开始,目的是为了让每一份文件名字不会重复

7.7.2.业务代码

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Log4j2Test {
    Logger logger = LoggerFactory.getLogger(Log4j2Test.class);

    public void test05() {
        Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
        for (int i = 0; i < 2000; i++) {
            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");
        }
    }
}

7.7.3.运行测试

7.8.异步日志

异步日志是 log4j2 最大的特色,其性能的提升主要也是从异步日志中受益。

**Log4j2 提供了两种实现日志的方式,一个是通过 AsyncAppender,一个是通过 AsyncLogger, 分别对应前面我们说的Appender组件和 Logger组件。 **

注意这是两种不同的实现方式,在设计和源码上都是不同的体现。

7.8.1.AsyncAppender方式

是通过引用别的Appender来实现的,当有日志事件到达时,会开启另外一个线程来处理 它们。需要注意的是,如果在 Appender 的时候出现异常,对应用来说是无法感知的。 AsyncAppender 应该在它引用的 Appender 之后配置,默认使用 java.util.concurrent.ArrayBlockingQueue 实现而不需要其它外部的类库。 当使用此 Appender 的 时候,在多线程的环境下需要注意,阻塞队列容易受到锁争用的影响,这可能会对性能产生影 响。这时候,我们应该考虑使用无锁的异步记录器(AsyncLogger)。

7.8.2.AsyncLogger方式

AsyncLogger才是 log4j2 实现异步最重要的功能体现,也是官方推荐的异步方式。 它可以使得调用 Logger.log 返回的更快。你可以有两种选择:全局异步和混合异步。 全局异步:所有的日志都异步的记录,在配置文件上不用做任何改动,只需要在 jvm 启动 的时候增加一个参数即可实现。 混合异步:你可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵 活。虽然 Log4j2 提供以一套异常处理机制,可以覆盖大部分的状态,但是还是会有一小部分 的特殊情况是无法完全处理的,比如我们如果是记录审计日志(特殊情况之一),那么官方就 推荐使用同步日志的方式,而对于其他的一些仅仅是记录一个程序日志的地方,使用异步日志 将大幅提升性能,减少对应用本身的影响。 混合异步的方式需要通过修改配置文件来实现,使用 AsyncLogger 标记配置。

性能对比:

7.8.3.基于AsyncAppender的方式异步日志实现

异步日志实现(单独分配线程做日志的记录)

7.8.3.1.引入异步日志依赖
<!-- 异步日志依赖 -->
<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.4.4</version>
</dependency>
7.8.3.2.配置log4j2.xml

在Appenders标签中,配置Async异步记录日志。

<?xml version="1.0" encoding="UTF-8" ?>
<Configuration>
    <!--    配置全局通用属性-->
    <properties>
        <property name="logDir">D://logs</property>
    </properties>
    <!--  配置appender-->
    <Appenders>
        <Console name="consoleAppender" target="SYSTEM_OUT"/>
        <!-- 配置异步日志 -->
        <Async name="myAsync">
             <!-- 将控制台输出做异步的操作 -->
            <AppenderRef ref="consoleAppender"/>
        </Async>
    </Appenders>
    <Loggers>
        <!--  配置rootLogger-->
        <Root level="trace">
            <AppenderRef ref="myAsync"/>
        </Root>
    </Loggers>
</Configuration>

在过日志记录的过程中,所谓异步就是单独为记录日志分配出来一个独立的线程用来做日志记录操作。当前的程序进入到这个方法之后,主线程会跨过日志记录的一部分,去执行业务逻辑的一部分。这样会提高我们的日志记录效率。

7.8.3.3.业务代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Log4j2Test {
    Logger logger = LoggerFactory.getLogger(Log4j2Test.class);

    public void test06() {
        /*
            异步日志实现(单独分配线程做日志的记录)
                方式1:使用AsyncAppender的方式
                1.添加异步日志依赖
                2.在Appenders标签中,对于异步进行配置
                  使用Async标签
                3.rootlogger引用Async
         */
        //日志的记录
        for (int i = 0; i < 500; i++) {
            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");
        }

        //系统业务逻辑
        for (int i = 0; i < 200; i++) {
            System.out.println("------------------");
        }
    }
}
7.8.3.4.运行测试

7.8.4.AsyncLogger全局异步日志

所有的日志都是异步的日志记录,在配置文件上不用做任何的改动,只需要在类路径resources下添加一个log4j2.component.properties属性文件,做一步配置即可。

7.8.4.1.配置log4j2.component.properties
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
7.8.4.2.log4j2.xml(同7.8.3.2)
7.8.4.3.业务代码(同7.8.3.3)
7.8.4.4.运行测试

7.8.5.AsyncLogger混合异步日志

异步日志实现(单独分配线程做日志的记录)

混合异步:可以在应用中同时使用同步日志和异步日志,这使得日志的配置及输出会更加的灵活。

需求:假设有自定义的logger(例如:com.stonebridge)让自定义的logger是异步的,其他Logger做同步。

注意:在做测试前,一定要将全局的异步配置(log4j2.component.properties)注释掉

7.8.5.1.配置log4j2.xml
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration>
    <!--    配置全局通用属性-->
    <properties>
        <property name="logDir">D://logs</property>
    </properties>
    <!--  配置appender-->
    <Appenders>
        <!--        配置控制台输出-->
        <Console name="consoleAppender" target="SYSTEM_OUT"/>
    </Appenders>
    <!--  配置logger-->
    <Loggers>
        <AsyncLogger name="com.stonebridge" level="trace" includeLocation="false" additivity="false">
            <!-- 将控制台输出consoleAppender,设置为异步打印 -->
            <AppenderRef ref="consoleAppender"/>
        </AsyncLogger>
        <!--  配置rootLogger-->
        <Root level="trace">
            <!--  引用Appender-->
            <AppenderRef ref="consoleAppender"/>
        </Root>
    </Loggers>
</Configuration>

通过标签AsyncLogger自定义logger,让自定义的logger为异步logger

  • includeLocation=“false”:表示去除日志记录中的行号信息,这个行号信息非常的影响日志记录的效率(生产中都不加这个行号),严重的时候可能记录的比同步的日志效率还有低。
  • additivity=“false”:表示不继承rootlogger

无论是否在Root下引入consoleAppender,com.stonebridge包下的所有日志都只会异步执行一次。

对于当前的logger(com.stonebridge.Log4j2Test01.class), Log4j2Test01本身就是在我们自定义的logger路径下的。因此com.stonebridge包下的所有日志都只会异步执行一次。

注意:

如果使用异步日志 AsyncAppender、AsyncLogger不要同时出现,没有这个需求,效果也不会叠加;如果同时出现,那么效率会以AsyncAppender为主;

AsyncLogger的全局异步和混合异步也不要同时出现,没有这个需求,效果也不会叠加

对于当前的logger(Log4j2Test01.class)Log4j2Test01本身就是在我们自定义的logger路径下的

7.8.5.2.业务代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Log4j2Test {
    Logger logger = LoggerFactory.getLogger(Log4j2Test.class);

    public void test08() {
      
        //日志的记录
        for (int i = 0; i < 2000; i++) {
            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");
        }
        //系统业务逻辑
        for (int i = 0; i < 1000; i++) {
            System.out.println("------------------");
        }
    }
}
7.8.5.3.运行测试

8.Spring Boot日志实现

SpringBoot 是现今市场上最火爆用来简化 spring 开发的框架,springboot 日志也是开发中 常用的日志系统。

SpringBoot 默认就是使用 SLF4J 作为日志门面,Logback 作为日志实现来记录日志。

8.1.Spring Boot日志依赖解析

找到SpringBoot项目核心配置文件 pom.xml

springboot web 启动器的坐标

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

在这个web启动器中间接依赖着日志相关的依赖slf4j和logback

以此证明日志使用的就是 slf4j 日志门面技术

8.2.SpringBoot日志打印级别

8.2.1.业务代码

public void test01() {
    logger.error("error信息");
    logger.warn("warn信息");
    logger.info("info信息");
    logger.debug("debug信息");
    logger.trace("trace信息");
}

8.2.2.运行测试

30223256633

springboot日志具体实现特点:

  1. 默认是info级别
  2. logback的风格输出(默认使用的是logback的日志实现)

8.3.springboot测试log4j2桥接器

8.3.1.业务代码

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class TestLog {

    public void test02() {
        /*
        桥接的作用:相当于之前使用的是log4j2,实际底层仍然是slf4j+logback,证明桥接器是起作用的。
         */
        Logger logger = LogManager.getLogger(TestLog.class);
        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");
    }
}

8.3.2.运行测试

桥接的作用:相当于之前使用的是log4j2,现在实际底层已经切换到slf4j+logback,证明桥接器是起作用的。

8.4.使用application.properties配置日志信息

8.4.1.application.properties

logging.level.com.stonebridge.springbootlog=trace
logging.pattern.console=%d{yyyy-MM-dd} [%level] -%m%n
#将日志输出到文件中
#使用logging.file.path来配置文件路径下的文件夹(logging.file直接配置文件的形式已经过时,不使用)
#在配置的文件夹下,日志文件生成的名字为spring.log
logging.file.path=D:/logs/springbootlog

8.4.2.业务代码

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestLog {
    public void test03() {
        /*
            application.properties(yml)是springboot的核心配置文件(用来简化开发使用)我们也可以通过该配置文件,修改日志相关的配置(简单)。
         */
        Logger logger = LoggerFactory.getLogger(TestLog.class);
        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");
    }
}

8.4.3.运行测试

application.properties(yml)是springboot的核心配置文件(用来简化开发使用)我们也可以通过该配置文件,修改日志相关的配置(简单)。

8.5.logback日志拆分

8.5.1.logback.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L %thread %m%n"></property>
    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <target>
            System.err
        </target>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <!-- 配置文件的appender 可拆分归档的文件 -->
    <appender name="roll" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 输入格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!-- 引入文件位置 -->
        <file>D:/logs/roll_logback.log</file>
        <!-- 指定拆分规则 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 按照时间和压缩格式声明文件名 压缩格式gz -->
            <fileNamePattern>D:/logs/roll.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
            <!-- 按照文件大小来进行拆分 -->
            <maxFileSize>1KB</maxFileSize>
        </rollingPolicy>
    </appender>
    <logger name="com.stonebridge" level="info" additivity="false">
        <appender-ref ref="roll"/>
    </logger>
</configuration>

8.5.2.业务代码

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestLog {
    Logger logger = LoggerFactory.getLogger(TestLog.class);
    
    public void test05() {
        /*
            如果是需要配置日志拆分等相对高级的功能
            那么application.properties就达不到需要了
            需要使用日志实现相应的配置文件
            
            例如我们现在使用的是logback日志实现
            那么就需要在类路径resources下,配置logback.xml
         */
        for (int i = 0; i < 2000; i++) {
            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");
        }
    }
}

8.5.3.运行测试

8.6.SpringBoot日志集成log4j2

8.6.1.移除启动器默认的logback依赖

所以需要将之前的环境中,logback的依赖去除掉

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <!-- 排除掉原始依赖 以此去除logback引用 -->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

8.6.2.添加log4j2依赖

<!-- 添加log4j2依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

8.6.3.配置log4j2.xml文件

类路径resources下配置log4j2.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Appenders>
        <!-- 配置控制台输出 -->
        <Console name="consoleAppender" target="SYSTEM_ERR"/>
    </Appenders>
    <!-- 配置logger -->
    <Loggers>
        <Root level="info">
            <AppenderRef ref="consoleAppender"/>
        </Root>
    </Loggers>
</Configuration>

8.6.4.业务代码

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestLog1 {
    public void test06() {
    /*
        由于log4j2性能的强大
        当今市场上越来越多的项目选择使用slf4j+log4j2的组合
        springboot默认使用的是slf4j+logback的组合
        我们可以将默认的logback替换成为log4j2

        1.启动器依赖,间接的依赖logback
            所以需要将之前的环境中,logback的依赖去除掉
        2.添加log4j2依赖
        3.将log4j2的配置文件log4j2.xml导入到类路径resources下面
     */
        Logger logger = LoggerFactory.getLogger(TestLog.class);
        for (int i = 0; i < 2000; i++) {
            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");
        }
    }
}

8.6.5.运行测试

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值