Spring Boot 生产级日志配置指南
适用:Spring Boot 3.x(Java 17)、单体应用、日志落服务器文件。 当前依赖:
lombok+spring-boot-starter-web------ 无需任何额外日志依赖。
0.:需要哪些依赖?
只需要 lombok + spring-boot-starter-web,日志依赖一个都不用加。
| 你需要的能力 | 来源 |
|---|---|
slf4j-api(日志门面) |
spring-boot-starter-web → spring-boot-starter → spring-boot-starter-logging 传递带入 |
logback-classic(日志实现) |
同上,传递带入 |
@Slf4j 注解 |
lombok |
xml
<!-- 全部需要的依赖,就这俩 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
想亲眼验证:
mvn dependency:tree,会看到ch.qos.logback:logback-classic和org.slf4j:slf4j-api挂在spring-boot-starter-web → ...-starter → ...-starter-logging下面 ------ 虽然你 pom 里一个字都没写它们。
可选依赖(本指南不需要,见文末「附录」):微服务链路追踪、JSON 日志(ELK/Loki)。
1. 代码里怎么打日志
只用 SLF4J 门面 (@Slf4j 生成的就是它),不要直接 import ch.qos.logback.*。
java
@Slf4j // Lombok 生成 private static final Logger log
@Service
public class OrderService {
public void create(Long userId) {
log.info("创建订单 userId={}", userId); // ✅ {} 占位,别用 "+" 拼接
try {
// ... 业务
} catch (Exception e) {
log.error("创建订单失败 userId={}", userId, e); // ✅ 异常对象放最后,保留堆栈
}
}
}
两条铁律
- 用
{}占位:级别没开时不拼字符串,省性能;也避免拼接出错。 - 异常对象放最后一个参数(
log.error(msg, e)):别写log.error(e.getMessage()),那样会丢堆栈。
级别由高到低:error > warn > info > debug > trace,生产环境 root 一般设 INFO。
2. logback-spring.xml
放在 src/main/resources/logback-spring.xml。
文件名必须 是
logback-spring.xml(带-spring),否则用不了<springProfile>/<springProperty>。 Spring Boot 启动时自动加载它。
xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
生产级 Logback 配置(单体应用 / 落文件)。
① 文件名必须 logback-spring.xml,否则 <springProfile>/<springProperty> 不生效。
② 放 src/main/resources/ 下,Spring Boot 启动自动加载。
-->
<configuration>
<!-- ============ 1. 从 application.yml 读变量 ============ -->
<!-- source=yml 里的 key;defaultValue=yml 没配时的兜底。路径可按环境在 yml 改 -->
<springProperty name="LOG_PATH" source="logging.file.path" defaultValue="/opt/app/logs"/>
<springProperty name="APP_NAME" source="spring.application.name" defaultValue="app"/>
<!-- ============ 2. 日志格式 ============ -->
<!--
%d 时间 | %thread 线程 | %-5level 级别(左对齐占5位)
%logger{36} 类名(最长36字符) | %msg 内容 | %n 换行
(单体应用不需要 traceId,所以这里没有 %X{traceId};要链路追踪见文末附录)
-->
<property name="PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<!-- ============ 3. 控制台(开发看 + 容器收 stdout)============ -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${PATTERN}</pattern>
<charset>UTF-8</charset> <!-- 防中文乱码 -->
</encoder>
</appender>
<!-- ============ 4. 主日志文件(滚动 + 自动删除,核心)============ -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APP_NAME}.log</file> <!-- 当前正在写的文件 -->
<!-- SizeAndTimeBased:同时按"天"和"大小"滚动 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--
归档命名:%d 按天,%i 同一天内按大小切的序号,.gz 自动压缩。
%d{yyyy-MM-dd} 决定滚动周期=天 → maxHistory 单位也是"天"。
-->
<fileNamePattern>${LOG_PATH}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize> <!-- 单文件满 100MB 就切,%i 递增(只切分,不删) -->
<maxHistory>30</maxHistory> <!-- ★自动删除①:按时间,只留最近30天,更早的删 -->
<totalSizeCap>10GB</totalSizeCap> <!-- ★自动删除②:按总量,所有归档超10GB从最老删 -->
<cleanHistoryOnStart>true</cleanHistoryOnStart> <!-- 启动时也清一次过期文件 -->
</rollingPolicy>
<encoder>
<pattern>${PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- ============ 5. 单独的 ERROR 文件(排障只看错误)============ -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APP_NAME}-error.log</file>
<!-- LevelFilter:只放行 ERROR,其它级别全拒绝 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APP_NAME}-error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>60</maxHistory> <!-- 错误日志想留久点,留60天 -->
<totalSizeCap>2GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- ============ 6. 异步包装(降低写日志对业务线程的拖累)============ -->
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>1024</queueSize> <!-- 缓冲队列大小,日志先入队、后台线程落盘 -->
<!--
队列剩余低于此阈值时丢弃 TRACE/DEBUG/INFO(保留 WARN/ERROR)。
0 = 任何级别都不丢(极端高压会阻塞业务线程,但日志最全);
想高吞吐、能容忍丢低级别 → 设成 queueSize 的 20%(如 200)。
-->
<discardingThreshold>0</discardingThreshold>
<appender-ref ref="FILE"/> <!-- 异步包住上面的 FILE -->
</appender>
<!-- ============ 7. 按 profile 装配 ============ -->
<!-- 开发:只打控制台,IDEA 里直接看,不落文件 -->
<springProfile name="dev">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<!-- 生产:控制台(给平台采集)+ 异步主文件 + 单独 error 文件 -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
</springProfile>
</configuration>
3. application.yml(配套)
yaml
spring:
application:
name: demo # 进日志(${APP_NAME});将来要追踪也用它
profiles:
active: prod # 决定 logback 走 dev 段还是 prod 段(上线设 prod)
logging:
file:
path: /opt/app/logs # logback 里 ${LOG_PATH} 读它;给"目录",不给文件名
level: # ★级别放 yml 改起来方便,会覆盖 xml <root> 的默认
root: INFO # 全局默认级别
com.example.demo: INFO # 你自己的包,临时排障时改 DEBUG
org.springframework: WARN # 压低框架噪音
设计取舍 :结构(appender / 滚动 / 异步 / error 文件)放 xml(yml 表达不了这些); 级别放 yml(改得频繁,放 yml 不用动 xml,还能用 application-prod.yml 按环境覆盖)。
4. "自动删除"是怎么实现的
| 配置项 | 作用 | 是否删文件 |
|---|---|---|
maxFileSize |
单文件超过大小就切分成新文件 | 否(只切分) |
maxHistory |
按时间:只保留最近 N 天,更早的归档 | ✅ 自动删 |
totalSizeCap |
按总量:所有归档超过上限,从最老删 | ✅ 自动删 |
maxHistory+totalSizeCap一起兜底:既不留太久、也不占太多。 这两个是防止磁盘被日志撑爆的关键,生产最容易漏、漏了最致命。
5. 生产必守清单
- 滚动 + 保留 + 总量封顶三件套必配,否则磁盘迟早爆。
- 配置文件名用
logback-spring.xml,不要logback.xml(后者用不了 Spring 的 profile / 属性)。 - 历史日志
.gz压缩,省空间。 - 生产用绝对路径 (
/opt/app/logs),可预期、不随启动目录漂。 - 别打敏感信息(密码、token、手机号 / 身份证全量),该脱敏脱敏。
- 容器 / K8s 部署 :只留
CONSOLE(stdout),交平台(ELK / Loki)采集,别在容器里写文件 (重启就没了);传统 ECS / 物理机才落文件。 - 启动用
--spring.profiles.active=prod走生产那套(或 yml 里默认)。
附录:什么时候才加那些可选依赖(本指南都不需要)
A. 链路追踪 traceId ------ 仅微服务需要
单体应用不需要。当一个请求要跨多个服务 、想用同一个 traceId 把各服务日志串起来时才加:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
加完后,把 pattern 改回带 [%X{traceId:-}] 的版本,traceId 才会有值。
备用:单体里若只想给每个请求加个请求 ID,不必上 micrometer ------ 写个 Filter 往 MDC 塞个 UUID,pattern 里用
%X{你的key}引用即可,同样零额外依赖。
B. JSON 日志 ------ 仅接 ELK / Loki / Grafana 需要
直接在服务器看文本日志的话不需要。要把日志送进集中式平台、需要 JSON 格式时才加:
xml
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>8.0</version> <!-- 版本对齐你的 logback / Spring Boot 3.x -->
</dependency>
然后把对应 appender 的 <encoder> 换成 LogstashEncoder 即可。