【 SLF4J + Logback】日志使用方法+技巧介绍+项目示例(SpringBoot)
一、介绍
本文主要通过SLF4J + Logback(Spring Boot 默认日志组合)来介绍日志框架的使用和技巧,通过实战项目演示工作和学习中日志的使用,良好的打印日志习惯可以快速的帮助我们排查系统问题,减少解决bug的时间和工作量。
二、 项目整体目录结构

三、实现步骤
1.创建springboot项目,编辑pom文件,引入所需依赖
由于Spring Boot 父工程已包含 spring-boot-starter-logging,因此无需额外添加日志依赖,只需引入spring web依赖和spring aop,用于演示接口依赖和使用切面工程统一处理。
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>
2.日志核心配置
在 src/main/resources 下创建 logback-spring.xml,配置日志级别、输出格式、文件滚动策略、异步日志等:
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds"><!-- 日志格式变量定义 --><property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/><property name="LOG_PATH" value="logs/springboot-log-demo"/> <!-- 日志文件存储路径 --><!-- 控制台输出 --><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>${LOG_PATTERN}</pattern></encoder></appender><!-- 业务日志文件输出(按天滚动、压缩、清理) --><appender name="BUSINESS_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_PATH}/business.log</file><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${LOG_PATH}/business.%d{yyyy-MM-dd}.log.gz</fileNamePattern><maxHistory>30</maxHistory> <!-- 保留30天日志 --></rollingPolicy><encoder><pattern>${LOG_PATTERN}</pattern></encoder><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>INFO</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><!-- 错误日志文件输出 --><appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_PATH}/error.log</file><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.log.gz</fileNamePattern><maxHistory>30</maxHistory></rollingPolicy><encoder><pattern>${LOG_PATTERN}</pattern></encoder><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><!-- 异步日志配置 --><appender name="ASYNC_BUSINESS" class="ch.qos.logback.classic.AsyncAppender"><discardingThreshold>0</discardingThreshold> <!-- 不丢弃日志 --><queueSize>512</queueSize> <!-- 队列大小 --><appender-ref ref="BUSINESS_FILE"/></appender><appender name="ASYNC_ERROR" class="ch.qos.logback.classic.AsyncAppender"><discardingThreshold>0</discardingThreshold><queueSize>512</queueSize><appender-ref ref="ERROR_FILE"/></appender><!-- 全局日志级别 --><root level="INFO"><appender-ref ref="CONSOLE"/><appender-ref ref="ASYNC_BUSINESS"/><appender-ref ref="ASYNC_ERROR"/></root><!-- 自定义包的日志级别(示例:降低 mapper 包日志级别) --><logger name="com.example.logdemo.mapper" level="DEBUG"/>
</configuration>
日志配置详细解析
1. 基础配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
scan="true":开启配置文件自动扫描(当配置文件修改时,无需重启应用即可生效)。scanPeriod="30 seconds":扫描间隔为 30 秒。
2. 全局变量定义
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<property name="LOG_PATH" value="logs/springboot-log-demo"/>
LOG_PATTERN:日志格式模板,各占位符含义:%d:日志时间(精确到毫秒)。[%thread]:线程名。%-5level:日志级别(左对齐,占 5 个字符)。%logger{36}:日志所在类的全限定名(最长 36 个字符,超出会缩写)。%msg%n:日志消息 + 换行。
LOG_PATH:日志文件存储根路径(相对路径,实际会生成在项目根目录下的logs/springboot-log-demo文件夹)。
3. 日志输出器(Appender)配置
Logback 通过 Appender 定义日志的输出目标(控制台、文件等),以下是配置的 5 种 Appender
3.1 控制台输出(CONSOLE)
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>${LOG_PATTERN}</pattern></encoder>
</appender>
- 作用:将日志输出到控制台。
ConsoleAppender:Logback 内置的控制台输出类。encoder:指定日志格式(复用LOG_PATTERN变量)。
3.2 业务日志文件输出(BUSINESS_FILE)
<appender name="BUSINESS_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_PATH}/business.log</file><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${LOG_PATH}/business.%d{yyyy-MM-dd}.log.gz</fileNamePattern><maxHistory>30</maxHistory></rollingPolicy><encoder><pattern>${LOG_PATTERN}</pattern></encoder><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>INFO</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter>
</appender>
- 作用:将 INFO 级别的日志输出到文件,并按天滚动、压缩和清理。
- 核心配置:
RollingFileAppender:支持日志滚动(切割)的文件输出类。<file>:当前日志文件路径(business.log)。TimeBasedRollingPolicy:按时间滚动策略:fileNamePattern:滚动后文件名格式(例如business.2025-11-05.log.gz),gz表示自动压缩。maxHistory:保留最近 30 天的日志文件,超过则自动删除。
LevelFilter:过滤日志级别,仅接受INFO级别,其他级别拒绝。
3.3 错误日志文件输出(ERROR_FILE)
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_PATH}/error.log</file><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.log.gz</fileNamePattern><maxHistory>30</maxHistory></rollingPolicy><encoder><pattern>${LOG_PATTERN}</pattern></encoder><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter>
</appender>
- 作用:将 ERROR 级别的日志输出到文件,配置与
BUSINESS_FILE类似,但:- 日志文件名为
error.log。 - 仅接受
ERROR级别日志。
- 日志文件名为
3.4 异步日志(ASYNC_BUSINESS 和 ASYNC_ERROR)
<appender name="ASYNC_BUSINESS" class="ch.qos.logback.classic.AsyncAppender"><discardingThreshold>0</discardingThreshold><queueSize>512</queueSize><appender-ref ref="BUSINESS_FILE"/>
</appender><appender name="ASYNC_ERROR" class="ch.qos.logback.classic.AsyncAppender"><discardingThreshold>0</discardingThreshold><queueSize>512</queueSize><appender-ref ref="ERROR_FILE"/>
</appender>
- 作用:通过异步方式输出日志,避免同步写文件阻塞主线程,提升性能。
- 核心配置:
AsyncAppender:异步日志包装类,依赖一个同步 Appender(如BUSINESS_FILE)。discardingThreshold="0":不丢弃任何日志(即使队列满了也会阻塞等待,确保日志不丢失)。queueSize="512":异步队列大小(最多缓存 512 条日志)。
4. 日志级别配置
4.1 全局日志级别(root)
<root level="INFO"><appender-ref ref="CONSOLE"/><appender-ref ref="ASYNC_BUSINESS"/><appender-ref ref="ASYNC_ERROR"/>
</root>
level="INFO":全局默认日志级别为INFO(即输出INFO及以上级别日志:INFO、WARN、ERROR)。- 关联的 Appender:日志会同时输出到控制台、异步业务日志、异步错误日志。
4.2 自定义包日志级别
<logger name="com.example.logdemo.mapper" level="DEBUG"/>
- 作用:为特定包(
com.example.logdemo.mapper)单独设置日志级别为DEBUG,覆盖全局的INFO级别。 - 场景:通常用于调试持久层(如 MyBatis 的 Mapper 接口)的 SQL 日志。
总结
该配置实现了以下功能:
日志同时输出到控制台和文件。
按级别分离日志:
INFO级日志到business.log,ERROR级日志到error.log。日志文件按天滚动、自动压缩,保留 30 天历史。
通过异步日志提升应用性能,且不丢弃任何日志。
支持配置文件热更新,无需重启应用。
3.代码实现
3.1 启动类(LogDemoApplication.java)
@SpringBootApplication
public class LogDemoApplication {private static final Logger logger = LoggerFactory.getLogger(LogDemoApplication.class);public static void main(String[] args) {logger.info("应用启动中...");SpringApplication.run(LogDemoApplication.class, args);logger.info("应用启动完成!");}}
3.2 控制器(LogController.java)
演示接口入参、出参、异常的日志记录
@RestController
public class LogController {private static final Logger logger = LoggerFactory.getLogger(LogController.class);@Resourceprivate LogService logService;@GetMapping("/demo")public String demo(@RequestParam String name) {// 记录接口入参logger.info("接收到请求,name = {}", name);try {String result = logService.process(name);// 记录接口出参logger.info("接口返回结果,result = {}", result);return result;} catch (Exception e) {// 记录异常(含堆栈)logger.error("接口处理异常", e);throw e;}}@GetMapping("/error")public void error() {logger.error("触发模拟异常");throw new RuntimeException("模拟运行时异常");}
}3.3 服务类(LogService.java)
@Service
public class LogService {private static final Logger logger = LoggerFactory.getLogger(LogService.class);public String process(String name) {// 记录业务步骤logger.info("开始处理业务,name = {}", name);// 循环场景:用 StringBuilder 优化日志输出(避免循环内频繁打日志)StringBuilder sb = new StringBuilder();for (int i = 0; i < 5; i++) {sb.append("步骤").append(i).append("完成,");// 批量记录(每5次记录一次,或循环结束后记录)if (i % 5 == 0 || i == 4) {logger.info("循环执行进度:{}", sb);sb.setLength(0); // 清空,复用 StringBuilder}}// 敏感信息过滤:日志中不直接记录密码等敏感信息String password = "123456"; // 模拟敏感信息logger.info("密码已加密存储,请勿泄露"); // 避免直接打印 passwordreturn "处理结果:" + name;}
}3.4 AOP 切面(LogAspect.java)
@Aspect
@Component
public class LogAspect {private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);// 定义切点:匹配 com.breeze.log_demo 包下所有类的所有方法@Pointcut("execution(* com.breeze.log_demo..*.*(..))")public void logPointcut() {}// 环绕通知:记录方法入参、出参、耗时@Around("logPointcut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {long startTime = System.currentTimeMillis();// 记录方法入参logger.info("方法 {} 入参:{}", joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()));try {Object result = joinPoint.proceed();// 记录方法出参和耗时long cost = System.currentTimeMillis() - startTime;logger.info("方法 {} 出参:{},耗时:{}ms",joinPoint.getSignature().getName(), result, cost);return result;} catch (Throwable e) {// 记录方法异常logger.error("方法 {} 执行异常", joinPoint.getSignature().getName(), e);throw e;}}
}3.5 测试类(LogDemoApplicationTests.java)
演示测试环境日志验证
@SpringBootTest
class LogDemoApplicationTests {private static final Logger logger = LoggerFactory.getLogger(LogDemoApplicationTests.class);@Testvoid contextLoads() {logger.debug("Debug 级别日志(测试环境可见)");logger.info("Info 级别日志(测试环境可见)");logger.error("Error 级别日志(测试环境可见)");}
}四、运行及效果展示
1. 启动项目:运行 LogDemoApplication,观察控制台和 logs 目录生成的日志文件。


日志文件里内容准确:

2. 调用接口:
- 访问
http://localhost:8080/demo?name=test,查看接口日志的入参、出参、业务步骤记录。

error.log 日志文件 成功记录到错误日志:
五、异常的相关知识总结
1. 异常体系结构
Throwable(顶层类)
├─ Error(错误:JVM层面的严重问题,程序无法处理)
│ ├─ VirtualMachineError(虚拟机错误)
│ │ ├─ OutOfMemoryError(内存溢出)
│ │ ├─ StackOverflowError(栈溢出)
│ │ └─ InternalError(内部错误)
│ ├─ AWTError(AWT组件错误)
│ └─ LinkageError(类链接错误)
│ ├─ NoClassDefFoundError(类定义未找到)
│ └─ UnsatisfiedLinkError(本地方法链接失败)
│
└─ Exception(异常:程序可处理的问题)├─ RuntimeException(运行时异常:非检查型,编译不强制处理)│ ├─ NullPointerException(空指针)│ ├─ IndexOutOfBoundsException(索引越界)│ ├─ ClassCastException(类型转换)│ ├─ IllegalArgumentException(非法参数)│ ├─ ArithmeticException(算术错误)│ └─ ConcurrentModificationException(并发修改)│└─ 受检异常(Checked Exception:编译强制处理)├─ IOException(IO操作异常)│ ├─ FileNotFoundException(文件未找到)│ └─ IOException(通用IO错误)├─ SQLException(数据库操作异常)├─ ClassNotFoundException(类未找到)└─ InterruptedException(线程中断)2. Error(错误):不可处理的严重问题
| 错误类型 | 含义 | 常见场景 | 解决方式 |
|---|---|---|---|
OutOfMemoryError | 内存溢出(JVM 堆 / 方法区不足) | 创建大量对象未释放、大文件加载 | 1. 调大 JVM 内存参数(-Xms/-Xmx)2. 优化对象生命周期,避免内存泄漏 |
StackOverflowError | 栈溢出(方法调用栈深度超过限制) | 递归调用无终止条件、多层嵌套方法 | 1. 修复递归终止条件2. 减少方法嵌套层级 |
NoClassDefFoundError | 运行时找不到类定义(编译存在,运行缺失) | 类路径(classpath)配置错误、jar 包缺失 | 1. 检查类路径是否包含目标类2. 确保依赖 jar 包存在 |
UnsatisfiedLinkError | 本地方法(JNI)链接失败 | 调用的本地库(.dll/.so)不存在或版本不匹配 | 1. 检查本地库路径是否正确2. 确保库文件与系统兼容 |
3. Exception(异常):可处理的问题
3.1 运行时异常(RuntimeException):非检查型异常
特点:编译时不强制处理,通常由程序逻辑错误导致,需通过代码优化避免。
| 异常类型 | 含义 | 常见场景 | 解决方式 |
|---|---|---|---|
NullPointerException | 调用 null 对象的方法 / 属性 | String s = null; s.length(); | 1. 调用前判空(if (obj != null))2. 使用Optional类避免空指针 |
IndexOutOfBoundsException | 数组 / 集合索引越界 | List<String> list = new ArrayList<>(); list.get(0); | 1. 访问前检查索引范围(index < size)2. 使用循环时避免硬编码索引 |
ClassCastException | 类型强制转换失败 | Object obj = "str"; Integer i = (Integer) obj; | 1. 转换前用instanceof判断类型2. 避免无意义的强制转换 |
IllegalArgumentException | 方法接收非法参数 | 传入负数到要求正数的方法(如new ArrayList(-1)) | 1. 方法内校验参数合法性2. 调用前确保参数符合要求 |
ArithmeticException | 算术错误(如除以 0) | int a = 1 / 0; | 1. 避免除数为 02. 计算前校验除数合法性 |
ConcurrentModificationException | 迭代集合时并发修改(如边遍历边删除) | for (String s : list) { list.remove(s); } | 1. 使用迭代器的remove()方法2. 使用CopyOnWriteArrayList等线程安全集合 |
3.2 受检异常(Checked Exception):编译时强制处理
特点:编译器要求必须捕获或声明抛出,通常由外部环境导致(如 IO、网络问题)。
| 异常类型 | 含义 | 常见场景 | 解决方式 |
|---|---|---|---|
IOException | IO 操作失败(读 / 写文件、网络流) | 读取不存在的文件、网络中断时写数据 | 1. 捕获异常并处理(如提示 "文件不存在")2. 确保资源路径正确,网络通畅 |
FileNotFoundException | 找不到指定文件 / 目录(IOException子类) | new FileInputStream("不存在的文件.txt") | 1. 检查文件路径是否正确2. 操作前判断文件是否存在(file.exists()) |
SQLException | 数据库操作失败 | 连接参数错误、SQL 语法错误、表不存在 | 1. 检查数据库连接配置和 SQL 语句2. 打印异常信息定位具体错误(如e.getMessage()) |
ClassNotFoundException | 加载类时找不到类定义(与NoClassDefFoundError的区别:前者是主动加载失败,后者是被动引用失败) | Class.forName("com.mysql.Driver") | 1. 检查类全限定名是否正确2. 确保依赖的 jar 包已引入 |
InterruptedException | 线程在等待 / 休眠时被中断 | Thread.sleep(1000)时调用thread.interrupt() | 1. 捕获后可恢复中断状态(Thread.currentThread().interrupt())2. 根据业务决定是否终止线程 |
