Spring Boot 3中TraceId无法通过RestTemplate跨服务传播问题
TraceId未在RestTemplate调用中传播的问题排查
环境信息
- Spring Boot 3.3.3
- Java 17
问题现象
通过RestTemplateBuilder构建RestTemplate,已引入actuator和micrometer-tracing-bridge-otel依赖,但测试发现/forward与/hello端点日志中的TraceId不一致,TraceId未成功在服务调用间传播。
相关配置与代码
1. pom.xml依赖配置
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.test</groupId> <artifactId>tracepropagation</artifactId> <version>0.0.1-SNAPSHOT</version> <name>tracepropagation</name> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing-bridge-otel</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
2. RestTemplate配置类
@Configuration @EnableConfigurationProperties(AppProperties.class) public class AppConfig { @Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder.build(); } }
3. Controller代码
@RestController @AllArgsConstructor @Slf4j public class AppController { private final RestTemplate restTemplate; private final AppProperties props; @GetMapping(path = "/forward") public String forward() { log.info("forward endpoint entered"); return this.restTemplate.getForObject("http://localhost:" + props.getPort() + "/hello", String.class); } @GetMapping(path = "/hello") public String hello(){ log.info("hello endpoint entered"); return "hello"; } }
4. 验证TraceId传播的测试用例
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class TracepropagationApplicationTests { @LocalServerPort private int port; @Autowired private AppProperties props; @Autowired private TestRestTemplate restTemplate; @Captor ArgumentCaptor<ILoggingEvent> logCaptor; @Mock private Appender<ILoggingEvent> mockAppender; @BeforeEach void beforeEach(){ final Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); when(this.mockAppender.getName()).thenReturn("MOCK"); root.addAppender(this.mockAppender); root.setLevel(Level.INFO); doNothing().when(mockAppender).doAppend(logCaptor.capture()); } @Test void testTraceIdPropagation() { props.setPort(port); assertThat(restTemplate.getForObject("http://localhost:" + port + "/forward", String.class)).isEqualTo("hello"); String firstLogTraceId = this.getLogTraceId("forward endpoint entered"); String secondLogTraceId = this.getLogTraceId("hello endpoint entered"); assertThat(firstLogTraceId).isEqualTo(secondLogTraceId); } private String getLogTraceId(String logMessage) { var traceOptional = logCaptor.getAllValues().stream().filter(e -> e.getFormattedMessage().contains(logMessage)) .findAny() .map(ILoggingEvent::getMDCPropertyMap) .map(m -> m.get("traceId")); assertThat(traceOptional).isPresent(); return traceOptional.get(); } }
5. AppProperties类
@ConfigurationProperties(prefix = "com.test") @Data public class AppProperties { private int port; }
排查解决方案
1. 补全日志格式配置
Spring Boot 3.x中,MDC中的traceId需要通过日志格式显式输出才能被捕获。在application.properties或application.yml中添加日志模式配置:
logging.pattern.level=%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]
未配置时,日志不会携带traceId,测试用例从MDC获取的内容会无效。
2. 显式启用测试环境的观测能力
在测试类上添加@AutoConfigureObservability注解,确保追踪功能在测试上下文里完全初始化:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureObservability class TracepropagationApplicationTests { // 原有测试代码 }
3. 手动绑定追踪拦截器
虽然自动配置应该为RestTemplate添加TraceRestTemplateInterceptor,但可以显式注入拦截器验证:
@Bean public RestTemplate restTemplate(RestTemplateBuilder builder, TraceRestTemplateInterceptor traceInterceptor) { return builder.addInterceptors(traceInterceptor).build(); }
4. 验证调用链路的上下文
在forward和hello方法中打印当前MDC的traceId,确认上下文传递情况:
@GetMapping(path = "/forward") public String forward() { log.info("forward endpoint entered, traceId: {}", MDC.get("traceId")); String url = "http://localhost:" + props.getPort() + "/hello"; log.info("Calling URL: {}", url); return this.restTemplate.getForObject(url, String.class); } @GetMapping(path = "/hello") public String hello(){ log.info("hello endpoint entered, traceId: {}", MDC.get("traceId")); return "hello"; }
5. 确认端口配置正确性
在forward方法中打印调用的URL,确保RestTemplate调用的端口与当前服务的随机端口一致,避免请求未进入目标端点。
内容的提问来源于stack exchange,提问作者joachimp
相关产品推荐
相关产品推荐

