SpringBoot之log4j2

介绍

Apache Log4j 2 是对 Log4j 的升级,是一款高性能日志框架,对比它的前一代或者 logback 等日志框架有很大的改进,在几个日志框架中性能最高,没有之一

使用

依赖

在 springboot2.x 版本中我们需要导入 log4j2 的 starter,以及排除自身的 logback

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

排除 logback

1
2
3
4
5
6
7
8
9
10
<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>

配置文件

这是我日常用的,有能力的可以进行自定义改动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
<?xml version="1.0" encoding="UTF-8"?>
<!-- status lgo4j2自身日志输出级别 monitorInterval 监测配置变动时间 -->
<configuration status="warn" monitorInterval="30">
<Properties>
<!-- 在此可定义一些属性-->
<Property name="LOG_HOME">./log</Property>
<!--<Property name="PATTERN">[%date{DEFAULT}] [%thread] [%-5level] TC:%x %logger{1.}.%method:%line - %msg%xEx%n</Property>-->
<Property name="LOG_EXCEPTION_CONVERSION_WORD">%xEx</Property>
<Property name="LOG_LEVEL_PATTERN">%5p</Property>
<Property name="LOG_DATEFORMAT_PATTERN">yyyy-MM-dd HH:mm:ss.SSS</Property>
<Property name="CONSOLE_LOG_PATTERN">%style{%d{${LOG_DATEFORMAT_PATTERN}}}{Dim} %highlight{${LOG_LEVEL_PATTERN}}{FATAL=Bright white, ERROR=Bright red, WARN=Bright yellow, INFO=Bright blue, DEBUG=Bright cyan, TRACE=Bright blue} %style{%-5pid}{Magenta} %style{---}{Dim} %style{[%15.15t]}{Dim} %style{%-40.40c{1.}}{Bright Cyan} %style{L%-4line}{Dim} %style{:}{Dim} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
<Property name="FILE_LOG_PATTERN">%d{${LOG_DATEFORMAT_PATTERN}} ${LOG_LEVEL_PATTERN} %-5pid --- [%t] %-40.40c{1.} L%-4line : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>


</Properties>
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<!--<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>-->

<!-- 日志样式-->
<PatternLayout pattern="${sys:CONSOLE_LOG_PATTERN}" disableAnsi="false" noConsoleNoAnsi="false"/>
</Console>

<!--处理DEBUG级别的日志,并把该日志放到logs/debug.log文件中-->
<!--打印出DEBUG级别日志,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileDebug" fileName="${LOG_HOME}/debug.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM}/debug-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<ThresholdFilter level="debug"/>
<!-- onMismatch="NEUTRAL" 可以让日志经过后续的过滤器-->
<ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<PatternLayout pattern="${sys:FILE_LOG_PATTERN}"/>
<Policies>
<SizeBasedTriggeringPolicy size="50 MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>

<!--处理INFO级别的日志,并把该日志放到logs/info.log文件中-->
<RollingFile name="RollingFileInfo" fileName="${LOG_HOME}/info.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<!--只接受INFO级别的日志,其余的全部拒绝处理-->
<ThresholdFilter level="info"/>
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<PatternLayout pattern="${sys:FILE_LOG_PATTERN}"/>
<Policies>
<SizeBasedTriggeringPolicy size="10 MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>

<!--处理WARN级别的日志,并把该日志放到logs/warn.log文件中-->
<RollingFile name="RollingFileWarn" fileName="${LOG_HOME}/warn.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<ThresholdFilter level="warn"/>
<ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<PatternLayout pattern="${sys:FILE_LOG_PATTERN}"/>
<Policies>
<SizeBasedTriggeringPolicy size="10 MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>

<!--处理error级别的日志,并把该日志放到logs/error.log文件中-->
<RollingFile name="RollingFileError" fileName="${LOG_HOME}/error.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz">
<ThresholdFilter level="error"/>
<PatternLayout pattern="${sys:FILE_LOG_PATTERN}"/>
<Policies>
<SizeBasedTriggeringPolicy size="10 MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>

<!--druid的日志记录追加器-->
<RollingFile name="druidSqlRollingFile" fileName="${LOG_HOME}/druid-sql.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM}/druid-sql-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="${sys:FILE_LOG_PATTERN}"/>
<Policies>
<SizeBasedTriggeringPolicy size="50 MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>

<!--慢SQL的日志记录追加器-->
<!-- dataSource 配置
<property name="filters" value="stat"/>
<property name="connectionProperties" value="druid.stat.logSlowSql=true;druid.stat.slowSqlMillis=5000"/>
-->
<RollingFile name="slowSqlRollingFile" fileName="${LOG_HOME}/slow-sql.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM}/slow-sql-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="${sys:FILE_LOG_PATTERN}"/>
<Policies>
<SizeBasedTriggeringPolicy size="50 MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>
</appenders>

<loggers>
<root level="info" includeLocation="true">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileDebug"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>

<!--记录druid-sql的记录-->
<logger name="druid.sql" level="debug" additivity="false">
<appender-ref ref="druidSqlRollingFile"/>
<appender-ref ref="Console"/>
</logger>

<!--记录慢sql的记录-->
<logger name="com.alibaba.druid.filter.stat" level="warn" additivity="false">
<appender-ref ref="slowSqlRollingFile"/>
<appender-ref ref="Console"/>
</logger>

<!-- 自定义日志输出控制:开始 -->
<logger name="com.github.busi" level="info"/>
<logger name="org.springframework" level="info"/>
<logger name="org.springframework.context.annotation" level="warn"/>
<logger name="org.springframework.beans.factory.support" level="warn"/>
<logger name="org.springframework.core.env.PropertySourcesPropertyResolver" level="warn"/>
<logger name="org.springframework.beans.factory.annotation" level="warn"/>
<logger name="org.springframework.core.LocalVariableTableParameterNameDiscoverer" level="warn"/>
<logger name="org.springframework.core.annotation.AnnotationUtils" level="warn"/>
<logger name="org.springframework.web.servlet.handler" level="info"/>
<logger name="org.springframework.aop.framework" level="info"/>
<logger name="org.hibernate.validator.internal" level="info"/>
<logger name="org.springframework.jmx.export" level="info"/>
<logger name="org.springframework.core.env" level="info"/>
<logger name="springfox.documentation" level="warn"/>
<logger name="org.apache.ibatis.logging.jdbc" level="off"/>
<!-- 如果需要输出mybatis日志,开启以下配置(主要是dao包) -->
<logger name="org.apache.ibatis.logging.jdbc" level="debug"/>
<logger name="com.doway.cloud.center.dao" level="debug"/>

<!--log4j2 自带过滤日志-->
<Logger name="org.apache.catalina.startup.DigesterFactory" level="error"/>
<Logger name="org.apache.catalina.util.LifecycleBase" level="error"/>
<Logger name="org.apache.coyote.http11.Http11NioProtocol" level="warn"/>
<logger name="org.apache.sshd.common.util.SecurityUtils" level="warn"/>
<Logger name="org.apache.tomcat.util.net.NioSelectorPool" level="warn"/>
<Logger name="org.crsh.plugin" level="warn"/>
<logger name="org.crsh.ssh" level="warn"/>
<Logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="error"/>
<Logger name="org.hibernate.validator.internal.util.Version" level="warn"/>
<logger name="org.thymeleaf" level="warn"/>
<logger name="org.springframework.core.env" level="warn"/>
<logger name="org.springframework.core.io.support" level="warn"/>
<logger name="org.springframework.web.filter" level="warn"/>
</loggers>
</configuration>

配置文件一般放在 src/main/resources 目录下,新建一个 log4j2.xml 文件,然后在 application.yml 中配置

1
2
3
4
5
6
7
logging:
config: classpath:log4j2.xml
level:
com.xx.xx.xx.dao: DEBUG

#我们可以在log4j2配置文件loggers标签中配置某个包的日志级别,也可以在此level下配置,此配置优先级更高
#这里我们配置了com.xx.xx.xx.dao这个包的日志级别是DEBUG,那么所有debug级别的日志都会输出

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
private static final Logger log = LoggerFactory.getLogger(TestController.class);

@GetMapping("/test")
public void test() {
log.trace("trace:{}", "trace级别信息");
log.debug("debug:{}", "debug级别信息");
log.info("info:{}", "info级别信息");
log.warn("warn:{}", "warn级别信息");
log.error("error:{}", "error级别信息");
}
}

image-20211118222246144

首先我们获取了 Logger 对象,可以看到这个是 slfj 日志门面的类,而 log4j2 仅仅是日志实现,这样换到其他日志框架只需要换一个依赖不会有其他影响(就如 logback 换到 log4j2),我们使用 lombok@Slfj注解可以替代这一句话,一般来说都是如此,具体实现感兴趣的可以去研究一下源码

image-20211118222922282

可以看到代码里的几个日志级别,trace 用得少,其他的都较常用。slf4j 提供了 {} 可以格式化参数,跟 String.foramt方法大同小异,需要注意的是参数会调用 toString 方法,所以不可为空

代码输出了 5 个级别的记录,但是控制台只打印了三个,trace 和 debug 的都未打印,这是因为上面日志配置文件定义的日志级别为 info,等级低于 info 的都不会被输出(但是后面的参数还是会计算,比如后面是 json 序列化)

level sort: trace<debug<info<warn<error

这些是常见的,有些地方有更多的等级

惰性日志

首先看下非惰性日志

image-20211119233141194

此时我们的日志级别为 info ,故而 debug 不会输出,但是我们可以发现后面的参数还是照常执行自增了,这可能造成不必要的性能浪费

我们可以使用一个方法来进行优化

1
2
3
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("debug:{} {}","debug级别信息",i.incrementAndGet());
}

当 debug 开启时才进行输出,但是这样并不优雅,接下来用优雅的方式实现

在 log4j2 中 重载了参数为 Supplier 的方法,它是一个函数式接口,因此我们可以利用 lambda 表达式的特性->惰性求值,只有被使用时才会去执行,这时只能使用 LogManager 来获取日志对象,@Slfj注解暂时没有这个实现(也许还有其他方法)

看看效果

image-20211119235136967

lambda、方法引用等知识点不在这里过多讲解

异步日志

log4j2 日志分为同步日志和异步日志,同步日志会阻塞线程,自然程序效率就会受影响

有两种方式,这里讲其中一种

需要引入一个依赖

1
2
3
4
5
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version>
</dependency>

disruptor 是一个高性能队列框架,具体的可以去百度谷歌查看

然后我们需要在 src/main/resources 目录下,新建一个 log4j2.component.properties 文件

1
2
3
4
log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
#设置系统属性,也可以在程序中设置
#配置AsyncLoggerContextSelector为异步日志时,请确保在log4j2配置文件中使用普通的 <root>和<logger>元素
#默认异步线程下不会获取日志输出行号,可以在root标签内配置 includeLocation="true" 即可

此时即可使用异步线程来记录日志了,这也是 log4j2 性能强大原因,可以使用 AsyncLoggerContextSelector.isSelected() 方法来测试异步线程是否成功开启

合理使用日志,会使程序状态更加清晰,更容易定位错误,但是不当使用会影响程序效率


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!