从零开始,手把手教你搭建Spring Boot后台工程并说明
Spring框架与SpringBoot的关联与区别
SpringBean生成流程详解 —— 由浅入深(附超精细流程图)
Spring监听器用法与原理详解
Spring事务畅谈 —— 由浅入深彻底弄懂 @Transactional注解
面试热点详解 ——BeanFactory 和 FactoryBean 的关联与区别
忽视日志吃大亏,手把手教你学习Spring Boot日志
上一次我们介绍了Springboot 下的几种常用日志插件,今天我们就专注讲解其中一个最年轻的日志插件,即Log4j2。Log4j2目前应用非常广泛,各方面较之前辈Log4j 都有不小的提升,不过要想利用好他,还得经过一定的学习,尤其是搞清楚它的种种配置,下面就让我们开始今天的进阶之旅。
作者简介:战斧,从事金融IT行业,有着多年一线开发、架构经验;爱好广泛,乐于分享,致力于创作更多高质量内容
本文收录于 Spring全家桶 专栏,有需要者,可直接订阅专栏实时获取更新
高质量专栏 云原生、RabbitMQ、Spring全家桶 等仍在更新,欢迎指导
Zookeeper Redis kafka docker netty等诸多框架,以及架构与分布式专题即将上线,敬请期待
我们可以使用以下依赖为我们项目引入Log4j2框架
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-apiartifactId>
<version>2.13.3version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.13.3version>
dependency>
当然,像上期所说,我更建议直接使用以下log4j-slf4j-impl
包
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-slf4j-implartifactId>
<version>2.13.3version>
dependency>
该包包含Log4j2的实现,也能对接SLF4J,更适合在项目中应用。
我们看如下一份配置文件:log4j2.xml
<Configuration status="DEBUG" monitorInterval="30">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} - [%t] %-5level %logger{36} - %msg%n"/>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<Buffered mode="ONCE" bufferSize="256"/>
Console>
<RollingFile name="RollingFile" fileName="/path/to/logs/test.log" filePattern="/path/to/logs/$${date:yyyy-MM}/test-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="10MB"/>
Policies>
<DefaultRolloverStrategy max="10"/>
RollingFile>
Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFile"/>
Root>
<Logger name="com.example" level="DEBUG" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFile"/>
Logger>
Loggers>
Configuration>
PatternLayout
是一种日志输出格式,用于设置日志输出的布局格式。该格式使用一系列特定的占位符,每个占位符都代表一种日志信息,例如输出日志事件的时间、线程名、日志级别、日志内容等
我们举两个例子,假如我们配置文件这么写
<PatternLayout pattern="%d{ISO8601} [%t] %-5level %logger{36} - %msg%n" />
输出的结果就为
2023-08-23T19:34:26,068 [main] INFO com.zhanfu.Main - This is a Main info message.
如果配置文件如下
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} |-%-5level [%t] %c{1.} %M [%L] -| %msg%n"/>
输出的结果就为
2023-08-23 20:18:06.833 |-INFO [main] c.z.Main main [11] -| This is a Main info message.
其中,%c{1.}
表示输出日志事件相关的类名,只取最右边的类名,前面的层级,只输出一个字母;如果配置文件如下
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} |-%-5level [%t] %c{2.} [%l] -| %msg%n"/>
输出的结果就为
2023-08-23 20:21:00.753 |-INFO [main] co.zh.Main [com.zhanfu.Main.main(Main.java:11)] -| This is a Main info message.
Appenders
是指日志输出的目的地。它定义了日志事件要被输出到哪些地方,例如控制台、文件、数据库等,其下可以配置不同类型的Appender
,我们罗列其中常见的一些:
对于单机应用来说,Console
与 File
是最为常用的,但对于集群,通常需要进行日志采集,此时也会通过Socket
或Kafka
等将日志发送至其他位置
Console
标签是用于将日志输出到控制台的标签,其有以下可配置的属性:
Console
标签可以包含以下子标签
比如我们可以做出如下配置
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
<ThresholdFilter level="WARN" onMatch="NEUTRAL " onMismatch="DENY"/>
<RegexFilter regex=".*\[(main|AsyncLogger)\].*"/>
Console>
我们可以发现 ThresholdFilter
的设置为 level=“WARN” onMatch="NEUTRAL " onMismatch=“DENY”,这种设置的意思就是当日志等级高于或等于 WARN
时,就通过了级别过滤器,但还需要经过后续过滤器如的进一步筛选。而低于 WARN
则直接拒绝打印。
比如我们有这样的过滤设置
<ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="DENY"/>
第一个过滤器否决打印error级别,第二个过滤器接收大于info级别,并且否决低于info级别的,那最终只能打印info 和 warn 级别了。此处的第三个过滤器实际上没被用到
RollingFile
标签用于配置滚动日志输出到文件的方式,什么叫滚动日志,就是日志输出在一个文件中,当某个事件发生时(如文件达到一定大小),会把这个文件进行归档封存,然后新建一个文件,再向新建的文件中输出日志,有点像现代的饮料生产线,灌满一瓶立即灌下一瓶
以下是RollingFile标签的常用属性:
RollingFile
可以包含下列子标签
我们可以以下面的配置为例子进行进一步解释
<Appenders>
<RollingFile name="RollingFile"
fileName="/logs/app.log"
filePattern="logs/app-%d{yyyy-MM-dd-HH}-%i.log">
<PatternLayout>
<pattern>%d %p %c{1.} [%t] %m%npattern>
PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10 MB"/>
Policies>
<DefaultRolloverStrategy max="5"/>
RollingFile>
Appenders>
该示例配置了一个名为“RollingFile”的RollingFile Appender,将日志记录到“/logs/app.log”文件中。文件名模式为“/logs/$${date:yyyy-MM}/app-%d{yyyy-MM-dd-HH}-%i.log.gz”
filePattern
是RollingFile Appender的一个属性,用于指定根据何种模式生成归档滚动日志文件的名称。filePattern可以使用一些特定的占位符,以便在滚动时自动生成新的日志文件名
filePattern占位符:
比如我们设置的
filePattern="logs/app-%d{yyyy-MM-dd-HH}-%i.log"
filePattern中的时间占位符%d在滚动时会自动更新为当前时间,索引号%i也会自动递增以避免覆盖先前的日志,所以最终会生成以下的归档文件名
app-2021-08-30-15-1.log
app-2021-08-30-15-2.log
app-2021-08-30-15-3.log
Policies
用于定义何时触发滚动动作以生成新的日志文件,可以定义TimeBased和SizeBased两种类型的触发策略。
TimeBasedTriggeringPolicy
TimeBasedTriggeringPolicy
是基于时间的触发策略,当指定的时间间隔过去时,将会触发滚动动作。
属性:
SizeBasedTriggeringPolicy
SizeBasedTriggeringPolicy
是基于日志文件大小的触发策略,当日志文件大小达到指定的大小时,将会触发滚动动作
属性:
-size:指定的日志文件大小,单位为字节,默认值为10MB
需要注意的是,
1.若modulate=true, 则封存时间将以0点为边界进行偏移计算。比如,modulate=true,interval=4hours,那么假设上次封存日志的时间为03:00,则下次封存日志的时间为04:00,之后的封存时间依次为08:00,12:00,16:00…如果modulate=false,则代表不调整,封存时间严格遵循离上次间隔4小时,则封存时间依次为07:00,11:00,15:00…
2.Policies可以同时指定多个触发策略,从而满足多种情况下的滚动需求,例如
<RollingFile name="example"
fileName="logs/logfile.log"
filePattern="logs/logfile-%d{yyyy-MM-dd_HH}.log">
<Policies>
<TimeBasedTriggeringPolicy interval="5" modulate="true"/>
<SizeBasedTriggeringPolicy size="100 MB"/>
Policies>
<DefaultRolloverStrategy max="5"/>
RollingFile>
以上配置将在每隔五小时或100M时将生成一个新的日志文件,文件名会包含时间戳
DefaultRolloverStrategy
是一个滚动策略,可以设置滚动文件数量以及删除策略,它有以下几个属性:
同时,它也支持以下子标签:
比如以下设置
<DefaultRolloverStrategy max="10" min="2" fileIndex="max">
<Delete basePath="logs" maxDepth="2">
<IfFileName glob="*.log" />
<IfLastModified age="14d" />
Delete>
DefaultRolloverStrategy>
这代表日志文件最多保留10个,最少保留2个,日志文件序号格式为max,同时配置了Delete子标签,用于删除14天前的logs文件夹下,两层路径以内的所有.log文件。其中,Delete子标签中的IfFileName和IfLastModified子标签用于指定删除条件,只有同时满足两个条件才进行删除操作
需要注意的是,文件的多少与我们设置的 filePattern 也有关系,其限制的其实是%i的最大数值:
比如
filePattern="logs/app-%d{yyyy-MM-dd-HH}-%i.log
这代表文件归档最小单位为小时,如果我们的 DefaultRolloverStrategy max="10" ,那么一个小时内最多保留10个文件,
当产生第11个文件时,将会删除本小时最早的日志文件
Loggers
和Logger
标签是用来配置日志记录器的。Loggers标签是定义所有Logger的容器标签,而Logger标签则是具体的记录器配置,Logger 标签有以下属性:
Logger标签还包括AppenderRef
子标签,用于指定Logger所要使用的Appender。可以在Logger标签中指定多个AppenderRef,这样就可以将同一个Logger的日志输出到多个Appender中
下面是一个典型的Loggers与Logger的例子:
<Appenders>
<RollingFile name="MyAppender"
fileName="/logs/app.log"
filePattern="logs/app-%d{yyyy-MM-dd-HH}-%i.log">
<PatternLayout>
<pattern>%d %p %c{1.} [%t] %m%npattern>
PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10 MB"/>
Policies>
<DefaultRolloverStrategy max="5"/>
RollingFile>
<Console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<Buffered mode="ONCE" bufferSize="256"/>
Console>
Appenders>
<Loggers>
<Logger name="com.example.MyLogger" level="info">
<AppenderRef ref="MyAppender"/>
Logger>
<Root level="error">
<AppenderRef ref="Console"/>
Root>
Loggers>
这个例子中定义了两个Logger
:com.example.MyLogger和Root
。com.example.MyLogger的级别是INFO,只会记录INFO及以上级别的日志,并且使用MyAppender作为输出目的地;Root的级别是ERROR,将所有ERROR及以上级别的日志输出到Console中
我们上面讲解的其实都是同步输出的日志,而Log4j2 的一大特性就是其异步输出能力,我们可以参考如下配置
<Configuration status="WARN">
<Appenders>
<File name="File" fileName="logs/app.log">
<PatternLayout>
<Pattern>%d %p %C{1.} [%t] %m%nPattern>
PatternLayout>
File>
<Async name="Async">
<AppenderRef ref="File"/>
Async>
Appenders>
<Loggers>
<Logger name="com.example" level="debug"/>
<Root level="info">
<AppenderRef ref="Async"/>
Root>
Loggers>
Configuration>
这个配置可以实现将日志异步输出到logs/app.log文件中,并且可以指定日志输出的格式
我们可以做一个测试,分别用同步和异步的方式进行运行:
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
log.info("This is a debug message. Count is "+i);
}
long endTime = System.currentTimeMillis();
log.info("Total time: "+(endTime - startTime)+"ms");
}
我们注意到最后一个阻塞队列的信息,其实不难想象,这里的日志异步依赖的就是高性能队列,不仅log4j2
,像Netty
等对性能有极高要求的框架,在队列的选择上都是精益求精的。log4j2支持四种阻塞队列,如下:
<Configuration name="LinkedTransferQueueExample">
<Appenders>
<List name="List"/>
<Async name="Async" bufferSize="262144">
<AppenderRef ref="List"/>
<AsyncQueueFullPolicy type="Discard"/>
<LinkedTransferQueue/>
Async>
Appenders>
<Loggers>
<Root>
<AppenderRef ref="Async"/>
Root>
Loggers>
Configuration>
当然,如果你想使用 DisruptorBlockingQueue
,那还需要引入 disruptor 包,注意版本兼容,log4j-2.17.1 对应了disruptor-3.4.0
<dependency>
<groupId>com.lmaxgroupId>
<artifactId>disruptorartifactId>
<version>3.4.0version>
dependency>
本次我们比较详细的介绍了Log4j2框架的种种配置,虽然没有讲原理,但解释了不少配置并给出示例,相信即便是新手,阅读完也能应对大部分使用场景了。希望大家能够举一反三,灵活运用。当然,有问题也可以直接去官方文档进行查阅和学习,点此直达
log4j2 手册