Spring Boot - 内置日志
序言
规范:项目开发不要编写 System.out.println()
,应该用 日志 记录信息。
# 1. 简介
Spring 使用
commons-logging
作为内部日志,但底层日志实现是开放的。可对接其他日志框架Spring5 及以后
commons-logging
被 Spring 直接自己写了。Spring 的日志支持 jul,log4j2,logback。SpringBoot 提供了默认的控制台输出配置,也可以配置输出为文件
logback 是默认使用的日志
虽然 日志框架很多,但是我们不用担心,使用 SpringBoot 的 默认配置就能工作的很好
SpringBoot 怎么把日志默认配置好的
1、每个 starter
场景,都会导入一个核心场景 spring-boot-starter
2、核心场景引入了日志的所用功能 spring-boot-starter-logging
3、默认使用了logback + slf4j
组合作为默认底层日志
4、日志是系统一启动就要用,xxxAutoConfiguration
是系统启动好了以后放好的组件
5、日志是利用 监听器机制 配置好的。ApplicationListener
6、日志所有的配置都可以通过修改配置文件实现。以 logging
开始的所有配置
# 2. 日志格式
2023-03-31T13:56:17.511+08:00 INFO 4944 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-03-31T13:56:17.511+08:00 INFO 4944 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.7]
2
根据上面日志格式得出默认输出格式:
- 时间和日期:毫秒级精度
- 日志级别:ERROR,WARN,INFO,DEBUG,TRACE
- 进程 ID
- ---:消息分割符
- 线程名:使用
[]
包含 - Logger 名:通常是产生日志的 类名
- 消息:日志记录的内容
注意:logback 没有 FATAL 级别,对应的是 ERROR。
默认值:参照:spring-boot
包 additional-spring-configuration-metadata.json
文件
默认输出格式值:%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}
。
可修改为:"%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{15} ===> %msg%n"
。
在 application.yml 中可以修改 Logback 默认的日志输出格式配置:
logging:
pattern:
console: %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{15} ===> %msg%n
2
3
也可以单独修改日期格式:
logging:
pattern:
dateformat: yyyy-MM-dd HH:mm:ss.SSS
2
3
这样就不会以 logging.pattern.console
的日志格式为主,而是以 logging.pattern.dateformat
为主。
# 3. 日志使用
通过 LoggerFactory 工厂获取日志对象
class Test {
Logger logger = LoggerFactory.getLogger(getClass());
public void test() {
logger.info("测试 Info");
}
}
2
3
4
5
6
7
或者使用 Lombok 的 @Slf4j
注解。
引入依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2
3
4
使用 @Slf4j
注解后,就会内置 log
对象,可以直接使用 log 进行输出
@Slf4j
class Test {
public void test() {
log.info("测试 Info");
}
}
2
3
4
5
6
7
# 4. 日志级别
由低到高:
ALL
,TRACE
,DEBUG
,INFO
,WARN
,ERROR
,FATAL
,OFF
- 只会打印指定级别及以上级别的日志
- ALL:打印所有日志
- TRACE:追踪框架详细流程日志,一般不使用
- DEBUG:开发调试细节日志
- INFO:关键、感兴趣信息日志
- WARN:警告但不是错误的信息日志,比如:版本过时
- ERROR:业务错误日志,比如出现各种异常
- FATAL:致命错误日志,比如jvm系统崩溃
- OFF:关闭所有日志记录
不指定级别的所有类,都使用 root 指定的级别作为默认级别
SpringBoot 日志 默认级别是 INFO
在 application.properties/yaml 中配置 logging.level.<logger-name>=<level>
指定日志级别
比如项目的某些类在 cn.youngkbt.serivce
路径下,想设定该包路径下的日志输出都是 info,则
logging:
level:
cn.youngkbt.serivce: info # 指定该包路径下输出日志级别为 info
2
3
level 可取值范围:``ALL,
TRACE,
DEBUG,
INFO,
WARN,
ERROR,
FATAL,
OFF,定义在
LogLevel` 类中。
除了自定义包路径,LogBack 可以设置全局的日志级别,也就是没有自定义的全局的默认日志级别:
root:所有未指定日志级别都使用 root 的 warn 级别,代表全局日志级别
logging:
level:
root: info
2
3
# 5. 日志分组
在Spring Boot应用中,配置日志级别是一个常见需求,尤其是当你需要根据不同的包路径设置不同的日志级别以方便调试和监控。为了简化配置和提高可维护性,Spring Boot提供了日志分组的功能,允许你将相关的日志记录器(logger)分组并统一管理它们的日志级别。
# 日志分组基本用法
当你有多个包路径需要设置相同的日志级别时,可以通过定义日志分组来简化配置:
logging:
group:
scholar: cn.scholar.service, cn.scholar.controller, cn.scholar.mapper
2
3
在上述配置中,youngkbt
是自定义的日志分组名称,它包含了cn.scholar.service
、cn.scholar.controller
和cn.scholar.mapper
三个包路径。随后,你可以使用这个分组名称来统一设置这些包路径的日志级别:
logging:
level:
scholar: info
2
3
这样,所有scholar
分组下的包路径都会应用info
级别的日志输出设置,从而简化了配置并增加了配置的清晰度。
# Spring Boot预定义的日志分组
Spring Boot还预定义了一些常用的日志分组,用于快速配置特定范围的日志级别:
- web分组:包括了Spring Web相关的核心日志记录器,适用于监控Web请求和响应等信息。
- sql分组:涵盖了Spring JDBC和Hibernate SQL等数据库操作相关的日志记录器,便于调试SQL查询和操作。
这些分组可以直接在配置中使用,例如设置Web相关日志和SQL相关日志的级别为info
:
logging:
level:
web: info
sql: info
2
3
4
日志分组是Spring Boot提供的一个非常实用的特性,它让日志级别的配置变得更加灵活和集中,尤其是当你需要对多个相关的包路径或常用模块进行日志级别管理时。通过自定义分组或利用Spring Boot预定义的分组,你可以轻松地实现日志级别的统一配置和管理。这不仅提高了配置的可读性,也便于后续的维护和调整。
# 6. 文件输出
在Spring Boot应用中,默认情况下,日志输出只会显示在控制台。如果你想将日志同时记录到文件中,可以通过application.properties
或application.yml
配置文件进行相应的配置。以下是如何配置日志文件输出的方法和规则:
# 日志文件配置
你可以使用logging.file.name
或logging.file.path
属性来配置日志文件的输出位置。
指定文件名和路径 (logging.file.name
):
如果直接指定日志文件的完整名称(包括路径),则日志将被写入该指定的文件中。
logging:
file:
name: D:\\youngkbt.log # 日志将被写入D盘的youngkbt.log文件中
2
3
指定文件路径 (logging.file.path
):
如果只指定了日志文件的路径而没有指定文件名,Spring Boot将使用默认的文件名spring.log
,并将日志写入该路径下。
logging:
file:
path: D:\\ # 日志将被写入D盘的spring.log文件中
2
3
# 配置规则总结
logging.file.name 设置 | logging.file.path 设置 | 示例 | 效果说明 |
---|---|---|---|
未设置 | 未设置 | 日志仅在控制台输出,不写入文件 | |
设置 | 未设置 | my.log | 日志写入指定的文件中,路径可在文件名中指定 |
未设置 | 设置 | /var/log | 日志写入指定路径,使用默认文件名spring.log |
设置 | 设置 | 同时设置 | 忽略logging.file.path ,按logging.file.name 的设置写入日志 |
通过上述配置,你可以灵活地控制日志的输出位置,无论是将日志输出到指定的文件中,还是输出到某个特定的目录下。这对于日志的查看和管理非常有用,特别是在生产环境中。
# 7. 文件归档与滚动切割
在Spring Boot应用中,通过合适的日志配置可以实现日志的归档和滚动切割,以保证日志文件的管理既高效又不会占用过多的磁盘空间。使用logback
(Spring Boot的默认日志框架)时,你可以在application.properties
或application.yml
中设置相应的滚动策略。
文件归档与滚动切割配置
以下是实现日志文件归档(按日期存档)和滚动切割(基于文件大小)的关键配置项:
- 文件名模式 (
file-name-pattern
): 定义了存档文件的命名格式,其中可以包含日期和索引,以支持按日期归档和按大小切割。 - 启动时清理历史 (
clean-history-on-start
): 决定应用启动时是否清除旧的日志文件存档。 - 最大文件大小 (
max-file-size
): 定义了触发日志文件滚动切割前,单个文件的最大大小。 - 总大小上限 (
total-size-cap
): 设置了日志文件总大小的上限,超过此大小时,旧的日志文件会被自动删除。 - 最大历史记录 (
max-history
): 指定了日志文件保留的最大天数,超过这个天数的日志文件将被删除。
logging:
logback:
rollingpolicy:
file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz # 指定日志存档的文件名格式
clean-history-on-start: false # 启动时不清除旧存档
max-file-size: 10MB # 每个日志文件的最大大小
total-size-cap: 1GB # 日志文件的总大小上限
max-history: 7 # 保留日志文件的最大天数
2
3
4
5
6
7
8
应用这些配置的效果
- 每天的日志都将单独存储在以日期命名的文件中,便于管理和查找特定日期的日志。
- 当任一日志文件大小达到10MB时,将会自动滚动,即开始写入新的日志文件。
- 总日志大小达到1GB后,最旧的日志文件将被删除,以避免磁盘空间被无限占用。
- 只保留最近7天的日志文件,超出这个范围的日志文件将被删除,以管理日志文件的生命周期。
通过上述配置,Spring Boot应用的日志管理变得既灵活又高效,有助于提高应用的运维性能。需要注意的是,这些配置仅适用于logback
日志框架,如果使用其他日志系统(如log4j2
),则需要通过相应的配置文件(如log4j2.xml
)来实现类似的功能。
# 8. 自定义配置
通常我们配置 application.properties/yml
就够了。当然也可以自定义。比如:
日志系统 | 自定义 |
---|---|
Logback | logback-spring.xml, logback-spring.groovy, logback.xml, or logback.groovy |
Log4j2 | log4j2-spring.xml or log4j2.xml |
JDK (Java Util Logging) | logging.properties |
如果可能,我们建议您在日志配置中使用 -spring
变量(例如,logback-spring.xml
而不是 logback.xml
)。如果您使用标准配置文件,spring 无法完全控制日志初始化。
最佳实战:自己要写配置,配置文件名为 logback-spring.xml
。
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- appender是configuration的子节点,是负责写日志的组件。 -->
<!-- ConsoleAppender:把日志输出到控制台 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- 默认情况下,每个日志事件都会立即刷新到基础输出流。 这种默认方法更安全,因为如果应用程序在没有正确关闭appender的情况下退出,则日志事件不会丢失。
但是,为了显着增加日志记录吞吐量,您可能希望将immediateFlush属性设置为false -->
<!--<immediateFlush>true</immediateFlush>-->
<encoder>
<!-- %37():如果字符没有37个字符长度,则左侧用空格补齐 -->
<!-- %-37():如果字符没有37个字符长度,则右侧用空格补齐 -->
<!-- %15.15():如果记录的线程字符长度小于15(第一个)则用空格在左侧补齐,如果字符长度大于15(第二个),则从开头开始截断多余的字符 -->
<!-- %-40.40():如果记录的logger字符长度小于40(第一个)则用空格在右侧补齐,如果字符长度大于40(第二个),则从开头开始截断多余的字符 -->
<!-- %msg:日志打印详情 -->
<!-- %n:换行符 -->
<!-- %highlight():转换说明符以粗体红色显示其级别为ERROR的事件,红色为WARN,BLUE为INFO,以及其他级别的默认颜色。 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) --- [%15.15(%thread)] %cyan(%-40.40(%logger{40})) : %msg%n</pattern>
<!-- 控制台也要使用UTF-8,不要使用GBK,否则会中文乱码 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- info 日志-->
<!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
<!-- 以下的大概意思是:1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是project_info.log -->
<!-- 2.如果日期没有发生变化,但是当前日志的文件大小超过10MB时,对当前日志进行分割 重命名-->
<appender name="info_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志文件路径和名称-->
<File>logs/project_info.log</File>
<!--是否追加到文件末尾,默认为true-->
<append>true</append>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch><!-- 如果命中ERROR就禁止这条日志 -->
<onMismatch>ACCEPT</onMismatch><!-- 如果没有命中就使用这条规则 -->
</filter>
<!--有两个与RollingFileAppender交互的重要子组件。 第一个RollingFileAppender子组件,即RollingPolicy:负责执行翻转所需的操作。
RollingFileAppender的第二个子组件,即TriggeringPolicy:将确定是否以及何时发生翻转。 因此,RollingPolicy负责什么和TriggeringPolicy负责什么时候.
作为任何用途,RollingFileAppender必须同时设置RollingPolicy和TriggeringPolicy,但是,如果其RollingPolicy也实现了TriggeringPolicy接口,则只需要显式指定前者。-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 日志文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
<!-- 文件名:logs/project_info.2017-12-05.0.log -->
<!-- 注意:SizeAndTimeBasedRollingPolicy中 %i和%d令牌都是强制性的,必须存在,要不会报错 -->
<fileNamePattern>logs/project_info.%d.%i.log</fileNamePattern>
<!-- 每产生一个日志文件,该日志文件的保存期限为30天, ps:maxHistory的单位是根据fileNamePattern中的翻转策略自动推算出来的,例如上面选用了yyyy-MM-dd,则单位为天
如果上面选用了yyyy-MM,则单位为月,另外上面的单位默认为yyyy-MM-dd-->
<maxHistory>30</maxHistory>
<!-- 每个日志文件到10mb的时候开始切分,最多保留30天,但最大到20GB,哪怕没到30天也要删除多余的日志 -->
<totalSizeCap>20GB</totalSizeCap>
<!-- maxFileSize:这是活动文件的大小,默认值是10MB,测试时可改成5KB看效果 -->
<maxFileSize>10MB</maxFileSize>
</rollingPolicy>
<!--编码器-->
<encoder>
<!-- pattern节点,用来设置日志的输入格式 ps:日志文件中没有设置颜色,否则颜色部分会有ESC[0:39em等乱码-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level --- [%15.15(%thread)] %-40.40(%logger{40}) : %msg%n</pattern>
<!-- 记录日志的编码:此处设置字符集 - -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- error 日志-->
<!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
<!-- 以下的大概意思是:1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是project_error.log -->
<!-- 2.如果日期没有发生变化,但是当前日志的文件大小超过10MB时,对当前日志进行分割 重命名-->
<appender name="error_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志文件路径和名称-->
<File>logs/project_error.log</File>
<!--是否追加到文件末尾,默认为true-->
<append>true</append>
<!-- ThresholdFilter过滤低于指定阈值的事件。 对于等于或高于阈值的事件,ThresholdFilter将在调用其decision()方法时响应NEUTRAL。 但是,将拒绝级别低于阈值的事件 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level><!-- 低于ERROR级别的日志(debug,info)将被拒绝,等于或者高于ERROR的级别将相应NEUTRAL -->
</filter>
<!--有两个与RollingFileAppender交互的重要子组件。 第一个RollingFileAppender子组件,即RollingPolicy:负责执行翻转所需的操作。
RollingFileAppender的第二个子组件,即TriggeringPolicy:将确定是否以及何时发生翻转。 因此,RollingPolicy负责什么和TriggeringPolicy负责什么时候.
作为任何用途,RollingFileAppender必须同时设置RollingPolicy和TriggeringPolicy,但是,如果其RollingPolicy也实现了TriggeringPolicy接口,则只需要显式指定前者。-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 活动文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
<!-- 文件名:logs/project_error.2017-12-05.0.log -->
<!-- 注意:SizeAndTimeBasedRollingPolicy中 %i和%d令牌都是强制性的,必须存在,要不会报错 -->
<fileNamePattern>logs/project_error.%d.%i.log</fileNamePattern>
<!-- 每产生一个日志文件,该日志文件的保存期限为30天, ps:maxHistory的单位是根据fileNamePattern中的翻转策略自动推算出来的,例如上面选用了yyyy-MM-dd,则单位为天
如果上面选用了yyyy-MM,则单位为月,另外上面的单位默认为yyyy-MM-dd-->
<maxHistory>30</maxHistory>
<!-- 每个日志文件到10mb的时候开始切分,最多保留30天,但最大到20GB,哪怕没到30天也要删除多余的日志 -->
<totalSizeCap>20GB</totalSizeCap>
<!-- maxFileSize:这是活动文件的大小,默认值是10MB,测试时可改成5KB看效果 -->
<maxFileSize>10MB</maxFileSize>
</rollingPolicy>
<!--编码器-->
<encoder>
<!-- pattern节点,用来设置日志的输入格式 ps:日志文件中没有设置颜色,否则颜色部分会有ESC[0:39em等乱码-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level --- [%15.15(%thread)] %-40.40(%logger{40}) : %msg%n</pattern>
<!-- 记录日志的编码:此处设置字符集 - -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--给定记录器的每个启用的日志记录请求都将转发到该记录器中的所有appender以及层次结构中较高的appender(不用在意level值)。
换句话说,appender是从记录器层次结构中附加地继承的。
例如,如果将控制台appender添加到根记录器,则所有启用的日志记录请求将至少在控制台上打印。
如果另外将文件追加器添加到记录器(例如L),则对L和L'子项启用的记录请求将打印在文件和控制台上。
通过将记录器的additivity标志设置为false,可以覆盖此默认行为,以便不再添加appender累积-->
<!-- configuration中最多允许一个root,别的logger如果没有设置级别则从父级别root继承 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
<!-- 指定项目中某个包,当有日志操作行为时的日志记录级别 -->
<!-- 级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG > TRACE -->
<logger name="com.sailing.springbootmybatis" level="INFO">
<appender-ref ref="info_log" />
<appender-ref ref="error_log" />
</logger>
<!-- 利用logback输入mybatis的sql日志,
注意:如果不加 additivity="false" 则此logger会将输出转发到自身以及祖先的logger中,就会出现日志文件中sql重复打印-->
<logger name="com.sailing.springbootmybatis.mapper" level="DEBUG" additivity="false">
<appender-ref ref="info_log" />
<appender-ref ref="error_log" />
</logger>
<!-- additivity=false代表禁止默认累计的行为,即com.atomikos中的日志只会记录到日志文件中,不会输出层次级别更高的任何appender-->
<logger name="com.atomikos" level="INFO" additivity="false">
<appender-ref ref="info_log" />
<appender-ref ref="error_log" />
</logger>
</configuration>
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
# 9. 切换日志组合
切换 log4j2 日志框架:传送门
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
log4j2 支持 yaml 和 json 格式的配置文件
格式 | 依赖 | 文件名 |
---|---|---|
YAML | com.fasterxml.jackson.core:jackson-databind + com.fasterxml.jackson.dataformat:jackson-dataformat-yaml | log4j2.yaml + log4j2.yml |
JSON | com.fasterxml.jackson.core:jackson-databind | log4j2.json + log4j2.jsn |
# 10. 最佳实战
- 导入任何第三方框架,先排除它的日志包,因为Boot底层控制好了日志
- 修改
application.properties
配置文件,就可以调整日志的所有行为。如果不够,可以编写日志框架自己的配置文件放在类路径下就行,比如logback-spring.xml
,log4j2-spring.xml
- 如需对接 专业日志系统,也只需要把 logback 记录的 日志 灌倒 kafka 之类的中间件,这和 SpringBoot 没关系,都是日志框架自己的配置,修改配置文件即可
- 业务中使用 slf4j-api 记录日志。不要再 sout 了