日志框架(四)Log4j2

日志框架(四)Log4j2

Apache Log4j 2是对Log4j的升级版,参考了logback的一些优秀的设计,并且修复了一些问题,因此带来了一些重大的提升,主要有:

  • 异常处理,在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异常处理机制。
  • 性能提升, log4j2相较于log4j 和logback都具有很明显的性能提升,后面会有官方测试的数据。
  • 自动重载配置,参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产上可以动态的修改日志的级别而不需要重启应用。
  • 无垃圾机制,log4j2在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集导致的jvm gc。

官网: https://logging.apache.org/log4j/2.x/

1. Log4j2入门

目前市面上最主流的日志门面就是SLF4J,虽然Log4j2也是日志门面,因为它的日志实现功能非常强大,性能优越。所以大家一般还是将Log4j2看作是日志的实现,Slf4j + Log4j2应该是未来的大势所趋。

  1. 添加依赖

    <!-- Log4j2 门面API-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.11.2</version>
    </dependency>
    <!-- Log4j2 日志实现 -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.11.2</version>
    </dependency>
    
  2. 快速入门

    public class Log4j2Test {
    
        private static final Logger LOGGER = LogManager.getLogger(Log4j2Test.class);
    
        @Test
        public void test(String[] args) {
            LOGGER.trace("Test log4j2 info");
            LOGGER.info("Test log4j2 info");
            LOGGER.debug("Test log4j2 info");
            LOGGER.warn("Test log4j2 warn");
            LOGGER.error("Test log4j2 error");
            LOGGER.fatal("Test log4j2 error");
        }
    
    }
    
  3. 使用slf4j作为日志的门面,使用log4j2作为日志的实现

    <!-- Log4j2 门面API-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.11.2</version>
    </dependency>
    <!-- Log4j2 日志实现 -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.11.2</version>
    </dependency>
    <!--使用slf4j作为日志的门面,使用log4j2来记录日志 -->
     <dependency>
         <groupId>org.slf4j</groupId>
         <artifactId>slf4j-api</artifactId>
         <version>1.7.25</version>
    </dependency>
    <!--为slf4j绑定日志实现 log4j2的适配器 -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.9.1</version>
    </dependency>
    

2 Log4j2配置

log4j2默认加载classpath下的 log4j2.xml 文件中的配置。

<?xml version="1.0" encoding="UTF-8"?>
<!--
    status:  日志框架本身的输出日志级别
    monitorInterval: 自动加载配置文件的间隔时间
-->
<Configuration status="debug" monitorInterval="5">

    <Properties>
        <!-- 日志输出级别 -->
        <Property name="LOG_INFO_LEVEL" value="INFO"/>
        <!-- error级别日志 -->
        <Property name="LOG_ERROR_LEVEL" value="ERROR"/>
        <!-- 在当前目录下创建名为log目录做日志存放的目录 -->
        <Property name="LOG_HOME" value="./log4j2-demo/log"/>
        <!-- 档案日志存放目录 -->
        <Property name="LOG_ARCHIVE" value="./log4j2-demo/log/archive"/>
        <!-- 模块名称, 影响日志配置名,日志文件名,根据自己项目进行配置 -->
        <Property name="LOG_MODULE_NAME" value="log4j2-demo"/>
        <!-- 日志文件大小,超过这个大小将被压缩 -->
        <Property name="LOG_MAX_SIZE" value="100 MB"/>
        <!-- 保留多少天以内的日志 -->
        <Property name="LOG_DAYS" value="15"/>
        <!--输出日志的格式:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度, %msg:日志消息,%n是换行符 -->
        <Property name="LOG_PATTERN" value="%d [%t] %-5level %logger{0} - %msg%n"/>
        <!--interval属性用来指定多久滚动一次-->
        <Property name="TIME_BASED_INTERVAL" value="1"/>
    </Properties>

    <!--日志处理器-->
    <Appenders>
        <!-- 控制台输出 -->
        <Console name="STDOUT" target="SYSTEM_OUT">
            <!--输出日志的格式-->
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="${LOG_INFO_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
        </Console>

        <!-- 这个会打印出所有的info级别以上,error级别一下的日志,每次大小超过size或者满足TimeBasedTriggeringPolicy,
             则日志会自动存入按年月日建立的文件夹下面并进行压缩,作为存档
            -->
        <RollingRandomAccessFile name="RollingRandomAccessFileInfo"
                                 fileName="${LOG_HOME}/${LOG_MODULE_NAME}-info.log"
                                 filePattern="${LOG_ARCHIVE}/${LOG_MODULE_NAME}-infoLog-%d{yyyy-MM-dd}-%i.log.gz"
                                 immediateFlush="false"
                                 append="true">
            <Filters>
                <!--如果是error级别拒绝,设置 onMismatch="NEUTRAL" 可以让日志经过后续的过滤器-->
                <ThresholdFilter level="${LOG_ERROR_LEVEL}" onMatch="DENY" onMismatch="NEUTRAL"/>
                <!--如果是info\warn输出-->
                <ThresholdFilter level="${LOG_INFO_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!-- 系统启动的时候生成新的文件 -->
                <OnStartupTriggeringPolicy/>
                <!--interval属性用来指定多久滚动一次,根据当前filePattern设置是1天滚动一次-->
                <TimeBasedTriggeringPolicy interval="${TIME_BASED_INTERVAL}"/>
                <SizeBasedTriggeringPolicy size="${LOG_MAX_SIZE}"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认同一文件夹下最多保存7个文件-->
            <DefaultRolloverStrategy max="${LOG_DAYS}"/>
        </RollingRandomAccessFile>
    </Appenders>

    <Loggers>
        <Root level="${LOG_INFO_LEVEL}" includeLocation="false">
            <AppenderRef ref="STDOUT"/>
            <AppenderRef ref="RollingRandomAccessFileInfo"/>
        </Root>
    </Loggers>

</Configuration>

3. Log4j2异步日志

异步日志

log4j2最大的特点就是异步日志,其性能的提升主要也是从异步日志中受益,我们来看看如何使用log4j2的异步日志。

  • 同步日志

    在这里插入图片描述

  • 异步日志

在这里插入图片描述

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

注意:配置异步日志需要添加依赖

<!--高性能并发编程框架 log4j2 使用异步模式-->
<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.3.4</version>
</dependency>
  1. AsyncAppender方式

    <?xml version="1.0" encoding="UTF-8"?>
    <!--
        status:  日志框架本身的输出日志级别
        monitorInterval: 自动加载配置文件的间隔时间
    -->
    <Configuration status="debug" monitorInterval="5">
    
        <Properties>
            <!-- 日志输出级别 -->
            <Property name="LOG_INFO_LEVEL" value="INFO"/>
            <!-- error级别日志 -->
            <Property name="LOG_ERROR_LEVEL" value="ERROR"/>
            <!-- 在当前目录下创建名为log目录做日志存放的目录 -->
            <Property name="LOG_HOME" value="./log4j2-demo/log"/>
            <!-- 档案日志存放目录 -->
            <Property name="LOG_ARCHIVE" value="./log4j2-demo/log/archive"/>
            <!-- 模块名称, 影响日志配置名,日志文件名,根据自己项目进行配置 -->
            <Property name="LOG_MODULE_NAME" value="log4j2-demo"/>
            <!-- 日志文件大小,超过这个大小将被压缩 -->
            <Property name="LOG_MAX_SIZE" value="100 MB"/>
            <!-- 保留多少天以内的日志 -->
            <Property name="LOG_DAYS" value="15"/>
            <!--输出日志的格式:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度, %msg:日志消息,%n是换行符 -->
            <Property name="LOG_PATTERN" value="%d [%t] %-5level %logger{0} - %msg%n"/>
            <!--interval属性用来指定多久滚动一次-->
            <Property name="TIME_BASED_INTERVAL" value="1"/>
        </Properties>
    
        <!--日志处理器-->
        <Appenders>
            <!-- 控制台输出 -->
            <Console name="STDOUT" target="SYSTEM_OUT">
                <!--输出日志的格式-->
                <PatternLayout pattern="${LOG_PATTERN}"/>
                <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
                <ThresholdFilter level="${LOG_INFO_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
            </Console>
    
            <!-- 这个会打印出所有的info级别以上,error级别一下的日志,每次大小超过size或者满足TimeBasedTriggeringPolicy,
                 则日志会自动存入按年月日建立的文件夹下面并进行压缩,作为存档
                -->
            <RollingRandomAccessFile name="RollingRandomAccessFileInfo"
                                     fileName="${LOG_HOME}/${LOG_MODULE_NAME}-info.log"
                                     filePattern="${LOG_ARCHIVE}/${LOG_MODULE_NAME}-infoLog-%d{yyyy-MM-dd}-%i.log.gz"
                                     immediateFlush="false"
                                     append="true">
                <Filters>
                    <!--如果是error级别拒绝,设置 onMismatch="NEUTRAL" 可以让日志经过后续的过滤器-->
                    <ThresholdFilter level="${LOG_ERROR_LEVEL}" onMatch="DENY" onMismatch="NEUTRAL"/>
                    <!--如果是info\warn输出-->
                    <ThresholdFilter level="${LOG_INFO_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
                </Filters>
                <PatternLayout pattern="${LOG_PATTERN}"/>
                <Policies>
                    <!-- 系统启动的时候生成新的文件 -->
                    <OnStartupTriggeringPolicy/>
                    <!--interval属性用来指定多久滚动一次,根据当前filePattern设置是1天滚动一次-->
                    <TimeBasedTriggeringPolicy interval="${TIME_BASED_INTERVAL}"/>
                    <SizeBasedTriggeringPolicy size="${LOG_MAX_SIZE}"/>
                </Policies>
                <!-- DefaultRolloverStrategy属性如不设置,则默认同一文件夹下最多保存7个文件-->
                <DefaultRolloverStrategy max="${LOG_DAYS}"/>
            </RollingRandomAccessFile>
            <!--开启AsyncAppender异步-->
     		<Async name="Async">
               <AppenderRef ref="RollingRandomAccessFileInfo"/>
            </Async>
        </Appenders>
    
        <!--Logger自定义-->
        <Loggers>
            <Root level="${LOG_INFO_LEVEL}" includeLocation="false">
                <AppenderRef ref="STDOUT"/>
                <AppenderRef ref="RollingRandomAccessFileInfo"/>
            </Root>
        </Loggers>
    </Configuration>
    

    只需要子啊标签中注明开启就行。

  2. AsyncLogger方式

    AsyncLogger才是log4j2 的重头戏,也是官方推荐的异步方式。它可以使得调用Logger.log返回的更快。你可以有两种选择:全局异步和混合异步。

    • 全局异步就是,所有的日志都异步的记录,在配置文件上不用做任何改动,只需要添加一个log4j2.component.properties 配置;

      Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
      
    • 混合异步就是,你可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵活。

      <?xml version="1.0" encoding="UTF-8"?>
      <!--
          status:  日志框架本身的输出日志级别
          monitorInterval: 自动加载配置文件的间隔时间
      -->
      <Configuration status="debug" monitorInterval="5">
      
          <Properties>
              <!-- 日志输出级别 -->
              <Property name="LOG_INFO_LEVEL" value="INFO"/>
              <!-- error级别日志 -->
              <Property name="LOG_ERROR_LEVEL" value="ERROR"/>
              <!-- 在当前目录下创建名为log目录做日志存放的目录 -->
              <Property name="LOG_HOME" value="./log4j2-demo/log"/>
              <!-- 档案日志存放目录 -->
              <Property name="LOG_ARCHIVE" value="./log4j2-demo/log/archive"/>
              <!-- 模块名称, 影响日志配置名,日志文件名,根据自己项目进行配置 -->
              <Property name="LOG_MODULE_NAME" value="log4j2-demo"/>
              <!-- 日志文件大小,超过这个大小将被压缩 -->
              <Property name="LOG_MAX_SIZE" value="100 MB"/>
              <!-- 保留多少天以内的日志 -->
              <Property name="LOG_DAYS" value="15"/>
              <!--输出日志的格式:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度, %msg:日志消息,%n是换行符 -->
              <Property name="LOG_PATTERN" value="%d [%t] %-5level %logger{0} - %msg%n"/>
              <!--interval属性用来指定多久滚动一次-->
              <Property name="TIME_BASED_INTERVAL" value="1"/>
          </Properties>
      
          <!--日志处理器-->
          <Appenders>
              <!-- 控制台输出 -->
              <Console name="STDOUT" target="SYSTEM_OUT">
                  <!--输出日志的格式-->
                  <PatternLayout pattern="${LOG_PATTERN}"/>
                  <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
                  <ThresholdFilter level="${LOG_INFO_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
              </Console>
      
              <!-- 这个会打印出所有的info级别以上,error级别一下的日志,每次大小超过size或者满足TimeBasedTriggeringPolicy,
                   则日志会自动存入按年月日建立的文件夹下面并进行压缩,作为存档
                  -->
              <RollingRandomAccessFile name="RollingRandomAccessFileInfo"
                                       fileName="${LOG_HOME}/${LOG_MODULE_NAME}-info.log"
                                       filePattern="${LOG_ARCHIVE}/${LOG_MODULE_NAME}-infoLog-%d{yyyy-MM-dd}-%i.log.gz"
                                       immediateFlush="false"
                                       append="true">
                  <Filters>
       </Appenders>
          <!--Logger自定义-->
          <Loggers>
              <!--自定义异步Logger对象
                  includeLocation: 关闭行号信息,否则影响性能
                  additivity: 不继承RootLogger对象-->
              <AsyncLogger name="com.tzh" level="info" includeLocation="false" additivity="false">
                  <AppenderRef ref="RollingRandomAccessFileError"/>
              </AsyncLogger>
      
              <Root level="${LOG_INFO_LEVEL}" includeLocation="false">
                  <AppenderRef ref="STDOUT"/>
                  <AppenderRef ref="RollingRandomAccessFileInfo"/>
                  <AppenderRef ref="RollingRandomAccessFileError"/>
              </Root>
          </Loggers>
      
      </Configuration>
      
      

      如上配置: com.tzh日志是异步的,root日志是同步的。

使用异步日志需要注意的问题

  1. 如果使用异步日志,AsyncAppender、AsyncLogger和全局日志,不要同时出现。性能会和AsyncAppender一致,降至最低。

  2. 设置includeLocation=false ,打印位置信息会急剧降低异步日志的性能,比同步日志还要慢。

4. Log4j2的性能

Log4j2最牛的地方在于异步输出日志时的性能表现,Log4j2在多线程的环境下吞吐量与Log4j和Logback的比较如下图。下图比较中Log4j2有三种模式:1)全局使用异步模式;2)部分Logger采用异步模式;3)异步Appender。可以看出在前两种模式下,Log4j2的性能较之Log4j和Logback有很大的优势。

在这里插入图片描述

无垃圾记录

​ 垃圾收集暂停是延迟峰值的常见原因,并且对于许多系统而言,花费大量精力来控制这些暂停。

​ 许多日志库(包括以前版本的Log4j)在稳态日志记录期间分配临时对象,如日志事件对象,字符串,字符数组,字节数组等。这会对垃圾收集器造成压力并增加GC暂停发生的频率。

​ 从版本2.6开始,默认情况下Log4j以“无垃圾”模式运行,其中重用对象和缓冲区,并且尽可能不分配临时对象。还有一个“低垃圾”模式,它不是完全无垃圾,但不使用ThreadLocal字段。

​ Log4j 2.6中的无垃圾日志记录部分通过重用ThreadLocal字段中的对象来实现,部分通过在将文本转换为字节时重用缓冲区来实现。

使用Log4j 2.5:内存分配速率809 MB /秒,141个无效集合。

在这里插入图片描述

Log4j 2.6没有分配临时对象:0(零)垃圾回收。

在这里插入图片描述

有两个单独的系统属性可用于手动控制Log4j用于避免创建临时对象的机制:

  • log4j2.enableThreadlocals - 如果“true”(非Web应用程序的默认值)对象存储在ThreadLocal字段中并重新使用,否则将为每个日志事件创建新对象。

  • log4j2.enableDirectEncoders - 如果将“true”(默认)日志事件转换为文本,则将此文本转换为字节而不创建临时对象。注意: 由于共享缓冲区上的同步,在此模式下多线程应用程序的同步日志记录性能可能更差。如果您的应用程序是多线程的并且日志记录性能很重要,请考虑使用异步记录器。

5. SpringBoot中的日志使用

springboot框架在企业中的使用越来越普遍,springboot日志也是开发中常用的日志系统。springboot默认就是使用SLF4J作为日志门面,logback作为日志实现来记录日志。

5.1 SpringBoot中的日志设计

springboot中的日志依赖关系

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.0.3.RELEASE</version>
</dependency>

依赖关系图:

在这里插入图片描述

总结:

  1. springboot 底层默认使用logback作为日志实现。

  2. 使用了SLF4J作为日志门面

  3. 将JUL也转换成slf4j

  4. 也可以使用log4j2作为日志门面,但是最终也是通过slf4j调用logback

5.2 SpringBoot日志使用

  1. 在springboot中测试打印日志

    @SpringBootTest
    public class ApplicationTest {
    
        public final static Logger LOGGER = LoggerFactory.getLogger(ApplicationTest.class);
    
        @Test
        public void contextLoads() {
            //打印日志信息
            LOGGER.error("error");
            LOGGER.warn("warn");
            LOGGER.info("info");
            // 默认日志级别
            LOGGER.debug("debug");
            LOGGER.trace("trace");
    
            //  使用log4j2 用桥接器切换为 slf4j们面和logback日志实现
            org.apache.logging.log4j.Logger logger = LogManager.getLogger(Application.class);
            logger.info("logger info");
        }
    }
    
  2. 指定配置

    给类路径下放上每个日志框架自己的配置文件;SpringBoot就不使用默认配置的了

日志框架配置文件
Logbacklogback-spring.xml logback.xml
Log4j2log4j2-spring.xml log4j2.xml
JULlogging.properties
  1. 使用SpringBoot解析日志配置

logback-spring.xml:由SpringBoot解析日志配置

<logger name="com.tzh" level="INFO" additivity="false">
    <!--当spring.profiles.active=dev时使用console输出-->
    <springProfile name="dev">
        <appender-ref ref="console"/>
    </springProfile>
    <!--当spring.profiles.active=prod时使用file输出-->
    <springProfile name="prod">
        <appender-ref ref="file"/>
    </springProfile>
</logger>
  1. 切换日志为log4j2
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <!--排除logback依赖-->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--添加log4j2依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

--------------最后感谢大家的阅读,愿大家技术越来越流弊!--------------

在这里插入图片描述

--------------也希望大家给我点支持,谢谢各位大佬了!!!--------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值