You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 20:05:25