Springboot 集成 TraceID
要在 Spring Boot 2.5.15 中实现日志打印包含 traceID 的功能,最简便的方式是使用 Spring Cloud Sleuth。它能自动生成和传播 traceID,并将其嵌入到日志中,无需手动处理。以下是具体实现步骤:
一、核心原理
Spring Cloud Sleuth 会通过 MDC(Mapped Diagnostic Context)机制,将生成的 traceID 和 spanID 存储到线程上下文中。我们只需在日志格式中配置从 MDC 中获取这些信息,即可让每条日志都自动带上 traceID。
二、具体实现步骤
1. 添加依赖(pom.xml)
在 pom.xml
中引入 Sleuth 依赖(无需 Zipkin,仅需日志中的 traceID 时):
xml
<!-- 引入 Spring Cloud BOM 管理版本 -->
<dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2020.0.6</version> <!-- 与 Spring Boot 2.5.15 匹配 --><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement><!-- Spring Cloud Sleuth:生成并传递 traceID -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency><!-- 确保有日志依赖(Spring Boot 默认已包含) -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId>
</dependency>
2. 配置日志格式(application.yml)
在配置文件中修改日志格式,添加从 MDC 中获取 traceId
和 spanId
的占位符:
yaml
spring:application:name: your-service-name # 服务名称(用于链路标识)sleuth:sampler:probability: 1.0 # 采样率 100%(开发环境建议,生产可按需调整)baggage:correlation-fields: traceId,spanId # 关联日志字段# 日志配置:关键是添加 [%X{traceId:-},%X{spanId:-}] 占位符
logging:pattern:# 控制台日志格式console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{traceId:-},%X{spanId:-}] %logger{50} - %msg%n"# 文件日志格式(如果需要输出到文件)file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{traceId:-},%X{spanId:-}] %logger{50} - %msg%n"# 日志文件路径(可选)file:name: logs/app.log
配置说明:
[%X{traceId:-},%X{spanId:-}]
:%X{key}
是从 MDC 中获取值的日志占位符,:-
表示如果获取不到则显示空(避免报错)。spring.sleuth.sampler.probability
:采样率设为 1.0 表示所有请求都生成 traceID,适合开发和测试环境。
3. 验证代码(无需额外编码)
Sleuth 会自动拦截所有 HTTP 请求、异步任务等,并注入 traceID。只需正常编写业务代码即可:
java
运行
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 DemoController {private static final Logger log = LoggerFactory.getLogger(DemoController.class);@GetMapping("/test")public String testTraceId() {log.info("这是一条包含 traceID 的日志");log.warn("这是一条警告日志,同样包含 traceID");return "查看控制台日志,检查是否包含 traceID";}
}
三、验证效果
- 启动 Spring Boot 应用;
- 访问接口
http://localhost:8080/test
; - 查看控制台日志,会看到类似如下输出:
plaintext
2024-08-25 10:15:30.123 [http-nio-8080-exec-1] INFO [abc1234567890,def7890123456] com.example.DemoController - 这是一条包含 traceID 的日志
2024-08-25 10:15:30.125 [http-nio-8080-exec-1] WARN [abc1234567890,def7890123456] com.example.DemoController - 这是一条警告日志,同样包含 traceID
其中 abc1234567890
就是本次请求的全局唯一 traceID,所有相关日志都会携带相同的 traceID。
四、特殊场景处理
1. 异步方法日志包含 traceID
默认情况下,Sleuth 会处理 @Async
注解的异步方法,但需确保异步配置正确:
java
运行
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(10);executor.initialize();return executor;}
}
使用异步方法时,日志会自动携带 traceID:
java
运行
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;@Service
public class DemoService {private static final Logger log = LoggerFactory.getLogger(DemoService.class);@Asyncpublic void asyncMethod() {log.info("异步方法中的日志,应包含 traceID");}
}
2. 手动获取 traceID
如果需要在代码中手动获取 traceID(如返回给前端),可注入 Tracer
工具类:
java
运行
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class TraceIdController {private final Tracer tracer;// 构造注入 Tracerpublic TraceIdController(Tracer tracer) {this.tracer = tracer;}@GetMapping("/get-trace-id")public String getTraceId() {// 获取当前 traceIDString traceId = tracer.currentSpan().context().traceIdString();return "当前请求的 traceID 是:" + traceId;}
}
场景二:
原始项目有logback配置日志样式,当前logging 配置发现没有生效,没有生成 logs/app.log 并且 新打印日志并没有注入traceID和spenID 问题解决如下:
当项目中存在 logback.xml
配置时,Spring Boot 的 application.yml
中 logging
相关配置可能会被覆盖,导致 TraceID 无法注入日志。这是因为 Logback 配置文件的优先级高于 Spring Boot 全局配置。以下是具体解决方案:
一、核心原因分析
- 配置优先级冲突:
logback.xml
是 Logback 的原生配置文件,优先级高于 Spring Boot 的application.yml
中的logging
配置,导致application.yml
中定义的日志格式(含 TraceID)和文件路径不生效。 - TraceID 注入依赖 Logback 配置:Sleuth 通过 MDC 注入的
traceId
和spanId
,需要在 Logback 的日志格式中显式配置才能输出。
二、解决方案:修改 logback.xml 配置
需直接在 logback.xml
中添加 TraceID/SpanID 占位符,并配置日志文件输出路径,确保与 Sleuth 集成。
1. 找到项目中的 logback.xml
通常位于 src/main/resources/logback.xml
,日志中提示存在多个 logback.xml
(如 newauditor-admin
和 newauditor-drools
模块),需确保修改的是当前启动模块使用的 logback.xml
。
2. 修改 logback.xml 配置
在日志格式中添加 [%X{traceId:-},%X{spanId:-}]
占位符,并配置文件输出路径。示例完整配置:
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false"><!-- 定义日志输出格式(核心:添加 traceId 和 spanId) --><property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{traceId:-},%X{spanId:-}] %logger{50} - %msg%n" /><!-- 定义日志文件存储路径 --><property name="LOG_PATH" value="logs" /><property name="LOG_FILE_NAME" value="app" /><!-- 控制台输出 --><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!-- 使用含 TraceID 的格式 --><pattern>${LOG_PATTERN}</pattern></encoder></appender><!-- info 级别日志文件输出 --><appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender"><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>INFO</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!-- 日志文件路径:logs/app.log --><fileNamePattern>${LOG_PATH}/${LOG_FILE_NAME}.%d{yyyy-MM-dd}.log</fileNamePattern><!-- 日志保留天数 --><maxHistory>30</maxHistory></rollingPolicy><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>${LOG_PATTERN}</pattern></encoder></appender><!-- warn 级别日志文件输出 --><appender name="file_warn" class="ch.qos.logback.core.rolling.RollingFileAppender"><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>WARN</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${LOG_PATH}/${LOG_FILE_NAME}-warn.%d{yyyy-MM-dd}.log</fileNamePattern><maxHistory>30</maxHistory></rollingPolicy><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>${LOG_PATTERN}</pattern></encoder></appender><!-- error 级别日志文件输出 --><appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender"><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.TimeBasedRollingPolicy"><fileNamePattern>${LOG_PATH}/${LOG_FILE_NAME}-error.%d{yyyy-MM-dd}.log</fileNamePattern><maxHistory>30</maxHistory></rollingPolicy><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>${LOG_PATTERN}</pattern></encoder></appender><!-- 日志输出级别 --><root level="INFO"><appender-ref ref="console" /><appender-ref ref="file_info" /><appender-ref ref="file_warn" /><appender-ref ref="file_error" /></root><!-- 自定义包日志级别(可选) --><logger name="com.newtouch.ai.newauditor" level="INFO" additivity="false"><appender-ref ref="console" /><appender-ref ref="file_info" /></logger>
</configuration>
3. 关键配置说明
- 日志格式:
[%X{traceId:-},%X{spanId:-}]
是从 MDC 中获取 Sleuth 注入的 TraceID 和 SpanID,:-
表示如果获取不到则显示空(避免日志报错)。 - 日志路径:通过
LOG_PATH
和LOG_FILE_NAME
定义日志文件路径为logs/app.log
,与application.yml
中期望的路径一致。 - 输出级别:确保
root
或自定义包的日志级别至少为INFO
(避免日志被过滤)。
三、验证配置是否生效
- 删除冲突配置:若
application.yml
中仍有logging
配置,建议注释或删除(避免与logback.xml
冲突)。 - 重启项目:启动后观察:
- 控制台日志是否包含
[traceId,spanId]
格式(如[a1b2c3d4e5f67890,a1b2c3d4e5f67890]
); - 项目根目录下是否生成
logs
文件夹及app.log
文件; - 访问接口后,检查日志文件中是否有带 TraceID 的日志。
- 控制台日志是否包含
四、特殊场景处理
1. 多模块项目 logback.xml 冲突
日志中提示 logback.xml
存在多个(如 newauditor-admin
和 newauditor-drools
):
- 确保启动模块(如
newauditor-admin
)的logback.xml
是最终生效的配置; - 其他模块的
logback.xml
可删除或统一继承主模块配置(通过<include>
标签引入)。
2. 异步线程日志不显示 TraceID
若项目中使用异步线程(如 @Async
),需配置 Sleuth 对异步线程的支持:
java
运行
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;@Configuration
@EnableAsync
public class AsyncConfig {@Beanpublic Executor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(10);// 关键:设置线程名称前缀,便于日志追踪executor.setThreadNamePrefix("Async-");// 初始化线程池executor.initialize();return executor;}
}
场景三:显著追加traceID,和 spanID字段
<property name="log.pattern" value="%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level [traceID:%X{traceId:-},spanID:%X{spanId:-}] %logger{20} - [%method,%line] - %msg%n" />
总结
核心是通过修改 logback.xml
配置,在日志格式中添加 TraceID/SpanID 占位符,并正确配置日志文件路径。由于 logback.xml
优先级高于 application.yml
,必须在此文件中完成所有日志相关配置,才能确保 Sleuth 生成的 TraceID 正常输出到日志中。
五、总结
通过以上配置,Spring Boot 应用的所有日志(包括控制器、服务、工具类等)都会自动包含 traceID,实现了:
- 单条请求的所有日志通过同一个 traceID 关联;
- 跨服务调用时 traceID 自动传递(如果有多个服务);
- 无需手动埋点,Sleuth 自动处理。
这种方式既能满足日志查询和问题定位的需求,又能最小化代码侵入性。bo t