- 参考文档:Spring Boot入门(21):轻松搞定日志记录!Spring Boot入门之Log4j2整合及配置详解
- 参考文档:Spring Boot入门(22):使用LogBack实现日志文件本地保存,让你的日志更安全可靠!
目录
Spring Boot 整合 Log4j2
一、为什么要用日志
没有日志不影响程序正确运行,但没有日志的项目是不完整的。日志的作用集中在三件事:
| 作用 | 说明 |
|---|---|
| 调试 | 查看栈信息与运行结果,比大量使用 System.out / System.err 更专业 |
| 错误定位 | 运行一段时间后出现数据、网络、内存等异常时,快速定位错误位置 |
| 数据分析 | 日志包含点击行为、兴趣偏好等用户数据,可用于用户画像 |
二、日志框架与 Log4j2
常见日志框架:Log4j、Log4j2、Slf4j、JDKLog、Logback。
- Log4j 已停止更新,Apache 推出 Log4j2 作为其替代与升级,同时改进了 Logback 的一些架构问题。
- 目前的主流组合是 Log4j2 + Slf4j。
Log4j 由三部分构成:
| 组成部分 | 含义 |
|---|---|
| 日志信息优先级 | 指定这条日志信息的重要程度(即日志级别) |
| 日志信息输出目的地 | 控制台、文件、GUI 组件、套接口服务器、NT 事件记录器、UNIX Syslog 守护进程等 |
| 日志信息输出格式 | 控制日志信息的显示内容 |
这三者都可以通过一个配置文件灵活调整,不需要修改应用代码。
三、日志级别
完整级别顺序:
ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
其中常用的六个级别:
| 级别 | 说明 |
|---|---|
trace |
追踪,最低级别,一般不使用 |
debug |
调试,开发阶段常设为最低级别 |
info |
信息,输出重要信息,使用较多 |
warn |
警告,程序不报错但需要提醒开发者 |
error |
错误,开发中常用 |
fatal |
严重错误,一旦发生程序基本要停止 |
设置某个级别后,会打印大于等于该级别的日志:例如设为 ERROR 只打印 ERROR、FATAL;设为 ALL 全部打印;设为 OFF 一条都不打印。所以严格来说只有六种日志级别。
四、集成步骤
环境:IDEA 2021.3、JDK 1.8、Spring Boot 2.3.1.RELEASE、Maven 3.8.2。
1. 引入依赖
<!-- log4j2的依赖引入 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
2. 编写测试用例,输出各级别日志
package com.example.demo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class Log4j2Test {
private static Logger logger = LogManager.getLogger(Log4j2Test.class);
@Test
void log4j2Test() throws Exception {
logAll();
}
// 打印各种级别的日志用于测试
public static void logAll() throws Exception {
// TRACE < DEBUG < INFO < WARN < ERROR < FATAL
logger.trace("trace level log");
logger.debug("debug level log");
logger.info("info level log");
logger.warn("warn level log");
logger.error("error level log");
logger.fatal("fatal level log");
}
}
3. 业务代码中打印日志
@GetMapping("/demo1")
public void test1() {
log.info("程序执行咯!记录日志啦");
}
控制台输出:
17:01:22.215 [http-nio-8080-exec-5] INFO com.example.demo.controller.TestController - 程序执行咯!记录日志啦
log4j2.xml 配置
src/main/resources/
├── application.yml
└── log4j2.xml ← 自己新建
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
配置分为两类:
| 节点 | 作用 |
|---|---|
Appenders |
定义日志输出目的地、内容和格式 |
Loggers |
定义日志级别以及使用的 Appenders |
输出格式与验证
PatternLayout 的 pattern 决定日志格式:
| 占位符 | 含义 |
|---|---|
%date{HH:mm:ss.SSS} |
输出日志打印日期,精确到毫秒 |
[%thread] |
当前线程名称 |
%-5level |
级别从左显示 5 个字符宽度 |
%logger{36} |
Logger 名称最长 36 个字符,一般是类的全限定路径名 |
%msg |
日志信息 |
%n |
日志换行 |
按上面的配置输出效果:
17:01:22.215 [http-nio-8080-exec-5] INFO com.example.demo.controller.TestController - 程序执行咯!记录日志啦
注意:示例中 Root level="ERROR",因此 trace / debug / info / warn 都不会输出,只有 error 及以上级别才会打印到控制台。
Spring Boot 整合 LogBack 实现日志本地保存
一、为什么可以直接用 Logback
- Logback 是 Spring Boot 的默认日志框架:
spring-boot-starter-web已自带spring-boot-starter-logging(内含 Logback),无需额外引入依赖。 - 本节目标:日志在输出控制台的同时,按用途分门别类写入本地文件,日后排查问题只需查阅本地日志文件即可。
| 文件 | 用途 |
|---|---|
sys.log |
系统日志(root 兜底) |
error.log |
只记录 ERROR 级别,异常一目了然 |
api.log |
接口日志 |
demo_system.log |
业务包的非 ERROR 操作日志 |
bak/ 目录 |
历史日志按天滚动备份 |
注意:
Logback 与第一章的 Log4j2 是二选一的关系。
若之前按第一章引入了 log4j2,改用 Logback 前需移除 log4j2 依赖并恢复 web starter 的默认依赖,否则两套日志实现会冲突。
二、创建 logback-spring.xml
在 resources 目录下新建 logback-spring.xml(文件名建议保持一致):
src/main/resources/
├── application.yml
└── logback-spring.xml ← 自己新建
三、配置模板(核心)
<?xml version="1.0" encoding="utf-8"?>
<!--
🔥 核心逻辑:
谁(logger)产生日志 → 发到哪里(appender)
🔥 当前设计:
error.log → 所有 ERROR
api.log → Controller
operation.log → 所有业务日志
sys.log → 第三方日志
-->
<!--
scan="true":开启配置文件热加载;
scanPeriod="5 minutes":每 5 分钟检测一次本文件是否被修改,
修改后无需重启应用即可生效(生产环境临时调整日志级别非常实用)。
-->
<configuration scan="true" scanPeriod="5 minutes">
<!-- ================== 日志目录 ================== -->
<!--
property:定义变量,后续以 ${LOG_HOME} 引用,集中管理路径。
相对路径 logs/hellospring 相对于"应用启动时的工作目录"
(IDEA 中一般是项目根目录;jar 部署时是执行命令所在目录)。
-->
<property name="LOG_HOME" value="logs/hellospring"/>
<!-- ================== 控制台日志 ================== -->
<!--
appender:日志输出目的地,name 供 <appender-ref> 引用。
ConsoleAppender:输出到控制台(System.out),开发期调试用。
pattern 占位符:
%d{HH:mm:ss.SSS} 时间(控制台省略日期,保持简洁)
[%thread] 打日志的线程名
%-5level 级别左对齐、最小宽 5(各列对齐)
%logger{36} logger 名,超 36 字符自动缩写包名,如 c.l.h.UserController
%msg 日志内容 %n 换行
-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 控制台简洁一点 -->
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- ================== 系统日志(第三方)sys.log ================== -->
<!--
RollingFileAppender:带滚动(拆分归档)能力的文件输出。
rollingPolicy 决定"何时以及如何拆分":
TimeBasedRollingPolicy 按时间滚动,跨天时自动把旧文件
改名为 bak/sys.log.2026-11-11,新日志继续写入 sys.log。
注意:这里未配 <maxHistory>,归档永不清理,磁盘会持续增长。
-->
<appender name="sysLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 当前正在写入的活动文件(不含日期,日期只出现在归档名里) -->
<file>${LOG_HOME}/sys.log</file>
<!-- 每天滚动一次,历史文件归档到 bak/ 子目录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/bak/sys.log.%d{yyyy-MM-dd}</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger:%L - %msg%n</pattern>
</encoder>
</appender>
<!-- ================== 错误日志 error.log ================== -->
<appender name="errorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--
LevelFilter:对日志级别"精确匹配"。
level=ERROR + onMismatch=DENY → 非 ERROR 一律拒绝;
onMatch 未配置时默认 NEUTRAL(放行) → 只放行 ERROR。
效果:error.log 成为全应用统一的错误日志。
-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMismatch>DENY</onMismatch>
</filter>
<file>${LOG_HOME}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/bak/error.log.%d{yyyy-MM-dd}</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger:%L - %msg%n</pattern>
</encoder>
</appender>
<!-- ================== 业务日志 operation.log ================== -->
<appender name="operationLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--
反向过滤:级别匹配到 ERROR 则 DENY,
其余(INFO/WARN/DEBUG)全部 ACCEPT。
→ error 由 errorLog 专管,本文件只记业务日志,
避免同一条 ERROR 在两个文件重复出现。
-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
<file>${LOG_HOME}/operation.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/bak/operation.log.%d{yyyy-MM-dd}</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger:%L - %msg%n</pattern>
</encoder>
</appender>
<!-- ================== 接口日志 api.log ================== -->
<!--
不加 filter:Logger 交给它的日志全收(含 ERROR),
完整记录 Controller 的进/出,便于还原一次请求的完整链路。
-->
<appender name="apiLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/api.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/bak/api.log.%d{yyyy-MM-dd}</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger:%L - %msg%n</pattern>
</encoder>
</appender>
<!-- ================== Logger配置 ================== -->
<!--
🔥 Controller 层(接口日志)
logger:按包名前缀匹配把日志路由到指定 appender;
更具体的包(controller)优先于父包(com.lxy.hellospring)。
additivity="false":禁止日志继续向父级(root)传播,
防止 Controller 日志再被 root 写一遍到 sys.log(去重+隔离)。
-->
<logger name="com.lxy.hellospring.controller" additivity="false">
<!-- 控制台 -->
<appender-ref ref="STDOUT"/>
<!-- 接口日志 -->
<appender-ref ref="apiLog"/>
<!-- 业务日志(保证链路完整) -->
<appender-ref ref="operationLog"/>
<!-- 错误日志 -->
<appender-ref ref="errorLog"/>
</logger>
<!--
🔥 业务层(Service / DAO 等)
兜底本包其余类:不打到 apiLog,只进 operationLog;
同样 additivity="false" 阻断向 root 传播。
-->
<logger name="com.lxy.hellospring" additivity="false">
<!-- 控制台 -->
<appender-ref ref="STDOUT"/>
<!-- 业务日志 -->
<appender-ref ref="operationLog"/>
<!-- 错误日志 -->
<appender-ref ref="errorLog"/>
</logger>
<!-- ================== 根日志(第三方日志) ================== -->
<!--
root:所有未显式配置的 logger 的最终兜底——
Spring/MyBatis 等第三方库的包名不在 com.lxy.hellospring 下,
全部落到这里。level="INFO" 过滤第三方框架的 DEBUG 噪音;
它只写控制台和 sys.log,不污染业务/错误日志文件。
-->
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="sysLog"/>
</root>
</configuration>
配置要点:
| 配置 | 作用 |
|---|---|
scan="true" scanPeriod="5 minutes" |
配置文件变更后每 5 分钟自动重新加载 |
<property name="LOG_HOME"> |
定义日志根目录变量,后续用 ${LOG_HOME} 引用 |
ConsoleAppender |
控制台输出 |
RollingFileAppender |
滚动文件输出:写当前文件 + 按规则备份历史 |
TimeBasedRollingPolicy |
按 %d{yyyy-MM-dd} 按天滚动,历史文件备份到 bak/ |
LevelFilter |
级别过滤:error.log 只收 ERROR(onMismatch=DENY);operation.log 只收非 ERROR(onMatch=DENY、onMismatch=ACCEPT) |
additivity="false" |
阻断向父级 logger 传播,避免同一条日志被重复写多份 |
<root level="INFO"> |
全局兜底,未被具体 logger 命中的日志进 sys.log + 控制台 |
原文把同名 logger(com.example.connectmysqldemo)写成了两段(错误日志一段、操作日志一段),Logback 会把两段的 appender 累加到同一个 logger 上;
这里直接合并为一段,效果一致:ERROR 由 errorLog 的过滤器接走,其余由 opLog 接走。
四、重启验证
- 配置完成后刷新 Maven(或关闭项目重新打开),再重启项目,避免误以为日志配置未生效。
- 项目目录下生成
logs/springboot-demo及各日志文件,控制台输出什么,实时写入对应的 log 文件。 - 定义一个接口请求验证:
/**
* 查询db所有用户信息
*/
@GetMapping("/get-users")
@ApiOperation(value = "查询db所有用户信息", notes = "查询db所有用户信息")
public List<UserEntity> getUserList() {
return userService.getUsers();
}
日志文件中可以清晰看到执行的 SQL 及返回条目数,方便日后 bug 定位。
五、控制台实时打印 SQL
在 application.yml 中把持久层日志级别调到 debug:
# 日志设置
logging:
level:
# 持久层日志级别
com.example.connectmysqldemo.dao: debug
dao 层设为 debug 后,MyBatis 执行的 SQL 会实时打印到控制台(同时按 appender 配置写入对应日志文件)。