Java日志框架学习--LogBack和Log4j2--下

2022-05-17 17:09:10 浏览数 (1)

Java日志框架学习--LogBack和Log4j2--下

  • Logback
    • Logback中的组件
    • Logback配置文件
    • 日志输出格式
    • 使用演示
    • 配置文件
      • 输出到控制台
      • 输出到控制台和文件
    • 输出到控制台,文件和html
    • 日志拆分
    • 过滤器
    • 异步日志
    • 自定义Logger
  • Log4j
    • Log4j2简介
    • Log4j2特征
    • 应用演示
    • 配置文件
    • slf4j加log4j2
    • 日志输出到文件
      • 日志拆分
    • 异步日志
      • AsyncAppender方式
      • AsyncLogger方式
      • AsyncAppender测试
      • AsyncLogger测试
      • 混合异步输出日志
      • 异步性能比较
  • SpringBoot整合日志框架
    • spring配置文件配置日志
    • spring配置文件配置日志输出到文件
    • spring引入logback的配置文件
    • Spring整合log4j2

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来访问日志的功能

Logback中的组件

  • Logger: 日志的记录器,主要用于存放日志对象,也可以定义日志类型、级别。
  • Appender:用于指定日志输出的目的地,目的地可以是控制台、文件、数据库等等。
  • Layout: 负责把事件转换成字符串,格式化的日志信息的输出。
  • 在Logback中Layout对象被封装在encoder中。
  • 也就是说我们未来使用的encoder其实就是Layout

Logback配置文件

Logback提供了3种配置文件

  • logback.groovy
  • logback-test.xml
  • logback.xml

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

日志输出格式

日志输出格式:

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

使用演示

代码语言:javascript复制
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.36</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.11</version>
        </dependency>

配置文件

输出到控制台

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
   <!--
    配置文件通用属性
    可以在当前配置文件中通过${name}的形式,取得value值
    我们在此指定通用的日志输出格式
    日志输出格式:
    %-10level  级别 案例为设置10个字符,左对齐
    %d{yyyy-MM-dd HH:mm:ss.SSS} 日期
    %c  当前类全限定名
    %M  当前执行日志的方法
    %L  行号
    %thread 线程名称
    %m或者%msg    信息
    %n  换行
         -->

   <property name="pattern" value="[%-5level]  %d{yyyy-MM-dd HH:mm:ss:SSS} %c %M %L %thread %m%n"></property>

   <!--
      配置控制台输出的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>


     <!--
        日志记录器
        配置Root Logger
         level: 日志级别
        -->
     <root level="all">
         <appender-ref ref="console"/>
     </root>
</configuration>

输出到控制台和文件

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!--
     配置文件通用属性
     可以在当前配置文件中通过${name}的形式,取得value值
     我们在此指定通用的日志输出格式
     日志输出格式:
     %-10level  级别 案例为设置10个字符,左对齐
     %d{yyyy-MM-dd HH:mm:ss.SSS} 日期
     %c  当前类全限定名
     %M  当前执行日志的方法
     %L  行号
     %thread 线程名称
     %m或者%msg    信息
     %n  换行
          -->

    <property name="pattern" value="[%-5level]  %d{yyyy-MM-dd HH:mm:ss:SSS} %c %M %L %thread %m%n"></property>

    <!--  配置文件的输出路径   -->
    <property name="dir" value="logback.log"></property>


    <!-- 配置文件输出的appender   -->
    <appender name="file" class="ch.qos.logback.core.FileAppender">
        <!-- 引入文件位置   -->
        <file>${dir}</file>

        <!--
          配置日志输出格式
       -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--  格式引用通用属性配置  -->
            <pattern>${pattern}</pattern>
        </encoder>

    </appender>

    <!--
       配置控制台输出的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>


    <!--
       日志记录器
       配置Root Logger
        level: 日志级别
       -->
    <root level="all">
        <appender-ref ref="file"/>
        <appender-ref ref="console"/>
    </root>
</configuration>

因为符合OGNL规范,因此配置文件中可以配置的属性,大多可以通过翻看对应的类源码,通过set方法或者属性名推测出来

输出到控制台,文件和html

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!--
     配置文件通用属性
     可以在当前配置文件中通过${name}的形式,取得value值
     我们在此指定通用的日志输出格式
     日志输出格式:
     %-10level  级别 案例为设置10个字符,左对齐
     %d{yyyy-MM-dd HH:mm:ss.SSS} 日期
     %c  当前类全限定名
     %M  当前执行日志的方法
     %L  行号
     %thread 线程名称
     %m或者%msg    信息
     %n  换行
          -->

    <property name="pattern" value="[%-5level]  %d{yyyy-MM-dd HH:mm:ss:SSS} %c %M %L %thread %m%n"></property>

    <!--  配置文件的输出路径   -->
    <property name="fileDir" value="logback.log"></property>

    <!--  配置输出为html形式的appender   -->
    <appender name="html" class="ch.qos.logback.core.FileAppender">
         <!-- 输出的文件位置 -->
        <file>logback.html</file>
         <!--  设置layout为输出html形式的  -->
         <layout class="ch.qos.logback.classic.html.HTMLLayout">
             <pattern>
                 ${pattern}
             </pattern>
         </layout>
    </appender>

    <!-- 配置文件输出的appender   -->
    <appender name="file" class="ch.qos.logback.core.FileAppender">
        <!-- 引入文件位置   -->
        <file>${fileDir}</file>

        <!--
          配置日志输出格式
       -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--  格式引用通用属性配置  -->
            <pattern>${pattern}</pattern>
        </encoder>

    </appender>

    <!--
       配置控制台输出的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>


    <!--
       日志记录器
       配置Root Logger
        level: 日志级别
       -->
    <root level="all">
        <appender-ref ref="file"/>
        <appender-ref ref="console"/>
        <appender-ref ref="html"/>
    </root>
</configuration>

html展示效果如果:

日志拆分

日志拆分使用的是RollingFileAppender,它继承了FileAppender,因此父类中能配置的属性,这里都可以配置,我们只需要关注当前类新增的查日志拆分策略属性即可。

在当前RollingFileAppender中也为我们提供了两个设置日志拆分策略的set方法:

先看一波如何配置,再来分析源码:

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!--
     配置文件通用属性
     可以在当前配置文件中通过${name}的形式,取得value值
     我们在此指定通用的日志输出格式
     日志输出格式:
     %-10level  级别 案例为设置10个字符,左对齐
     %d{yyyy-MM-dd HH:mm:ss.SSS} 日期
     %c  当前类全限定名
     %M  当前执行日志的方法
     %L  行号
     %thread 线程名称
     %m或者%msg    信息
     %n  换行
          -->

    <property name="pattern" value="[%-5level]  %d{yyyy-MM-dd HH:mm:ss:SSS} %c %M %L %thread %m%n"></property>

    <!--  配置文件的输出路径   -->
    <property name="fileDir" value="logback.log"></property>

    <!--  配置输出为html形式的appender   -->
    <appender name="html" class="ch.qos.logback.core.FileAppender">
         <!-- 输出的文件位置 -->
        <file>logback.html</file>
         <!--  设置layout为输出html形式的  -->
         <layout class="ch.qos.logback.classic.html.HTMLLayout">
             <pattern>
                 ${pattern}
             </pattern>
         </layout>
    </appender>

    <!-- 配置文件输出的appender   -->
    <appender name="file" class="ch.qos.logback.core.FileAppender">
        <!-- 引入文件位置   -->
        <file>${fileDir}</file>

        <!--
          配置日志输出格式
       -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--  格式引用通用属性配置  -->
            <pattern>${pattern}</pattern>
        </encoder>

    </appender>

    <!--
       配置控制台输出的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="roll" class="ch.qos.logback.core.rolling.RollingFileAppender">
         <!--  设置输出格式    -->
         <layout class="ch.qos.logback.classic.PatternLayout">
             <pattern>${pattern}</pattern>
         </layout>
         <!--  设置未归档的日志文件输出位置   -->
         <file>roll_logback.log</file>

          <!--  指定拆分规则  -->
          <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
               <!--   按照时间和压缩格式声明文件名,压缩格式gz    -->
              <fileNamePattern>roll.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>

              <!--   按照文件大小来进行拆分 -->
              <maxFileSize>1KB</maxFileSize>
          </rollingPolicy>
    </appender>

    <!--
       日志记录器
       配置Root Logger
        level: 日志级别
       -->
    <root level="all">
        <appender-ref ref="roll"/>
    </root>
</configuration>

下面分析一波,这里源码流程从subAppend函数讲起,因为每一条日志的输出,都需要交给至少一个appender,完成日志的输出,而交给appender后,一定会来到appender的subAppend这里,各位可以自行debug源码流程

这里拿RollingFileAppender进行讲解:

代码语言:javascript复制
    @Override
    protected void subAppend(E event) {
        //日志真正写入前,现需要判断是否应该触发当前日志文件的归档行为
        //因为可能当前写入数据超过了日志文件大小的限制,那么当前日志就应该归档,再创建一个新的日志文件
        // The roll-over check must precede actual writing. This is the
        // only correct behavior for time driven triggers.
      
        // We need to synchronize on triggeringPolicy so that only one rollover
        // occurs at a time
        //加锁确保一次只有一个线程进行日志roll操作
        synchronized (triggeringPolicy) {
        //triggeringPolicy负责判断是否需要进行roll over
            if (triggeringPolicy.isTriggeringEvent(currentlyActiveFile, event)) {
                rollover();
            }
        }
        //调用父类方法真正执行写入操作 
        super.subAppend(event);
    }

具体回滚策略是如何执行的,这里不再进行分析

过滤器

代码语言:javascript复制
<?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>

    <!--  配置文件的输出路径   -->
    <property name="fileDir" value="logback.log"></property>

    <!--
       配置控制台输出的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>
               <!--     高于Level中设置的日志级别,则打印日志      -->
               <onMatch>ACCEPT</onMatch>
               <!--     低于Level中设置的日志级别,则跳过          -->
              <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!--
       日志记录器
       配置Root Logger
        level: 日志级别
       -->
    <root level="all">
        <appender-ref ref="console"/>
    </root>
</configuration>

通过过滤器,我们可以设置某个appender的日志过滤输出

过滤器链在何时会执行呢?

异步日志

代码语言:javascript复制
<?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="console" class="ch.qos.logback.core.ConsoleAppender">
        <target>
            System.err
        </target>
        
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>
    
    <!-- 异步日志的配置   -->
    <appender name="async" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="console"/>
    </appender>
    
    <root level="all">
        <appender-ref ref="async"/>
    </root>
</configuration>

异步日志测试:

异步日志实现源码来看看吧:

先说明一下,logback采用生产者消费者模型实现的日志异步输出

默认如果丢弃的话,会丢弃掉info以下的日志输出

自定义Logger

代码语言:javascript复制
    <logger name="com.dhy" level="info" additivity="false">
        <appender-ref ref="async"/>
    </logger>

name是包路径,包路径的设置是具有父子关系的

level是日志等级

additivity表示是否继承父类logger的配置

additivity为true,表示会继承父类的appenders,为false表示不继承父类的appenders

Log4j

Log4j2简介

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

Log4j2特征

  • 性能提升

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

  • 自动重新加载配置

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

  • 高级过滤

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

  • 插件架构

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

  • 无垃圾机制

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

目前市面上最主流的日志门面就是SLF4J,虽然Log4j2 也是日志门面,因为它的日志实现功能非常强大,性能优越。所以我们一般情况下还是将 Log4j2 看作是日志的实现

SLF4j Log4j2 的组合,是市场上最强大的日志功能实现方式,绝对是未来的主流趋势。

应用演示

引入依赖:

代码语言:javascript复制
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.12.1</version>
</dependency>

可以设置jdk编译版本:

代码语言:javascript复制
<build>
    <plugins>
        <!-- 设置编译版本为1.8 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
            </configuration>
        </plugin>
    </plugins>
</build>

使用演示:

代码语言:javascript复制
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.annotations.Test;

public class LogTest {
    @Test
    public void Test(){
        Logger logger = LogManager.getLogger(LogTest.class.getName());
            log(logger);
    }


    private void log(Logger logger) {
        logger.error("error");
        logger.warn("warn");
        logger.info("info");
        logger.debug("debug");
        logger.trace("trace");
    }
}

没有提供配置文件,会提供一个日志的默认配置,但是会给出一个警告

配置文件

log4j2的配置文件类似logback,会默认去加载类路径下的log4j2.xml

因为log4j2参考了logback的设计思路,但是未来减少借鉴量,就将原本logback配置文件中小写的标签值,都转为了大写

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8" ?>
<configuration  status="debug" monitorInterval="5">
<!--  配置appender  -->
 <Appenders>
<!--  默认是system.out,这里我们设置为system.err,这一点和logback一样    -->
     <Console name="console" target="SYSTEM_ERR"></Console>
 </Appenders>

<!-- 配置logger   -->
    <Loggers>
<!--   配置rootLogger     -->
    <Root level="trace">
        <AppenderRef ref="console"/>
    </Root>
    </Loggers>
</configuration>

默认输出格式为%m%n

slf4j加log4j2

我们只需要导入一个依赖就够了:

代码语言:javascript复制
        <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.17.2</version>
        </dependency>

这样一个适配器依赖模块以及将slf4j门面依赖,log4j门面和log4k2日志实现依赖全部导入了

使用还是正常使用slf4j的日志门面api即可

日志输出到文件

这里的使用和之前logback的配置类似

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8" ?>
<configuration monitorInterval="5">

<!--  公共属性  -->
 <properties>
     <property name="logDir">log4j2.log</property>
 </properties>

<!--  配置appender  -->
 <Appenders>
<!--  默认是system.out,这里我们设置为system.err,这一点和logback一样    -->
     <Console name="console" target="SYSTEM_ERR"></Console>
     <File name="file" fileName="${logDir}">
<!--       输出格式           -->
         <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n"></PatternLayout>
     </File>
 </Appenders>

<!-- 配置logger   -->
    <Loggers>
<!--   配置rootLogger     -->
    <Root level="trace">
        <AppenderRef ref="console"/>
        <AppenderRef ref="file"/>
    </Root>
    </Loggers>
</configuration>

文件默认也是追加写入

日志拆分

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8" ?>
<configuration monitorInterval="5">

<!--  公共属性  -->
 <properties>
     <property name="logDir">log4j2.log</property>
 </properties>

<!--  配置appender  -->
 <Appenders>
<!--  默认是system.out,这里我们设置为system.err,这一点和logback一样    -->
     <Console name="console" target="SYSTEM_ERR"></Console>
     <File name="file" fileName="${logDir}">
<!--       输出格式           -->
         <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n"></PatternLayout>
     </File>

     <RollingFile name="rollingFile" fileName="rollLog.log"
      filePattern="${date:yyyy-MM-dd}/roll-%d{yyyy-MM-dd-HH-mm}-%i.log">
         <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n"></PatternLayout>
         <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>

关于log4j2的配置文件解析源码,大家可以参考AbstractConfiguration

异步日志

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

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

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

AsyncAppender方式

是通过引用别的Appender来实现的,当有日志事件到达时,会开启另外一个线程来处理它们。

需要注意的是,如果在Appender的时候出现异常,对应用来说是无法感知的。

AsyncAppender应该在它引用的Appender之后配置,默认使用 java.util.concurrent.ArrayBlockingQueue实现而不需要其它外部的类库。

当使用此Appender的时候,在多线程的环境下需要注意,阻塞队列容易受到锁争用的影响,这可能会对性能产生影响。

这时候,我们应该考虑使用无锁的异步记录器(AsyncLogger)。

AsyncLogger方式

AsyncLogger才是log4j2实现异步最重要的功能体现,也是官方推荐的异步方式。

它可以使得调用Logger.log返回的更快。你可以有两种选择:全局异步和混合异步。

全局异步:所有的日志都异步的记录,在配置文件上不用做任何改动,只需要在jvm启动的时候增加一个参数即可实现。

混合异步:你可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵活。虽然Log4j2提供以一套异常处理机制,可以覆盖大部分的状态,但是还是会有一小部分的特殊情况是无法完全处理的,比如我们如果是记录审计日志(特殊情况之一),那么官方就推荐使用同步日志的方式,而对于其他的一些仅仅是记录一个程序日志的地方,使用异步日志将大幅提升性能,减少对应用本身的影响。

混合异步的方式需要通过修改配置文件来实现,使用AsyncLogger标记配置。

AsyncAppender测试

  • 引入异步日志依赖
代码语言:javascript复制
        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>3.3.7</version>
        </dependency>
  • 修改配置
代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8" ?>
<configuration monitorInterval="5">

<!--  公共属性  -->
 <properties>
     <property name="logDir">log4j2.log</property>
 </properties>

<!--  配置appender  -->
 <Appenders>
<!--  默认是system.out,这里我们设置为system.err,这一点和logback一样    -->
     <Console name="console" target="SYSTEM_ERR"></Console>
     <File name="file" fileName="${logDir}">
<!--       输出格式           -->
         <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n"></PatternLayout>
     </File>
     
     <Async name="async">
         <AppenderRef ref="console"></AppenderRef>
     </Async>
 </Appenders>

<!-- 配置logger   -->
    <Loggers>
<!--   配置rootLogger     -->
    <Root level="trace">
        <AppenderRef ref="async"/>
    </Root>
    </Loggers>
</configuration>
  • 测试异步效果
代码语言:javascript复制
public class LogTest {
    @Test
    public void Test(){
        Logger logger = LoggerFactory.getLogger(LogTest.class.getName());
            log(logger);
        System.out.println("1111");
        System.out.println("3333");
        System.out.println("2222");
    }


    private void log(Logger logger) {
        logger.error("error");
        logger.warn("warn");
        logger.info("info");
        logger.debug("debug");
        logger.trace("trace");
    }
}

AsyncLogger测试

代码语言:javascript复制
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

配置文件去掉async的配置

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8" ?>
<configuration monitorInterval="5">

<!--  配置appender  -->
 <Appenders>
<!--  默认是system.out,这里我们设置为system.err,这一点和logback一样    -->
     <Console name="console" target="SYSTEM_ERR"></Console>
     <File name="file" fileName="${logDir}">
<!--       输出格式           -->
         <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n"></PatternLayout>
     </File>
 </Appenders>

<!-- 配置logger   -->
    <Loggers>
<!--   配置rootLogger     -->
    <Root level="trace">
        <AppenderRef ref="console"/>
    </Root>
    </Loggers>
</configuration>

测试

混合异步输出日志

代码语言:javascript复制
<!--    配置异步Logger    -->
    <AsyncLogger name="com" level="info"
      includeLocation="false" additivity="false">
        <AppenderRef ref="console"/>
    </AsyncLogger>

异步性能比较

SpringBoot整合日志框架

springboot提供对日志的整合模块主要是spring-boot-starter-logging模块

首先引入了logback-classic,可以看出springboot默认使用的日志框架为logback

下面还有两个:一个是log4j-to-slf4j:2.14.1,这个是桥接器模块,因为该模块里面只有log4j2的门面api,这里还是通过api重定向,将log4j2的api使用,重定向到slf4j,然后slf4j底层再使用logback作为底层日志框架实现

jul-to-slf4j类似原理

下面来验证两点,一点是springboot默认使用的是logback作为日志选型,另一点是我们使用log4j2和jul的日志api,最终底层调用的还是logback

代码语言:javascript复制
package helper.com.logTest;



import org.apache.logging.log4j.LogManager;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;


/**
 * @author 大忽悠
 * @create 2022/5/14 10:28
 */
public class Log4jTest {
    @Test
    public void testLogBack() {
        System.err.println("------------testLogBack---------------");
        Logger logger = LoggerFactory.getLogger(Log4jTest.class);
        logger.error("error");
        logger.warn("warn");
        logger.info("info");
        logger.debug("debug");
        logger.trace("trace");
        System.err.println("-----------testLog4j2Bridge----------------");
        org.apache.logging.log4j.Logger log = LogManager.getLogger(Log4jTest.class);
        log.error("error");
        log.warn("warn");
        log.info("info");
        log.debug("debug");
        log.trace("trace");
        System.err.println("-----------testJULBridge----------------");
        java.util.logging.Logger julLog= java.util.logging.Logger.getLogger(Log4jTest.class.getName());
        julLog.addHandler(new SLF4JBridgeHandler());
        julLog.severe("severe");
        julLog.warning("warning");
        julLog.info("info");
        julLog.fine("fine");
        julLog.finer("finer");
        julLog.finest("finest");

    }

}

JUL的桥接需要手动添加handler完成,该handler的publish方法源码如下:

代码语言:javascript复制
    public void publish(LogRecord record) {
        if (record != null) {
            org.slf4j.Logger slf4jLogger = this.getSLF4JLogger(record);
            String message = record.getMessage();
            if (message == null) {
                message = "";
            }

            if (slf4jLogger instanceof LocationAwareLogger) {
                this.callLocationAwareLogger((LocationAwareLogger)slf4jLogger, record);
            } else {
                this.callPlainSLF4JLogger(slf4jLogger, record);
            }

        }
    }

spring配置文件配置日志

代码语言:javascript复制
logging:
   level:
     helper:
         com: trace
  pattern:
    console: "%d{yyyy年-MM月-dd日} [%-5level] %m%n"

spring配置文件配置日志输出到文件

代码语言:javascript复制
logging:
  level:
     helper:
         com: trace
  pattern:
    console: "%d{yyyy年-MM月-dd日} [%-5level] %m%n"
  file:
    path: test

指定的是存放日志文件夹的名字,日志会生成在该文件夹下面,名字为spring.log

spring引入logback的配置文件

放在类路径下,名字就叫logback.xml就可以了

代码语言:javascript复制
<?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>

    <!--  配置文件的输出路径   -->
    <property name="fileDir" value="logback.log"></property>
    
    <!--
       配置控制台输出的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="roll" class="ch.qos.logback.core.rolling.RollingFileAppender">
         <!--  设置输出格式    -->
         <layout class="ch.qos.logback.classic.PatternLayout">
             <pattern>${pattern}</pattern>
         </layout>
         <!--  设置未归档的日志文件输出位置   -->
         <file>roll_logback.log</file>

          <!--  指定拆分规则  -->
          <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
               <!--   按照时间和压缩格式声明文件名,压缩格式gz    -->
              <fileNamePattern>roll.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>

              <!--   按照文件大小来进行拆分 -->
              <maxFileSize>1KB</maxFileSize>
          </rollingPolicy>
    </appender>

    <!--
       日志记录器
       配置Root Logger
        level: 日志级别
       -->
    <root level="info">
        <appender-ref ref="roll"/>
        <appender-ref ref="console"/>
    </root>
</configuration>

Spring整合log4j2

  • 移除spring-boot-starter-logging模块依赖
  • 添加之前我们使用的log4j2的依赖即可
代码语言:javascript复制
        <!--        spring web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

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

下面引入log4j2的配置文件进行测试即可:

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8" ?>
<configuration  status="debug" monitorInterval="5">
    <!--  配置appender  -->
    <Appenders>
        <!--  默认是system.out,这里我们设置为system.err,这一点和logback一样    -->
        <Console name="console" target="SYSTEM_ERR"></Console>
    </Appenders>

    <!-- 配置logger   -->
    <Loggers>
        <!--   配置rootLogger     -->
        <Root level="info">
            <AppenderRef ref="console"/>
        </Root>
    </Loggers>
</configuration>

0 人点赞