
JavaEE进阶Spring Boot日志
- [1. 日志到底解决什么问题](#1. 日志到底解决什么问题)
- [1.1 System.out.print 的四个"原罪"](#1.1 System.out.print 的四个"原罪")
- [1.2 日志的三份正经工作](#1.2 日志的三份正经工作)
- [2. 在 Spring Boot 里打印第一条日志](#2. 在 Spring Boot 里打印第一条日志)
- [2.1 先拿到日志对象](#2.1 先拿到日志对象)
- [2.2 用日志对象输出的效果](#2.2 用日志对象输出的效果)
- [2.3 一条日志长什么样(六件套)](#2.3 一条日志长什么样(六件套))
- [3. 日志级别:给消息分级上报](#3. 日志级别:给消息分级上报)
- [3.1 六个级别](#3.1 六个级别)
- [3.2 默认只打印 info 及以上](#3.2 默认只打印 info 及以上)
- [4. 日志框架与门面模式](#4. 日志框架与门面模式)
- [4.1 SLF4J 是什么](#4.1 SLF4J 是什么)
- [4.2 门面模式:一家奶茶店就讲明白了](#4.2 门面模式:一家奶茶店就讲明白了)
- [4.3 没有门面的日子有多惨](#4.3 没有门面的日子有多惨)
- [5. 日志配置:级别、持久化、分割、格式](#5. 日志配置:级别、持久化、分割、格式)
- [5.1 改日志级别](#5.1 改日志级别)
- [5.2 日志持久化](#5.2 日志持久化)
- [5.3 日志文件分割](#5.3 日志文件分割)
- [5.4 自定义日志格式](#5.4 自定义日志格式)
- [6. 更省事的写法:@Slf4j](#6. 更省事的写法:@Slf4j)
- [7. 总结与速查表](#7. 总结与速查表)
前言:
这里是小谢同学的 Spring Boot 日志学习记录,整理日志的基本用法、级别、门面模式、配置与持久化。笔记用于自我复盘巩固,有错误欢迎大家指出,专栏还有 Java、网络、C 语言等系列笔记欢迎翻阅。同时也希望这篇文章能够帮助到你~
1. 日志到底解决什么问题
1.1 System.out.print 的四个"原罪"
日志其实我们一点都并不陌生,从 JavaSE 开始我们就在用 System.out.print 打印东西了,就好比我们在写算法题目的时候,有时候我们并不好观察,我们就可以使用IDEA的sout进行打印出来看看对不对?是不是符合我们的预期?
但是如果项目开始复杂起来了呢?
System.out.print 就明显不够用了。举个一个例子,之前我写一个简单的借书接口,所有信息全靠 println,在测试的时候出现了问题,控制台里密密麻麻的文字,我根本分不清哪一行是几点几秒打印的、是哪个类的、是哪一次请求的。
就像我们使用Spring Boot框架一样,Spring Boot 本身给我们设置好了一些日志的功能,我们在启动的时候能够观察到时间,位置,进程ID,等等信息:

四条硬伤总结一下:
| 对比项 | System.out.print | 专业日志框架 |
|---|---|---|
| 时间信息 | 没有,只能靠自己手写 | 默认带精确到毫秒的时间 |
| 日志级别 | 没有,所有输出一样重 | 有 ERROR/WARN/INFO/DEBUG 等级别 |
| 来源信息 | 没有,不知道哪个类打的 | 自动带类名、线程名、进程号 |
| 开关控制 | 只能删代码,改一行重新打包 | 改个配置项就能压掉某些日志 |
| 持久化 | 只能重定向,格式乱七八糟 | 一行配置写文件,还能自动切分 |
| 性能 | 同步阻塞,量大时拖慢接口 | 支持异步,量大了也不至于卡死 |
所以说白了,"专业的人干专业的事情",此时我们就需要一个专门的日志框架;
[↩ 返回目录](#↩ 返回目录)
1.2 日志的三份正经工作
除了最常见的"排查 bug",日志其实还在干三件大事:
① 系统监控
一个成熟的系统基本上都离不开监控;把每一个接口的响应时间,返回状态记录到日志中,然后根据统计分析设计阈值,当超过了阈值就会报警;比如统计日志中"库存不足"这个关键字出现了多少次,一分钟超过 100 次就发告警,这就是很常见的需求。
也可以换一个说法: 一天晚上,公司的服务器出现异常,假设出现的问题只有以下这几种;
- 第一种:可能出现的情况: P3(低优先级、日志统计、非核心小报错)
通知:纯邮箱、日报汇总
- 不弹任何强提醒,定期复盘
- 第二种:P2(预警、慢查询、磁盘慢慢要满、证书快到期)
通知:群消息 + 邮箱
- 不用半夜打扰,上班之后排查就行
- 第三种:P1(高危但业务没全崩:错误率飙升、内存打满、单实例宕机)
通知:短信 + 钉钉 / 企微机器人
- 不用电话吵醒,看到消息尽快处理,适合晚上非致命故障
- 第四种:P0(核心炸了,最高级:支付、下单、首页挂了,大量用户受影响)
通知:电话(值班 on-call 工程师)+ 短信 + 群告警
- 先打值班开发 / 运维,不是直接打领导;工程师处理超时没确认,才升级给组长、领导打电话
- 目标:立刻接电话,5~10 分钟响应止损
② 数据采集
采集来的数据能用在很多地方:
- 数据统计:页面的浏览量(PV)、访客量(UV)、点击量,用来看运营效果;
- 推荐排序:把用户浏览过什么、停留多久记进日志,算法同学拿去训练模型做推荐。购物、广告、新闻这些领域基本都在这么干。
③ 日志审计
这个在正经公司里特别重要。运营在后台删了一条数据、改了一条数据,如果没有操作日志,你根本不知道是谁干的;记了日志,一查就清楚。还有些内部违规、信息泄漏的情况(比如客户信息被偷偷导出去卖),没有日志留存,事后根本没法追查。所以很多公司连"频繁查询客户信息"都会记录并报警。
就好比生活中的例子:
别人污蔑你进去他房间偷东西,但是没有摄像头,刚好你又进去过,东西又丢失了,没有任何的证明来证明自己没有偷
就像数据库设计中,一般使用的都是逻辑删除,而不是正真意义上的删除,目的就是不要去麻烦人家,欠别人的人情,即使自己不小心删错了,也能进行找回来,不会造成很大的损失
一句话:日志不只是给开发看的,它还是运维的眼睛、算法的粮食、合规的证据。
[↩ 返回目录](#↩ 返回目录)
2. 在 Spring Boot 里打印第一条日志
2.1 先拿到日志对象

Spring Boot 项目一启动,控制台就自带一堆日志,这些可不是 System.out 打的,是内置的日志框架干的活。Spring Boot 内置的是 SLF4J + Logback,我们直接调 SLF4J 就能输出日志。
打印日志就两步:拿到日志对象 → 用它输出内容。
拿对象要用日志工厂 LoggerFactory(注意是工厂,不是直接 new):
java
private static final Logger logger = LoggerFactory.getLogger(TestController.class);
getLogger 要传一个参数,用来标记这个日志是谁打的。一般就传当前类的 class,这样出问题时看日志里的类名就能直接定位到代码,非常直观。
⚠️ 踩坑提醒:Logger 有几个同名类,一定要导 org.slf4j.Logger,别导成 org.apache.logging.log4j.Logger 或者 java.util.logging.Logger,不然要么编译报错,要么日志打不出来。
[↩ 返回目录](#↩ 返回目录)
2.2 用日志对象输出的效果
我简单写一个借阅的接口,例如:
java
@RestController
public class TestController {
private static final Logger logger = LoggerFactory.getLogger(TestController.class);
@RequestMapping("/borrow")
public String borrow() {
logger.info("用户借阅《Java 编程思想》,编号 B1024");
System.out.println("用户借阅《Java 编程思想》,编号 B1024");
return "借阅成功";
}
}
我们可以简单来看看日志情况:

访使用postman或者浏览器访问一下 http://localhost:8080/book/borrow,控制台输出的长这样:
对比一下 System.out 打出来的那行干巴巴的文字,信息量差得不是一点半点。
[↩ 返回目录](#↩ 返回目录)
2.3 一条日志长什么样(六件套)
上面那行其实是六块内容拼起来的,从左往右看:
| 序号 | 元素 | 示例 | 说明 |
|---|---|---|---|
| 1 | 时间日期 | 2026-10-21T17:28:08.040+08:00 |
精确到毫秒,排查偶发问题时很关键 |
| 2 | 日志级别 | INFO |
ERROR、WARN、INFO、DEBUG、TRACE |
| 3 | 进程 ID | 18632 |
多实例部署时用来区分是哪个进程 |
| 4 | 线程名 | [nio-8080-exec-1] |
能看出是哪个线程处理的,并发问题全靠它 |
| 5 | Logger 名 | c.e.b.c.BookController |
通常就是类名(超长会缩写) |
| 6 | 日志内容 | 用户借阅... |
我们自己写的那句话 |
看到第 5 项没?这就是为什么 getLogger 里要传当前类的 class,否则这里只会显示一个你自己起的名字,定位起来全靠猜。
[↩ 返回目录](#↩ 返回目录)
3. 日志级别:给消息分级上报
打个比方。假设你是一家两万人公司的老板,如果每个员工的鸡毛蒜皮都直接汇报给你,你早就干不下去了。于是有了组织架构:组员报组长,组长报研发一组,研发一组报 Java 研发......层层上报,重要的才往上走。
日志级别就是这个道理。给消息分好级,出问题时只看 ERROR,日常看 INFO,就不用在一堆输出里翻半天了。
3.1 六个级别
日志从高到低分一共6个日志等级(Spring Boot 用的 Logback 里没有 FATAL,它被并到了 ERROR):
| 级别 | 含义 | 借书场景里的例子 |
|---|---|---|
| FATAL | 致命,系统级错误,必须立刻处理 | 数据库连不上了,整个服务不可用 |
| ERROR | 错误,但仍不影响系统继续运行 | 扣库存失败,这一单借阅失败返回 500 |
| WARN | 警告,不影响使用,但值得注意 | 用户积分不够,跳过积分抵扣 |
| INFO | 普通信息,记录正常运行轨迹 | 借阅成功、项目启动完成 |
| DEBUG | 调试信息,开发期用 | 进入 borrow 方法,bookId=1024 |
| TRACE | 追踪,比 DEBUG 更细 | 循环里每一轮迭代的中间值 |
注意: Bug 级别没有半毛钱关系
我们需要知道的是,日志级别是自己开发设置的,给开发人员自己看的;Bug 级别是测试根据现象和影响范围定的。
还有个口诀:级别越高,能收到的消息越少。
[↩ 返回目录](#↩ 返回目录)
3.2 默认只打印 info 及以上
Logger 对每个级别都有对应的方法,我一次性全打一遍:
java
@RequestMapping("/levels")
public String levels() {
logger.trace("========== trace ==========");
logger.debug("========== debug ==========");
logger.info("========== info ==========");
logger.warn("========== warn ==========");
logger.error("========== error ==========");
return "打印不同级别的日志";
}
使用postman访问: http://127.0.0.1:8080/levels
结果只出来三行:

这下面两行根本就没有打印出来:
java
logger.trace("========== trace ==========");
logger.debug("========== debug ==========");
踩坑记录:第一次见这个现象我还以为是 trace 和 debug 方法没生效。其实 Spring Boot 默认日志级别就是 info,只有大于等于 当前级别的日志才会输出,所以 info、warn、error 出来了,debug 和 trace 被过滤掉了。想让它们出现,改配置就行(下一节说: [5.1 改日志级别](#5.1 改日志级别))。
💡 再补一个小技巧:SLF4J 支持 {} 占位符,不用自己拼字符串,性能也更好:
java
logger.info("用户 {} 借阅了《{}》,编号 {}", userId, bookName, bookNo);
[↩ 返回目录](#↩ 返回目录)
4. 日志框架与门面模式
4.1 SLF4J 是什么
市面上日志框架一大堆:Log4j、Logback、Log4j2、JUL......那为什么我们代码里只导 org.slf4j 的包?

因为 SLF4J 本身不是 一个具体的日志实现,它只是一层抽象:一套规范、一套接口。它不能单独干活,必须搭配 Logback 这类真正的实现来用。
这就引出它背后的设计思想------门面模式。
[↩ 返回目录](#↩ 返回目录)
4.2 门面模式:一家奶茶店就讲明白了
门面模式(Facade Pattern,也叫外观模式)的定义是:为子系统中的一组接口提供一个统一的入口,让子系统更容易使用。
生活中的例子也比较多,例如去医院挂号,门诊,化验,取药,等流程比较繁琐,患者需要自己跑一圈,这可比较累了;但是如果有一个导诊台帮你把这些事情穿起来,你只需要跟导诊台打交道就行;
我用奶茶店来写这段代码------顾客只跟收银台说话,后面的泡茶、加料、封口各干各的:
java
public class FacadeDemo {
public static void main(String[] args) {
MilkTeaShop shop = new MilkTeaShop();
shop.order("珍珠奶茶");
}
}
/**
* 奶茶店门店:门面角色,对外的统一入口
*/
class MilkTeaShop {
private TeaMaker teaMaker = new TeaMaker();
private ToppingAdder toppingAdder = new ToppingAdder();
private CupSealer cupSealer = new CupSealer();
public void order(String name) {
System.out.println("收到订单:" + name);
teaMaker.brew();
toppingAdder.add();
cupSealer.seal();
System.out.println("出餐,请取餐~");
}
}
class TeaMaker {
public void brew() {
System.out.println("① 泡茶底,焖 8 分钟");
}
}
class ToppingAdder {
public void add() {
System.out.println("② 加珍珠,两勺");
}
}
class CupSealer {
public void seal() {
System.out.println("③ 封口,贴标");
}
}

顾客(客户端)压根不知道后面有几个工位、谁先谁后,他只知道"点单"这一个动作。这就是门面的价值:
| 优点 | 说明 |
|---|---|
| 降低耦合 | 客户端只依赖门面,子系统内部怎么改都不影响调用方 |
| 使用更简单 | 客户端不用关心执行顺序和内部细节 |
| 安全性更好 | 门面里不开放的方法,外面就调不到 |
对于顾客来说: 我不关心你后面有几道工序,我只关系我需要一杯奶茶~
门面模式里面之一两个角色: 外观角色 (Facade,对外的统一接口,上面的 MilkTeaShop)和子系统角色(SubSystem,可以有很多个,而且子系统本身并不知道门面的存在,对它来说门面也只是个普通调用方)。
用我们现在的话来说就是: 我要去买奶茶,我付钱,你给我奶茶~
[↩ 返回目录](#↩ 返回目录)
4.3 没有门面的日子有多惨
把奶茶换成日志框架就懂了。假设你的项目用的是 Log4j,结果依赖的一个第三方组件(比如某个消息中间件)用的是 Logback,那你就得把 Logback 也引进来。麻烦随之而来:
- 两套框架的 API 和配置文件都不一样,你得维护两份配置;
- 哪天想换框架,业务代码里的日志调用全得改,改的过程还容易冲突;
- 依赖一多,可能同时存在三四套日志,配置越堆越乱。
引入门面模式之后就清爽了: 应用程序只需要面向SLF4J这一套的接口代码,地城换成Logback,Log4j2都行.,业务层代码一行都不需要动,同理配置文件也只有一份;
所以那句话说得挺对:SLF4J 让你的代码独立于任何特定的日志 API。
[↩ 返回目录](#↩ 返回目录)
5. 日志配置:级别、持久化、分割、格式
Spring Boot 的日志配置全写在 application.properties 或 application.yml 里,两个文件配一个就行 。两者可以互换,规律很简单:properties 里的点 .,在 yml 里就是换行。
5.1 改日志级别
修改日志的级别,我们只需要配置相关内容: logging.level:
例如:
properties
logging.level.root=trace
或者
yaml
logging:
level:
root: trace
root 表示根 logger,也就是全局生效。改完重启,刚才那五条日志就全出来了------debug 和 trace 都在。实际开发里一般不用全局开 debug,日志会爆炸,通常是给某个包单独开,比如只盯自己写的 Controller:
但是当你修改完默认的日志级别之后,你去启动项目,你会发现,下面的日志滚动的非常快~
[↩ 返回目录](#↩ 返回目录)
5.2 日志持久化
控制台日志一关窗口就没了,线上出了问题想回溯根本没戏。把日志存到文件里,就叫持久化 。有两种配法。
方式一:指定文件名
properties
logging.file.name=log/book.log
或者:
yaml
logging:
file:
name: log/book.log
路径可以写相对路径,也可以写绝对路径(比如 D:/logs/book.log)。配完跑一下,项目根目录下就多出 log/book.log,内容就是控制台打印的那些。
方式二:只指定目录
properties
logging.file.path=D:/logs
或者:
yaml
logging:
file:
path: D:/logs
这种方式只能指定目录,文件名是固定的 spring.log,不能自己起名。
⚠️ 踩坑记录:两个都配了会怎样?只生效一个,以 logging.file.name 为准。我第一次配的时候两个都写了,还纳闷为什么文件名不是我想要的那个,后来才发现是优先级问题。所以别贪心,选一个用。
[↩ 返回目录](#↩ 返回目录)
5.3 日志文件分割
所有日志都堆在一个文件里,跑上几天就是几个 G,打开都能卡死编辑器的。日志框架其实早就想到了,默认超过 10MB 就自动切分。
想自己定规则,配这两个项:
| 配置项 | 说明 | 默认值 |
|---|---|---|
logging.logback.rollingpolicy.file-name-pattern |
分割后的文件名格式 | ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz |
logging.logback.rollingpolicy.max-file-size |
单个文件超过多大就切 | 10MB |
为了看得清楚,我把阈值调成 1KB 做个演示:
properties
logging.logback.rollingpolicy.file-name-pattern=${LOG_FILE}.%d{yyyy-MM-dd}.%i
logging.logback.rollingpolicy.max-file-size=1KB
或者:
yaml
logging:
logback:
rollingpolicy:
max-file-size: 1KB
file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i
多点几次接口多打些日志,目录里就出现了一堆文件:

命名规律就是"日志名.日期.索引"。1KB 只是为了演示,企业里一般设 200MB 或 500MB,这个没有硬性标准,看磁盘和日志量自己定。
[↩ 返回目录](#↩ 返回目录)
5.4 自定义日志格式
默认格式看习惯了其实挺好,但有时候需要精简,或者想把自己的方法名、行号加上去。控制台和文件可以分开配:
| 配置项 | 作用 |
|---|---|
logging.pattern.console |
控制台日志格式 |
logging.pattern.file |
日志文件格式 |
常用占位符先收好:
| 占位符 | 含义 |
|---|---|
%d{yyyy-MM-dd HH:mm:ss.SSS} |
日期时间,精确到毫秒 |
%5p |
日志级别,不足 5 位右边补空格 |
%t / %thread |
线程名 |
%c |
类的全限定名 |
%M |
方法名 |
%L |
行号 |
%m / %msg |
日志内容 |
%n |
换行 |
%clr(表达式){颜色} |
给日志上色,支持 blue、cyan、faint、green、magenta、red、yellow |
举个精简版配置:时间 + 类名 + 方法 + 行号 + 线程 + 内容:
properties
logging.pattern.console='%d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
或者:
yaml
logging:
pattern:
console: '%d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
file: '%d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
这样看起来就清爽多了:

格式里那几个数字规则也顺手记一下:%5 是不够 5 位时右边补空格,%-5 是左边补空格,%.15 是超过 15 位就截断,%15.15 是两个都干。
⚠️ 踩坑记录:想用 %clr 上色,结果控制台一点颜色都没有?不是配置写错了,是 IDEA 默认不给控制台 ANSI 颜色。要在启动配置的 VM options 里加一句:
text
-Dspring.output.ansi.enabled=ALWAYS
重启就有了。不过说实话,实际开发里默认格式就够用,除非有特殊要求,不太建议折腾。

只不过我这是专业版,自动给日志渲染上了颜色~
[↩ 返回目录](#↩ 返回目录)
6. 更省事的写法:@Slf4j
每个类都写一遍 LoggerFactory.getLogger(xxx.class),说实话挺烦的,尤其是 Controller 一多,粘贴都粘到手软。Lombok 给了个偷懒方案。
第一步,加依赖(Spring Boot 项目一般创建时就自带了):
xml
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
第二步,类上打 @Slf4j,然后直接用 log:
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/book")
public class LogController {
public void log() {
log.info("用户借阅《Java 编程思想》,编号 B1024");
}
}
就这么简单,log 这个对象是注解帮我们生成的,不用自己声明。
⚠️ 踩坑提醒:如果 IDEA 里 log 报红,基本都是 Lombok 没配好------要么没开注解处理器(Enable annotation processing),要么插件没装。不是代码写错了。
再给大叫推荐几个工具:

这几个工具还是非常的好用的,具体用法这里就不过多解释了;

我们就能打开对一些配置文件进行快速添加:

[↩ 返回目录](#↩ 返回目录)
7. 总结与速查表
这一节内容不多,但都是天天要用的东西。三句话概括:
- 日志是项目的基础设施,能帮我们发现问题、分析问题、定位问题,还能做监控、数据采集和审计;Spring Boot 内置 SLF4J + Logback,默认以 info 级别输出到控制台,用
@Slf4j+log就能快速打日志。 - 日志有 6 个级别(FATAL/ERROR/WARN/INFO/DEBUG/TRACE),级别越高收到的消息越少;通过配置文件名或目录可以把日志持久化到磁盘。
- 想改行为就改配置:级别、存哪儿、怎么切、长什么样,四个维度全都能配。
其实一些配置文件我们不需去记: 我们可以提前存储起来,需要使用的时候能想起来,直接使用复制粘贴的方式~
配置速查表
| 需求 | 配置项 | 备注 |
|---|---|---|
| 改全局日志级别 | logging.level.root=debug |
也可给指定包单独设 |
| 指定日志文件名 | logging.file.name=log/book.log |
支持绝对/相对路径 |
| 只指定日志目录 | logging.file.path=D:/logs |
文件名固定为 spring.log |
| 两者同时配 | --- | 以 logging.file.name 为准 |
| 切分阈值 | logging.logback.rollingpolicy.max-file-size |
默认 10MB |
| 切分文件名 | logging.logback.rollingpolicy.file-name-pattern |
默认含日期和索引 |
| 控制台格式 | logging.pattern.console |
占位符见上表 |
| 文件格式 | logging.pattern.file |
与控制台可分开配 |
| 控制台颜色 | VM options 加 -Dspring.output.ansi.enabled=ALWAYS |
IDEA 需要 |
易错点速记
| 坑 | 正解 |
|---|---|
| Logger 导错包 | 必须是 org.slf4j.Logger |
| debug 日志不打印 | 默认级别是 info,配置里放开 |
| 两个持久化配置都写了 | 只有 logging.file.name 生效 |
| 只配 path 想改文件名 | 不行,是固定的 spring.log |
| 颜色配置不生效 | IDEA 要开 ANSI 输出 |
| 用字符串拼接打日志 | 改用 {} 占位符 |
好了,日志这块就先整理到这里。下一篇继续 Spring Boot 的其他内容,感兴趣的可以蹲一下~
如果这篇笔记对你有帮助,欢迎点赞 👍 收藏 ⭐ 留言 📝,有写错的地方也请帮忙指正,一起进步!
[↩ 返回目录](#↩ 返回目录)
