日志配置实战
Logback 配置
核心组件
text
Logger → 日志记录器(按包/类级别控制)
Appender → 输出目的地(控制台、文件、数据库等)
Encoder → 日志格式编码(PatternLayout)
Layout → 日志格式模板spring-boot-starter
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 默认内置 spring-boot-starter-logging,含 logback -->logback-spring.xml 配置
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
<!-- 引入 Spring Boot 默认配置 -->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!-- 应用名称(用于文件名) -->
<springProperty name="APP_NAME" source="spring.application.name" defaultValue="app"/>
<!-- 日志路径 -->
<property name="LOG_HOME" value="${LOG_PATH:-./logs}"/>
<!-- ============================================ -->
<!-- Console Appender(开发环境使用) -->
<!-- ============================================ -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- 只输出 >= WARN 的日志到 System.err -->
<target>System.out</target>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- 彩色输出(需要 ANSI 支持) -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) [%thread] %cyan(%logger{36}) - %msg%n</pattern>
</encoder>
</appender>
<!-- ============================================ -->
<!-- RollingFile Appender(生产环境,按天+大小滚动)-->
<!-- ============================================ -->
<appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 当前日志文件 -->
<file>${LOG_HOME}/${APP_NAME}.log</file>
<!-- 滚动策略: 每天 + 每 500MB 滚动 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 最多保留 30 天 -->
<maxHistory>30</maxHistory>
<!-- 总日志文件大小上限 10GB -->
<totalSizeCap>10GB</totalSizeCap>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>500MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<!-- 统一使用 UTF-8 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- ============================================ -->
<!-- 异常日志(单独输出 ERROR 级别) -->
<!-- ============================================ -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/${APP_NAME}-error.log</file>
<!-- 只输出 ERROR 级别 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/${APP_NAME}-error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxHistory>90</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>200MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- ============================================ -->
<!-- 异步 Appender(包装同步 Appender) -->
<!-- ============================================ -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<!-- 丢弃 TRACE/DEBUG/INFO 级别的日志(当队列满 80% 时)-->
<discardingThreshold>0</discardingThreshold>
<!-- 队列深度 -->
<queueSize>1024</queueSize>
<!-- 只保留 1 个线程处理日志 -->
<neverBlock>true</neverBlock>
<!-- 被包装的同步 Appender -->
<appender-ref ref="ROLLING"/>
</appender>
<!-- ============================================ -->
<!-- Logger 配置 -->
<!-- ============================================ -->
<!-- 应用日志级别 -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC"/>
<appender-ref ref="ERROR_FILE"/>
</root>
<!-- 第三方库降级日志,减少干扰 -->
<logger name="org.springframework" level="WARN"/>
<logger name="org.mybatis" level="WARN"/>
<logger name="com.zaxxer.hikari" level="WARN"/>
<logger name="org.apache.kafka" level="WARN"/>
<!-- 指定包的 SQL 日志 -->
<logger name="com.example.mapper" level="TRACE"/>
<!-- Spring Profile 条件配置 -->
<springProfile name="dev">
<root level="DEBUG"/>
<logger name="org.springframework.web" level="DEBUG"/>
</springProfile>
<springProfile name="prod">
<root level="INFO"/>
</springProfile>
</configuration>Log4j 2 配置
log4j2-spring.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30">
<Properties>
<Property name="APP_NAME">${sys:spring.application.name:-app}</Property>
<Property name="LOG_HOME">${sys:LOG_PATH:-./logs}</Property>
</Properties>
<Appenders>
<!-- 控制台 -->
<Console name="CONSOLE" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%-5level} [%t] %c{36} - %msg%n"/>
</Console>
<!-- 滚动文件(按天+大小) -->
<RollingFile name="ROLLING" fileName="${LOG_HOME}/${APP_NAME}.log"
filePattern="${LOG_HOME}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %c{36} - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
<!-- 异步日志(基于 Disruptor) -->
<Async name="ASYNC">
<AppenderRef ref="ROLLING"/>
</Async>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="CONSOLE"/>
<AppenderRef ref="ASYNC"/>
</Root>
</Loggers>
</Configuration>异步日志原理
Logback AsyncAppender
text
业务线程 → Log Event → 阻塞队列 (有界) → 工作线程 → Appender
参数说明:
queueSize: 队列容量(默认 256)
neverBlock: 队列满时是否阻塞业务线程
discardingThreshold: 丢弃低级别日志的阈值注意:Logback 的 AsyncAppender 在队列满时默认丢弃 TRACE/DEBUG/INFO 级别日志,neverBlock=true 时队列满也不阻塞业务线程。
Log4j 2 异步日志
Log4j 2 使用 LMAX Disruptor——无锁环形缓冲区,性能远超 Logback 的 ArrayBlockingQueue:
text
业务线程 → RingBuffer (CPU 缓存友好) → 工作线程 → Appender
核心优势:
- 无锁设计: CAS 替代锁
- 预分配: 复用对象,减少 GC
- 缓存行填充: 避免伪共享启用方式(在 classpath 放置 log4j2.component.properties):
properties
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector日志分割策略
| 策略 | Logback | Log4j 2 | 说明 |
|---|---|---|---|
| 按时间滚动 | TimeBasedRollingPolicy | TimeBasedTriggeringPolicy | 支持 daily/hourly |
| 按大小滚动 | SizeAndTimeBasedFNATP | SizeBasedTriggeringPolicy | 通常与时间结合 |
| 按文件数 | maxHistory | DefaultRolloverStrategy.max | 保留 N 个文件 |
| 总大小限制 | totalSizeCap | 需自行估算 | 防止磁盘写满 |
推荐策略:按天滚动 + 单文件上限 500MB + 保留 30 天
MDC(Mapped Diagnostic Context)
MDC 是 SLF4J 提供的线程绑定上下文,用于在日志中注入请求维度的信息。
基础使用
java
// 放入 MDC
MDC.put("traceId", TraceContext.getTraceId());
MDC.put("userId", currentUser.getId());
log.info("处理订单"); // 会自动打印 traceId 和 userId
// 清除(务必在 finally 中)
MDC.clear();配合 Filter 自动注入
java
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MdcFilter implements Filter {
private static final String TRACE_ID = "traceId";
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
try {
// 1. 从请求头获取 traceId(上游传入)
String traceId = req.getHeader("X-Trace-Id");
if (traceId == null || traceId.isEmpty()) {
traceId = UUID.randomUUID().toString().replace("-", "");
}
MDC.put(TRACE_ID, traceId);
MDC.put("uri", req.getRequestURI());
MDC.put("method", req.getMethod());
// 2. 放入响应头(传递给下游)
HttpServletResponse resp = (HttpServletResponse) response;
resp.setHeader("X-Trace-Id", traceId);
chain.doFilter(request, response);
} finally {
// 3. 必须清除,防止线程池复用导致 MDC 污染
MDC.clear();
}
}
}Logback 配置打印 MDC
xml
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [traceId=%X{traceId}] %logger{36} - %msg%n</pattern>输出示例:
text
2026-07-22 10:15:30.123 [http-nio-8080-exec-1] INFO [traceId=a1b2c3d4] com.example.OrderService - 处理订单 1001日志级别动态调整
Spring Boot Actuator
properties
# 启用日志级别端点
management.endpoints.web.exposure.include=loggersbash
# 查看所有日志级别
GET /actuator/loggers
# 查看指定包的级别
GET /actuator/loggers/com.example
# 动态修改(无需重启)
POST /actuator/loggers/com.example
{"configuredLevel": "DEBUG"}生产环境建议
text
生产环境默认 INFO 级别
临时 DEBUG: 通过 Actuator 动态开启,问题排查后关闭
ERROR 日志单独文件,保留更长周期(90 天)
SQL 日志仅在 DEV 环境开启 TRACE