前言
springboot默认使用logback作为日志记录框架,常见的日志记录框架有log4j、logback、log4j2。这篇文章我们来学习怎样将logbak替换为log4j2。
一、为什么使用log4j2?
我们在项目中经常使用一个叫SLF4J的依赖,它是做什么的呢?
slf4j是对所有日志框架制定的一种规范、标准、接口,并不是一个框架的具体的实现,它的接口并不能独立使用,需要和具体的日志框架实现配合使用(如log4j、logback、log4j2)。
log4j是apache实现的一个开源日志组件。
logback同样是由log4j的作者设计完成的,拥有更好的特性,用来取代log4j的一个日志框架,是slf4j的原生实现。
Log4j2是log4j 1.x和logback的改进版,性能最高。
log4j2异步记录日志使用了disruptor框架,性能得到了提高,记录日志的流程如下:
几种日志框架的性能测试对比如下:
二、使用步骤
1.引入库
gradle:
//引入log4j2
api 'org.springframework.boot:spring-boot-starter-log4j2'
api "com.lmax:disruptor:3.4.4"//删除logback依赖
configurations {api.exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'api.exclude group: 'ch.qos.logback', module: 'logback-access'api.exclude group: 'ch.qos.logback', module: 'logback-classic'api.exclude group: 'ch.qos.logback', module: 'logback-core'
}
maven:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><!-- 排除掉logging,不使用logback,改用log4j2 --><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>
<!-- disruptor -->
<dependency><groupId>com.lmax</groupId><artifactId>disruptor</artifactId><version>3.4.4</version>
</dependency>
2.编写log4j2配置文件
在application.properties中引入配置文件
logging.config=classpath:log4j2-spring.xml
在resources下添加log4j2-spring.xml配置文件
以下是我在项目中的配置,注意的是使用AsyncLogger才会使用disruptor提高性能。如果使用的AsyncAppender,则使用的是类似logback一样的队列的方式做异步记录。
<?xml version="1.0" encoding="UTF-8"?><!--Configuration 后面的 status,这个用于设置 log4j2 自身内部的信息输出级别,可以不设置,当设置成 trace 时,你会看到 log4j2 内部各种详细输出-->
<!--monitorInterval:Log4j2 能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="error" monitorInterval="30"><!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --><!--变量配置--><properties><!-- 格式化输出:%date 表示日期,%thread 表示线程名,%-5level:级别从左显示 5 个字符宽度 %msg:日志消息,%n 是换行符--><!-- %logger{36} 表示 Logger 名字最长 36 个字符 --><property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%logger{50}:%L] - %msg%n"/><property name="LOG_CONSOLE_PATTERN" value="%style{%d{ISO8601}}{bright,green} %highlight{%-5level} [%style{%t}{bright,blue}] %style{%C{}}{bright,yellow}: %msg%n%style{%throwable}{red}"/><!-- 定义日志存储的路径 --><property name="FILE_PATH" value="logs"/><property name="FILE_NAME" value="newframe"/></properties><Appenders><!--*********************控制台日志***********************--><Console name="consoleAppender" target="SYSTEM_OUT"><!--设置日志格式及颜色--><PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/></Console><!--info级别日志--><!-- 这个会打印出所有的info及以上级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--><RollingFile name="infoFileAppender"fileName="${FILE_PATH}/${FILE_NAME}/log_info.log"filePattern="${FILE_PATH}/${FILE_NAME}/log-info-%d{yyyy-MM-dd}_%i.log.gz"append="true"><!--设置日志格式--><PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/><Filters><!--过滤掉warn及更高级别日志--><ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL" /><ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/></Filters><Policies><!-- 基于时间的触发策略。该策略主要是完成周期性的log文件封存工作。有两个参数:interval,integer型,指定两次封存动作之间的时间间隔。单位:以日志的命名精度来确定单位,比如yyyy-MM-dd-HH 单位为小时,yyyy-MM-dd-HH-mm 单位为分钟modulate,boolean型,说明是否对封存时间进行调制。若modulate=true,则封存时间将以0点为边界进行偏移计算。比如,modulate=true,interval=4hours,那么假设上次封存日志的时间为00:00,则下次封存日志的时间为04:00,之后的封存时间依次为08:00,12:00,16:00--><TimeBasedTriggeringPolicy interval="1"/><SizeBasedTriggeringPolicy size="10MB"/></Policies><!-- DefaultRolloverStrategy 属性如不设置,则默认为最多同一文件夹下当天 7 个文件后开始覆盖--><DefaultRolloverStrategy max="30"><!-- 删除处理策略,在配置的路径中搜索,maxDepth 表示往下搜索的最大深度 --><Delete basePath="${FILE_PATH}/${FILE_NAME}/" maxDepth="2"><!-- 文件名搜索匹配,支持正则 --><IfFileName glob="*.log.gz"/><!--!Note: 这里的 age 必须和 filePattern 协调, 后者是精确到 dd, 这里就要写成 xd, xD 就不起作用另外, 数字最好 >2, 否则可能造成删除的时候, 最近的文件还处于被占用状态,导致删除不成功!--><!--7天--><IfLastModified age="7d"/></Delete></DefaultRolloverStrategy></RollingFile><!--warn级别日志--><!-- 这个会打印出所有的warn及以上级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--><RollingFile name="warnFileAppender"fileName="${FILE_PATH}/${FILE_NAME}/log_warn.log"filePattern="${FILE_PATH}/${FILE_NAME}/log-warn-%d{yyyy-MM-dd}_%i.log.gz"append="true"><!--设置日志格式--><PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/><Filters><!--过滤掉error及更高级别日志--><ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL" /><ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/></Filters><Policies><!-- 基于时间的触发策略。该策略主要是完成周期性的log文件封存工作。有两个参数:interval,integer型,指定两次封存动作之间的时间间隔。单位:以日志的命名精度来确定单位,比如yyyy-MM-dd-HH 单位为小时,yyyy-MM-dd-HH-mm 单位为分钟modulate,boolean型,说明是否对封存时间进行调制。若modulate=true,则封存时间将以0点为边界进行偏移计算。比如,modulate=true,interval=4hours,那么假设上次封存日志的时间为00:00,则下次封存日志的时间为04:00,之后的封存时间依次为08:00,12:00,16:00--><TimeBasedTriggeringPolicy interval="1"/><SizeBasedTriggeringPolicy size="10MB"/></Policies><!-- DefaultRolloverStrategy 属性如不设置,则默认为最多同一文件夹下当天 7 个文件后开始覆盖--><DefaultRolloverStrategy max="30"><!-- 删除处理策略,在配置的路径中搜索,maxDepth 表示往下搜索的最大深度 --><Delete basePath="${FILE_PATH}/${FILE_NAME}/" maxDepth="2"><!-- 文件名搜索匹配,支持正则 --><IfFileName glob="*.log.gz"/><!--!Note: 这里的 age 必须和 filePattern 协调, 后者是精确到 dd, 这里就要写成 xd, xD 就不起作用另外, 数字最好 >2, 否则可能造成删除的时候, 最近的文件还处于被占用状态,导致删除不成功!--><!--7天--><IfLastModified age="7d"/></Delete></DefaultRolloverStrategy></RollingFile><!--error级别日志--><!-- 这个会打印出所有的error及以上级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--><RollingFile name="errorFileAppender"fileName="${FILE_PATH}/${FILE_NAME}/log_error.log"filePattern="${FILE_PATH}/${FILE_NAME}/log-error-%d{yyyy-MM-dd}_%i.log.gz"append="true"><!--设置日志格式--><PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/><Filters><ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/></Filters><Policies><!-- 基于时间的触发策略。该策略主要是完成周期性的log文件封存工作。有两个参数:interval,integer型,指定两次封存动作之间的时间间隔。单位:以日志的命名精度来确定单位,比如yyyy-MM-dd-HH 单位为小时,yyyy-MM-dd-HH-mm 单位为分钟modulate,boolean型,说明是否对封存时间进行调制。若modulate=true,则封存时间将以0点为边界进行偏移计算。比如,modulate=true,interval=4hours,那么假设上次封存日志的时间为00:00,则下次封存日志的时间为04:00,之后的封存时间依次为08:00,12:00,16:00--><TimeBasedTriggeringPolicy interval="1"/><SizeBasedTriggeringPolicy size="10MB"/></Policies><!-- DefaultRolloverStrategy 属性如不设置,则默认为最多同一文件夹下当天 7 个文件后开始覆盖--><DefaultRolloverStrategy max="30"><!-- 删除处理策略,在配置的路径中搜索,maxDepth 表示往下搜索的最大深度 --><Delete basePath="${FILE_PATH}/${FILE_NAME}/" maxDepth="2"><!-- 文件名搜索匹配,支持正则 --><IfFileName glob="*.log.gz"/><!--!Note: 这里的 age 必须和 filePattern 协调, 后者是精确到 dd, 这里就要写成 xd, xD 就不起作用另外, 数字最好 >2, 否则可能造成删除的时候, 最近的文件还处于被占用状态,导致删除不成功!--><!--7天--><IfLastModified age="7d"/></Delete></DefaultRolloverStrategy></RollingFile><!--<Async name="Async" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></Async>--></Appenders><Loggers><AsyncLogger name="org.apache.http" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="io.lettuce" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="io.netty" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.quartz" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.springframework" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.springdoc" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="druid.sql" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="io.undertow" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="sun.rmi" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="com.sun.mail" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="javax.management" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="de.codecentric" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.hibernate.validator" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.mybatis.spring.mapper" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.xnio" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="springfox" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="com.baomidou" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="io.micrometer.core" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="Validator" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.neo4j" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.apache.zookeeper" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.apache.curator" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="oshi.util" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="net.javacrumbs" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="com.atomikos" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncRoot level="debug" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncRoot></Loggers><!--Logger 节点用来单独指定日志的形式,比如要为指定包下的 class 指定不同的日志级别等。--><!--然后定义 loggers,只有定义了 logger 并引入的 appender,appender 才会生效--><!--<Loggers><Logger name="java.sql" level="debug" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="com.sxd.swapping.dao.mybatis" level="debug" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="org.apache.ibatis" level="debug" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="sun.rmi.transport.tcp" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="org.apache.http" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="com.sun.mail.smtp" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="javax.management" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="sun.rmi" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="de.codecentric" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="druid.sql.Connection" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="druid.sql.Statement" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="druid.sql.ResultSet" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="org.hibernate.validator" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="org.mybatis.spring.mapper" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="org.xnio.nio" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="springfox.documentation" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="springfox.bean" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="com.baomidou.mybatisplus.core" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="io.undertow" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="io.micrometer.core" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="com.baomidou.mybatisplus.extension.spring" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="Validator" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="io.lettuce" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="io.netty" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="org.springframework.boot.actuate.redis.RedisReactiveHealthIndicator" level="error" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="org.neo4j.driver" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="org.apache.zookeeper" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="org.apache.curator.framework.recipes.cache" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="oshi.util.platform.windows" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="org.quartz.core" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="org.quartz.simpl" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="net.javacrumbs.shedlock.core.DefaultLockingTaskExecutor" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="com.atomikos.jdbc.AbstractDataSourceBean" level="warn" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="com.atomikos.jdbc.AtomikosConnectionProxy" level="error" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="org.springframework.boot.actuate.mail" level="error" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="org.springframework.web" level="info" additivity="false" ><AppenderRef ref="Async"/></Logger><Logger name="org.springframework.context" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="org.springframework.data" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="org.springframework.beans" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="org.springframework.core" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="org.springframework.jdbc" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><Logger name="org.springframework.messaging" level="info" additivity="false"><AppenderRef ref="Async"/></Logger><!– 根日志设置 –><Root level="debug" additivity="false"><AppenderRef ref="Async"/></Root></Loggers>--></configuration>
总结
log4j2使用了两种方式记录日志:AsyncAppender和AsyncLogger,上面我们的配置采用了AsyncLogger,性能最佳。
1、AsyncAppender使用队列异步记录日志,但是一旦队列已满,appender线程需要等待。
2、AsyncLogger是采用Disruptor,通过环形队列无阻塞队列作为缓冲,多生产者多线程的竞争是通过CAS实现,无锁化实现,可以降低极端大的日志量时候的延迟尖峰,Disruptor 可是号称一个线程里每秒处理600万订单的高性能队列。
下图是官方给出的性能对比: