目录
Spring Boot 日志详解:从入门到精通(新手版)
1. 日志概述:为什么要学?
1.1 从System.out.println到专业日志框架
2. 日志使用:手把手教你写代码
2.1 打印日志:第一个日志程序
知识点:如何获取日志对象
知识点:日志占位符避免字符串拼接
2.2 日志框架介绍:门面模式详解
知识点:什么是门面模式?
知识点:SLF4J就是日志框架的门面
2.3 日志格式说明
2.4 日志级别:代码演示与配置效果
知识点:六级别详解
知识点:配置日志级别如何影响代码行为
2.5 日志配置详解
知识点:日志持久化(存文件)
知识点:日志分割配置
3. Lombok简化日志:更优雅的写法
3.1 添加依赖
3.2 使用@Slf4j注解
4. 日志最佳实践与扩展知识
4.1 日志安全:避免记录敏感信息
4.2 高性能日志记录:性能优化技巧
技巧1:始终使用占位符
技巧2:复杂日志使用条件判断
4.3 集中式日志管理:微服务场景
4.4 日志标准化:JSON结构化日志
4.5 日志与追踪结合:分布式TraceID
5. 总结:知识体系回顾
5.1 核心知识点速查表
5.2 新手学习路径建议
5.3 代码 vs 结论:如何建立联系?
附录:完整项目结构示例
Spring Boot 日志详解:从入门到精通(新手版)
1. 日志概述:为什么要学?
1.1 从System.out.println到专业日志框架
新手理解:想象一下,你做的程序就像一个黑盒子。当它出问题时,你需要透过"窗户"看里面发生了什么。System.out.println就像在小盒子上戳个小洞,只能看到一点点;而专业日志框架就像安装了监控摄像头,能全方位、分级别、可配置地观察系统状态。
核心痛点:
System.out.println打印的信息只能看到控制台,程序重启就消失无法区分信息的重要性(是致命错误还是普通提示?)
不能控制输出位置(只能控制台,不能存文件)
影响性能(每次都要打印,无法动态关闭)
2. 日志使用:手把手教你写代码
2.1 打印日志:第一个日志程序
知识点:如何获取日志对象
/* * 【完整代码示例1:基础日志打印】 * 这段代码演示了如何在Spring Boot中获取并使用日志对象 * 对应结论:Spring Boot内置SLF4J框架,通过LoggerFactory获取日志对象 */ // ========== import区域:导入必要的类 ========== import org.slf4j.Logger; // 【第1行】导入SLF4J的Logger接口,这是所有日志操作的入口 import org.slf4j.LoggerFactory; // 【第2行】导入LoggerFactory工厂类,用于创建Logger实例 import org.springframework.web.bind.annotation.RequestMapping; // 【第3行】导入Spring MVC的请求映射注解,用于定义URL路径 import org.springframework.web.bind.annotation.RestController; // 【第4行】导入REST控制器注解,表示这个类处理HTTP请求 // ========== 类定义区域 ========== @RestController // 【第6行】标记这个类是一个RESTful控制器,能处理HTTP请求并返回JSON或纯文本 public class LoggerController { // 【第7行】定义控制器类,类名自定义 // ========== 核心:获取日志对象 ========== // 【第9行】声明一个静态(final不可变)、线程安全的日志对象 // static:所有实例共享同一个logger对象,节省内存 // final:确保logger引用不会被修改,保证线程安全 // LoggerFactory.getLogger(LoggerController.class):创建与当前类绑定的logger // 参数作用:LoggerController.class告诉框架"这个日志来自哪个类",日志中会显示类名 private static final Logger logger = LoggerFactory.getLogger(LoggerController.class); // ========== 请求处理方法 ========== @RequestMapping("/logger") // 【第13行】定义URL路径,访问 http://localhost:8080/logger 会触发此方法 public String logger() { // 【第14行】定义处理请求的方法,返回字符串 // 【第16行】使用logger对象打印INFO级别的日志,日志内容自定义 // 这行代码执行后,日志会显示在控制台(默认配置) logger.info("--------------要输出日志的内容----------------"); return "打印日志"; // 【第18行】返回给浏览器的响应内容 } }代码与结论的联系:
结论:"通过LoggerFactory获取当前类的Logger实例"
代码体现:第9行
LoggerFactory.getLogger(LoggerController.class)就是具体的获取方式,参数LoggerController.class保证了日志中能显示完整的类名路径,方便定位问题
知识点:日志占位符避免字符串拼接
// 演示代码片段 String userId = "user123"; String action = "login"; /* * 【重点】为什么用占位符{}而不是字符串拼接? * * 字符串拼接的问题: * logger.debug("用户 " + userId + " 执行了 " + action + " 操作"); * 即使日志级别设置为WARN(不输出DEBUG),JVM仍会先执行字符串拼接,浪费性能 * * 占位符的优势: * 下面这行代码,当日志级别为WARN时,不会执行字符串拼接,直接跳过,性能更好 */ logger.debug("用户 {} 执行了 {} 操作", userId, action);2.2 日志框架介绍:门面模式详解
知识点:什么是门面模式?
现实生活中的例子:你去餐厅吃饭,不需要直接去后厨告诉厨师怎么炒菜、告诉服务员怎么摆盘。你只需要对服务员(门面)说"我要一份宫保鸡丁",服务员会协调后厨、配菜、上菜等所有子系统。
代码层面的体现:
/* * 【完整代码示例2:门面模式演示】 * 这段代码展示了如何通过门面模式统一控制多个灯 * 对应结论:门面模式简化客户端与复杂子系统的交互 */ // ========== 客户端代码:使用门面 ========== public class FacadePatternDemo { // 【第1行】演示类 public static void main(String[] args) { // 【第2行】主方法,程序入口 // 【第4行】创建门面对象:客户端只与门面交互,不直接操作具体灯 LightFacade lightFacade = new LightFacade(); // 【第5行】调用门面的统一方法:一句代码开启所有灯 // 结论体现:客户端无需知道有几个灯、灯怎么开,只需调用门面方法 lightFacade.lightOn(); } } // ========== 门面类:统一接口 ========== /* * 灯的门面,提供统一的开关控制 * 门面角色:隐藏了子系统的复杂性 */ class LightFacade { // 【第12行】门面对象 // 【第14-16行】子系统:包含多个具体灯对象(类的集合) // 这些是门面管理的子系统,客户端不需要直接访问 private Light livingRoomLight = new LivingRoomLight(); // 客厅灯 private Light hallLight = new HallLight(); // 走廊灯 private Light diningLight = new DiningLight(); // 餐厅灯 // ========== 门面方法:封装子系统操作 ========== // 【第19行】统一开启所有灯:遍历调用每个子系统的on方法 public void lightOn() { livingRoomLight.on(); // 调用客厅灯on hallLight.on(); // 调用走廊灯on diningLight.on(); // 调用餐厅灯on } // 【第24行】统一关闭所有灯 public void lightOff() { livingRoomLight.off(); hallLight.off(); diningLight.off(); } } // ========== 子系统接口:定义规范 ========== interface Light { // 【第30行】灯的接口,定义子系统必须实现的方法 void on(); // 开灯方法 void off(); // 关灯方法 } // ========== 子系统实现:具体灯类 ========== // 【第35-47行】客厅灯实现:实现Light接口的具体行为 class LivingRoomLight implements Light { @Override public void on() { System.out.println("打开客厅灯"); // 具体开灯逻辑 } @Override public void off() { System.out.println("关闭客厅灯"); } } // 【第49-61行】走廊灯实现 class HallLight implements Light { @Override public void on() { System.out.println("打开走廊灯"); } @Override public void off() { System.out.println("关闭走廊灯"); } } // 【第63-75行】餐厅灯实现 class DiningLight implements Light { @Override public void on() { System.out.println("打开餐厅灯"); } @Override public void off() { System.out.println("关闭餐厅灯"); } }代码与结论的联系:
结论:"减少系统相互依赖,客户端只与门面对象交互"
代码体现:
FacadePatternDemo的main方法中,客户端只创建了LightFacade对象,调用了lightOn()方法,完全没有直接引用LivingRoomLight、HallLight等子系统类,实现了隔离
知识点:SLF4J就是日志框架的门面
示意图解读:
【你的应用代码】 --> 【SLF4J API(门面接口)】 --> 【SLF4J绑定(适配器)】 --> 【具体实现(Logback)】为什么需要这个门面?
// 假设你直接依赖Log4j import org.apache.log4j.Logger; // 硬编码依赖Log4j Logger logger = Logger.getLogger(MyClass.class); // 问题来了:如果项目要换成Logback,你需要修改所有类的import和代码 // 工作量巨大,风险极高 // 使用SLF4J门面: import org.slf4j.Logger; // 接口不变 import org.slf4j.LoggerFactory; Logger logger = LoggerFactory.getLogger(MyClass.class); // 好处:切换实现只需改依赖和配置,代码完全不用动 // 比如从Log4j换成Logback,只需在pom.xml中替换依赖,代码零改动2.3 日志格式说明
默认日志格式:
2023-05-15 10:30:45.123 INFO 12345 --- [nio-8080-exec-1] com.example.demo.LoggerController : --------------要输出日志的内容----------------逐段解析:
2023-05-15 10:30:45.123:时间戳,精确到毫秒,用于追踪事件发生顺序INFO:日志级别,告诉你这条信息的重要性12345:进程ID,多实例部署时区分是哪个进程打印的---:分隔符,视觉分隔,提高可读性[nio-8080-exec-1]:线程名,并发场景下定位哪个线程执行的com.example.demo.LoggerController:Logger名(通常用类名),精确定位代码位置::分隔符--------------要输出日志的内容----------------:日志消息,你自定义的内容
结论与代码的联系:
结论:"这种格式设计使得日志信息丰富且结构化"
代码体现:当你执行
logger.info("要输出日志的内容")时,Logback框架会自动在前面拼接时间、级别、线程等信息,无需你手动添加
2.4 日志级别:代码演示与配置效果
知识点:六级别详解
/* * 【完整代码示例3:日志级别演示】 * 这段代码演示如何打印不同级别的日志 * 对应结论:日志级别从低到高为 TRACE < DEBUG < INFO < WARN < ERROR * 对应结论:Spring Boot默认只显示INFO及以上级别 */ import org.slf4j.Logger; // 导入日志接口 import org.slf4j.LoggerFactory; // 导入工厂类 import org.springframework.web.bind.annotation.RequestMapping; // 导入请求映射 import org.springframework.web.bind.annotation.RestController; // 导入REST控制器 @RestController // 【第6行】标记为REST控制器 public class LoggerController { // 【第9行】获取与当前类绑定的logger对象 private static final Logger logger = LoggerFactory.getLogger(LoggerController.class); /** * 打印不同级别的日志 * @return 响应字符串 */ @RequestMapping("/printLog") // 【第15行】定义URL路径 public String printLog() { // 【第16行】处理方法 // 【第18行】TRACE级别:最详细,用于追踪代码执行路径(默认不显示) logger.trace("================= trace==============="); // 【第20行】DEBUG级别:调试信息,开发环境使用(默认不显示) logger.debug("================= debug==============="); // 【第22行】INFO级别:普通信息,记录业务流程(默认显示) logger.info("================= info==============="); // 【第24行】WARN级别:警告信息,可能有问题(默认显示) logger.warn("================= warn==============="); // 【第26行】ERROR级别:错误信息,需要处理(默认显示) logger.error("================= error==============="); return "打印不同级别的日志"; // 【第29行】返回响应 } }代码与结论的联系:
结论:"Spring Boot默认配置只输出INFO级别及以上的日志"
代码体现:执行
printLog()方法后,控制台只会看到info、warn、error三行,trace和debug不会显示。这是因为默认配置相当于logging.level.root=INFO,低于INFO级别的被过滤掉了
知识点:配置日志级别如何影响代码行为
配置文件:
# application.properties logging.level.root=debug # 设置根日志级别为DEBUG # 也可以针对特定包设置 logging.level.com.example.demo=trace # 这个包下所有类都输出TRACE级别配置效果示意图:
配置前(默认INFO): 代码中的5条日志 → [过滤器] → 只显示≥INFO的3条 配置后(root=DEBUG): 代码中的5条日志 → [过滤器] → 显示≥DEBUG的4条(TRACE仍被过滤) 配置后(com.example.demo=TRACE): 代码中的5条日志 → [过滤器] → 全部5条都显示代码与配置的联系:
配置结论:"logging.level可以针对不同包设置级别"
代码体现:如果你的项目结构是
com.example.demo.controller.LoggerController,那么logging.level.com.example.demo.controller=trace这个配置就会让LoggerController类中的logger.trace()生效
2.5 日志配置详解
知识点:日志持久化(存文件)
方式一:指定文件名
# application.yml logging: file: name: logger/springboot.log # 相对路径,会在项目根目录下创建logger文件夹方式二:指定目录
# application.yml logging: file: path: D:/temp # 只指定目录,文件名默认为spring.log代码与配置的联系:
结论:"如果同时配置name和path,只有name生效"
实际操作:
yaml logging: file: name: myapp.log # 这个生效 path: D:/logs # 这个被忽略
日志会保存在./myapp.log,而不是D:/logs/spring.log
知识点:日志分割配置
# application.yml logging: logback: rollingpolicy: max-file-size: 1KB # 单个文件超过1KB就分割 file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i # 分割后命名规则效果演示:
初始文件:springboot.log 当大小>1KB后: - springboot.log(新文件) - springboot.log.2023-05-15.0(旧文件) - springboot.log.2023-05-15.1(更旧的文件)时间格式占位符说明:
%d{yyyy-MM-dd}:按天分割%d{yyyy-MM-dd_HH}:按小时分割%i:序号,从0开始递增
3. Lombok简化日志:更优雅的写法
3.1 添加依赖
<!-- pom.xml中添加Lombok依赖 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> <!-- 可选依赖,不传递给其他项目 --> </dependency>3.2 使用@Slf4j注解
/* * 【完整代码示例4:Lombok简化日志】 * 这段代码演示如何使用Lombok的@Slf4j注解自动生成logger * 对应结论:Lombok减少样板代码,直接使用log变量 */ // ========== import区域 ========== import lombok.extern.slf4j.Slf4j; // 【第1行】导入Lombok的日志注解 import org.springframework.web.bind.annotation.RestController; // 【第2行】导入REST控制器 // ========== 使用@Slf4j注解 ========== @Slf4j // 【第4行】**核心注解**:在编译期自动生成以下代码: // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogController.class); @RestController // 【第5行】标记为REST控制器 public class LogController { // 【第6行】类定义 /** * 演示日志输出方法 */ public void log() { // 【第9行】普通方法 // 【第11行】直接使用自动生成的log对象,无需手动创建 // 效果与logger.info完全相同 log.info("--------------要输出日志的内容----------------"); // ========== 高级用法1:占位符避免字符串拼接 ========== String userName = "张三"; int userId = 123; // 【第16行】使用{}占位符,效果与2.1.2节相同 // Lombok生成的log对象支持所有SLF4J方法 log.debug("用户 {} (ID: {}) 访问了系统", userName, userId); // ========== 高级用法2:条件日志提升性能 ========== // 【第20行】判断是否开启DEBUG级别,避免执行耗时操作 // 这是一个性能优化技巧,对复杂日志尤其重要 if (log.isDebugEnabled()) { // 【第22行】只有在DEBUG开启时才执行复杂计算 // 如果DEBUG关闭,generateExpensiveLogData()方法根本不会被执行 String expensiveData = generateExpensiveLogData(); log.debug("详细信息: {}", expensiveData); } } /** * 模拟生成复杂日志数据的耗时操作 * @return 复杂数据字符串 */ private String generateExpensiveLogData() { // 【第32行】模拟耗时操作,睡眠100毫秒 try { Thread.sleep(100); // 模拟耗时100ms } catch (InterruptedException e) { // 【第34行】处理中断异常 // 【第35行】恢复线程中断状态,这是最佳实践 Thread.currentThread().interrupt(); } return "复杂日志数据"; // 【第37行】返回结果 } }代码与结论的联系:
结论:"Lombok减少样板代码,无需手动创建Logger"
代码体现:比较示例1和示例4,示例1需要写第9行的
LoggerFactory.getLogger(...),而示例4只需在类上加@Slf4j注解,直接使用log变量结论:"使用log.isDebugEnabled()避免不必要的计算"
代码体现:第20行判断,如果DEBUG级别未开启,第22行的
generateExpensiveLogData()方法(耗时100ms)根本不会执行,节省性能
4. 日志最佳实践与扩展知识
4.1 日志安全:避免记录敏感信息
/* * 【完整代码示例5:日志安全脱敏】 * 这段代码演示如何安全地记录日志 * 对应结论:避免记录密码等敏感信息,必要时脱敏处理 */ public class SecurityLogDemo { private static final Logger logger = LoggerFactory.getLogger(SecurityLogDemo.class); public void login(String username, String password) { // ========== 错误示范:直接记录密码 ========== // 【第9行】**极度危险**:日志文件可能被多人访问,密码明文存储违反安全规范 // 如果日志被黑客获取,直接得到用户密码 logger.info("用户 {} 使用密码 {} 登录", username, password); // ❌ 不要这样做 // ========== 正确示范:只记录业务行为 ========== // 【第13行】**安全做法**:只记录谁尝试登录,不记录密码 logger.info("用户 {} 尝试登录", username); // ✅ 推荐 // ========== 正确示范:调试时脱敏显示 ========== // 【第17行】脱敏处理:将密码替换为掩码 // 这样即使开了DEBUG级别,也看不到真实密码 String maskedPassword = password != null ? "***" : "null"; // 脱敏逻辑 logger.debug("用户 {} 使用密码 {} 登录", username, maskedPassword); // ✅ 安全 // 实际业务逻辑... boolean success = validatePassword(password); if (success) { logger.info("用户 {} 登录成功", username); } else { logger.warn("用户 {} 登录失败", username); // 记录失败,但不记录密码 } } private boolean validatePassword(String password) { // 模拟密码验证 return true; } }代码与结论的联系:
结论:"避免记录敏感信息,必要时脱敏"
代码体现:第9行错误示范直接记录
password,第17行正确示范使用"***"替代真实密码为什么这样做:日志通常会被收集到集中系统,可能被运维、开发人员查看,明文密码是重大安全隐患
4.2 高性能日志记录:性能优化技巧
技巧1:始终使用占位符
// ❌ 低效做法:先拼接字符串,再判断日志级别 // 即使DEBUG未开启,也会执行getName()和getAction()方法,并拼接字符串 logger.debug("当前用户: " + user.getName() + ", 操作: " + user.getAction()); // ✅ 高效做法:使用占位符 // 如果DEBUG未开启,getName()和getAction()都不会执行,直接跳过 logger.debug("当前用户: {}, 操作: {}", user::getName, user::getAction); // Java 8方法引用,更优技巧2:复杂日志使用条件判断
// 当需要构造复杂日志消息时 public void processLargeData(List<Data> dataList) { // 假设dataList有10000条数据 // ❌ 低效:即使DEBUG关闭,也会先格式化整个列表为字符串 logger.debug("处理数据: {}", dataList.toString()); // toString()耗时很长 // ✅ 高效:先判断,再执行耗时操作 if (logger.isDebugEnabled()) { // 【第7行】轻量级判断 // 只有DEBUG开启才执行toString() String dataSummary = dataList.subList(0, 10).toString() + "...共" + dataList.size() + "条"; logger.debug("处理数据: {}", dataSummary); // 【第10行】实际记录 } // 处理数据... }代码与结论的联系:
结论:"使用占位符避免不必要的字符串拼接"
代码体现:低效做法中
+操作符会立即执行拼接;高效做法中{}占位符在日志级别判断后才处理性能差异:在高并发场景下,这种优化可减少大量CPU和内存开销
4.3 集中式日志管理:微服务场景
问题场景:你有10个微服务,每个服务有3个实例,日志分散在30台机器上。用户投诉下单失败,你如何快速定位问题?
解决方案:ELK Stack架构
【各个服务】 → 【Filebeat(收集)】 → 【Logstash(处理)】 → 【Elasticsearch(存储)】 → 【Kibana(展示)】Spring Boot集成Filebeat:
# 在每个服务的application.yml中配置 logging: file: name: /opt/logs/${spring.application.name}.log # 统一日志路径 # Filebeat配置(filebeat.yml) filebeat.inputs: - type: log enabled: true paths: - /opt/logs/*.log # 收集所有服务日志 output.logstash: hosts: ["logstash:5044"]代码与结论的联系:
结论:"单机日志管理已不足够,需要集中式解决方案"
代码体现:通过配置
logging.file.name将日志落盘到统一路径,再由Filebeat收集。没有这些配置,日志只在控制台,无法集中管理
4.4 日志标准化:JSON结构化日志
/* * 【完整代码示例6:JSON结构化日志】 * 需要添加依赖:net.logstash.logback:logstash-logback-encoder */ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.logstash.logback.marker.Markers; // 导入JSON标记类 public class StructuredLogDemo { private static final Logger logger = LoggerFactory.getLogger(StructuredLogDemo.class); public void userLogin(Long userId, String action, String status) { // ========== 传统日志:非结构化 ========== // 缺点:只能通过正则表达式解析,效率低,容易出错 logger.info("用户 {} 执行 {} 操作,状态: {}", userId, action, status); // 输出:2023-05-15...用户 123 执行 login 操作,状态: success // ========== 结构化日志:JSON格式 ========== // 优点:可直接作为JSON解析,每个字段独立可查询 logger.info( Markers.append("userId", userId) // 【第17行】添加userId字段 .and(Markers.append("action", action)) // 【第18行】添加action字段 .and(Markers.append("status", status)), // 【第19行】添加status字段 "用户登录" // 【第20行】简洁的消息 ); // 输出示例: // { // "timestamp": "2023-05-15T10:30:45.123+08:00", // "level": "INFO", // "logger": "com.example.demo.StructuredLogDemo", // "message": "用户登录", // "userId": 123, // "action": "login", // "status": "success" // } } }代码与结论的联系:
结论:"结构化日志便于机器解析,可直接作为字段查询"
代码体现:第17-19行的
Markers.append()将额外数据作为独立字段添加到JSON中查询优势:在Kibana中可以直接用
userId:123查询,而传统日志需要用正则匹配用户 123
4.5 日志与追踪结合:分布式TraceID
/* * 【完整代码示例7:分布式追踪】 * 需要添加依赖:spring-cloud-starter-sleuth */ 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 OrderController { private static final Logger logger = LoggerFactory.getLogger(OrderController.class); @GetMapping("/createOrder") public String createOrder() { // Spring Cloud Sleuth自动注入TraceID和SpanID到MDC // MDC(Mapped Diagnostic Context)是SLF4J的线程本地变量映射 // 在日志配置中配置:logging.pattern.level=%5p [${spring.application.name},%X{traceId},%X{spanId}] // 输出示例:2023-05-15... INFO [order-service,5af7801cc2ab4ad4,5af7801cc2ab4ad4] ... 创建订单 logger.info("创建订单开始"); // 【第18行】这行日志会自动带上TraceID // 调用其他服务(RestTemplate或Feign) // 这些调用会自动传递TraceID,形成完整的调用链 logger.info("创建订单完成"); // 【第22行】同一请求的TraceID相同 return "订单创建成功"; } }跨服务调用示例:
用户请求 → [API Gateway] → [Order Service] → [Inventory Service] → [Payment Service] ↓ ↓ ↓ TraceID:5af78... TraceID:5af78... TraceID:5af78... SpanID:1 SpanID:2 SpanID:3 所有服务的日志都包含相同的TraceID: 5af7801cc2ab4ad4代码与结论的联系:
结论:"通过TraceID关联同一请求在不同服务的日志"
代码体现:无侵入式,只需添加依赖和配置,第18行和第22行的日志会自动包含相同的TraceID
排查流程:用户反馈订单失败 → 从网关日志获取TraceID → 在ELK中查询该TraceID → 看到所有相关服务的完整调用链日志
5. 总结:知识体系回顾
5.1 核心知识点速查表
| 知识点 | 代码体现 | 配置关联 | 最佳实践 |
获取Logger |
| 无需配置 | 使用Lombok的 |
日志级别 |
|
| 生产环境用INFO,开发用DEBUG |
占位符 |
| 无需配置 | 始终使用占位符,避免字符串拼接 |
日志文件 |
| application.yml | 线上必须持久化日志 |
日志分割 |
| application.yml | 按天和大小分割,防止磁盘满 |
性能优化 |
| 无需配置 | 复杂日志务必加判断 |
敏感信息 |
| 无需配置 | 密码、密钥等绝不打印明文 |
分布式追踪 | 自动注入 | Spring Cloud Sleuth | 微服务架构必须启用 |
5.2 新手学习路径建议
第一步:掌握基础(2.1节代码),能打印不同级别日志
第二步:理解配置(2.5节),能控制日志输出和文件存储
第三步:使用Lombok(3.2节),简化代码,提升效率
第四步:应用最佳实践(4.1-4.2节),写出安全、高性能的日志代码
第五步:了解分布式日志(4.3-4.5节),为微服务架构做准备
5.3 代码 vs 结论:如何建立联系?
方法论:
看到结论问"代码在哪?":例如结论"占位符避免字符串拼接",立即想到
logger.debug("msg {}", var)这行代码看到代码问"结论是什么?":例如看到
logging.file.name配置,思考结论"日志持久化"和这个配置的关系实践验证:修改配置
logging.level.root=TRACE,运行示例3,观察TRACE日志是否出现,直观感受配置对代码行为的影响
附录:完整项目结构示例
spring-boot-logging-demo/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/example/demo/ │ │ │ ├── LoggerController.java # 示例1、3代码 │ │ │ ├── LogController.java # 示例4代码 │ │ │ ├── SecurityLogDemo.java # 示例5代码 │ │ │ └── StructuredLogDemo.java # 示例6代码 │ │ └── resources/ │ │ ├── application.yml # 核心配置文件 │ │ └── logback-spring.xml # 高级日志配置(可选) │ └── test/ │ └── java/ └── pom.xml # Maven依赖管理application.yml完整配置示例:
# 应用基本信息 spring: application: name: logging-demo # 日志配置 logging: # 级别配置 level: root: INFO com.example.demo: DEBUG # 本项目的包设为DEBUG # 文件配置 file: name: logs/${spring.application.name}.log # 日志文件名 # Logback滚动策略 logback: rollingpolicy: max-file-size: 10MB file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz # 压缩旧日志 # 控制台格式(开发环境) pattern: console: '%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n' # 文件格式(生产环境) pattern: file: '%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n'新手建议:从最简单的LoggerController开始,逐步添加配置,观察日志输出变化,再学习Lombok简化,最后研究高级特性。每个知识点都动手写代码验证,理解会更深刻!