[英]Defining a RestTemplateBuilder bean causes traceId propagation across service boundaries to no longer work
The following code demonstrates that by including a RestTemplateBuilder
bean into a project, micrometer-tracing
will not propagate traceId
s correctly across service boundaries.以下代码演示了通过将
RestTemplateBuilder
bean 包含到项目中, micrometer-tracing
不会跨服务边界正确传播traceId
。
I am posting the sample code here as my client prohibits me from sharing code via GitHub.我在这里发布示例代码,因为我的客户禁止我通过 GitHub 共享代码。
Create two Spring Maven projects, demo-client and demo-server, referencing the code below, then run the two scripts to get the microservices running.创建两个 Spring Maven 项目,demo-client 和 demo-server,参考下面的代码,然后运行这两个脚本让微服务运行起来。 Then, send a GET request to
http://localhost:2048/tracer-response
and you'll see in the demo-client logs that the traceId
s match.然后,向
http://localhost:2048/tracer-response
发送 GET 请求,您将在演示客户端日志中看到traceId
匹配。 Next, uncomment the RestTemplateBuilder
bean in DemoClientApplication
and re-run the scripts.接下来,取消注释
DemoClientApplication
中的RestTemplateBuilder
bean 并重新运行脚本。 Retest, and you'll see that the traceId
s no longer match.重新测试,您会发现
traceId
不再匹配。
Please advise as to why this configuration no longer works.请告知为什么此配置不再有效。 It previously worked with Spring Boot 2.7.7 and Spring Cloud 2021.0.5.
它之前使用 Spring Boot 2.7.7 和 Spring Cloud 2021.0.5。 If the intent from the Spring team is for this configuration to no longer be used and this is pointed out in the documentation, then I apologize for missing it.
如果 Spring 团队的意图是不再使用此配置并且在文档中指出了这一点,那么我为错过它而道歉。 If it isn't pointed out, then in my opinion this should be highlighted as a warning, as it clearly negates the whole purpose behind using distributed tracing.
如果没有指出,那么在我看来,这应该被强调为警告,因为它清楚地否定了使用分布式跟踪背后的全部目的。
demo-client code演示客户端代码
package com.example.democlient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@Slf4j
public class DemoClientApplication {
public static void main(String[] args) {
SpringApplication.run(DemoClientApplication.class, args);
}
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
// Uncomment the bean definition below, and traceId propagation will no longer work, meaning the traceId
// values logged by demo-client and demo-server will not be the same value.
/* @Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder();
}*/
}
package com.example.democlient;
import io.micrometer.tracing.Tracer;
import jakarta.annotation.security.PermitAll;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@Slf4j
public class DemoClientController {
@Autowired RestTemplate restTemplate;
@Autowired Tracer tracer;
@PermitAll
@GetMapping(value = "/tracer-response", produces = MediaType.APPLICATION_JSON_VALUE)
public TracerResponse getClientResponse() {
log.info("Getting the tracer response from demo server...");
var clientTraceId = tracer.currentSpan().context().traceId();
var clientSpanId = tracer.currentSpan().context().spanId();
log.info(
"Here's the traceId and spanId before the call: {} and {}", clientTraceId, clientSpanId);
var serverTracerResponse =
restTemplate
.getForEntity("http://localhost:2049/tracer-response", TracerResponse.class)
.getBody();
var serverTraceId = serverTracerResponse.getTraceId();
var serverSpanId = serverTracerResponse.getSpanId();
log.info(
"Here's the traceId and spanId from the server: {} and {}", serverTraceId, serverSpanId);
log.info("Do the client and server traceIds match? {}", clientTraceId.equals(serverTraceId));
return serverTracerResponse;
}
}
package com.example.democlient;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
@JsonInclude(JsonInclude.Include.NON_NULL)
@Data
public class TracerResponse {
private String spanId;
private String traceId;
}
src/main/resources/application.yml src/main/resources/application.yml
server:
port: 2048
spring:
application:
name: demo-client
src/main/resources/logback-spring.xml src/main/resources/logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include
resource="org/springframework/boot/logging/logback/defaults.xml"/>
<springProperty scope="context" name="springAppName"
source="spring.application.name"/>
<property name="LOG_PATTERN"
value="%d{yyyy-MM-dd, America/New_York} | %d{HH:mm:ss.SSS, America/New_York} | %-16.16thread | %-5.5p | %-40.40logger{40} | %-39.39(Trace: %X{traceId}) | %-22.22(Span: %X{spanId}) | %m%n"/>
<appender name="console"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<logger name="com.example" level="DEBUG" additivity="false">
<appender-ref ref="console"/>
</logger>
<logger name="org.springframework" level="WARN"
additivity="false">
<appender-ref ref="console"/>
</logger>
<root level="WARN">
<appender-ref ref="console"/>
</root>
</configuration>
pom.xml pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
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.0.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-client</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
demo-server code演示服务器代码
package com.example.demoserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoServerApplication {
public static void main(String[] args) {
SpringApplication.run(DemoServerApplication.class, args);
}
}
package com.example.demoserver;
import io.micrometer.tracing.Tracer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class DemoServerController {
@Autowired
Tracer tracer;
@GetMapping(value = "/tracer-response", produces = MediaType.APPLICATION_JSON_VALUE)
public TracerResponse getClientResponse() {
log.info("Getting the serverResponse...");
log.info("traceId: {} spanId: {}",tracer.currentSpan().context().traceId(),tracer.currentSpan().context().spanId());
var response = new TracerResponse();
response.setTraceId(tracer.currentSpan().context().traceId());
response.setSpanId(tracer.currentSpan().context().spanId());
return response;
}
}
package com.example.demoserver;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
@JsonInclude(JsonInclude.Include.NON_NULL)
@Data
public class TracerResponse {
private String traceId;
private String spanId;
}
src/main/resources/application.yml src/main/resources/application.yml
server:
port: 2049
spring:
application:
name: demo-server
src/main/resources/logback-spring.xml src/main/resources/logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include
resource="org/springframework/boot/logging/logback/defaults.xml"/>
<springProperty scope="context" name="springAppName"
source="spring.application.name"/>
<property name="LOG_PATTERN"
value="%d{yyyy-MM-dd, America/New_York} | %d{HH:mm:ss.SSS, America/New_York} | %-16.16thread | %-5.5p | %-40.40logger{40} | %-39.39(Trace: %X{traceId}) | %-22.22(Span: %X{spanId}) | %m%n"/>
<appender name="console"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<logger name="com.example" level="DEBUG" additivity="false">
<appender-ref ref="console"/>
</logger>
<logger name="org.springframework" level="WARN"
additivity="false">
<appender-ref ref="console"/>
</logger>
<root level="WARN">
<appender-ref ref="console"/>
</root>
</configuration>
pom.xml pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
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.0.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
scripts to manage and run the microservices管理和运行微服务的脚本
kill_demo_server.sh kill_demo_server.sh
#! /bin/bash
set -e
set -x
ps -ef | grep 'demo-server-0.0.1' | kill -9 $(awk 'NR==1{print $2}') 2>/dev/null;
build_deploy_demos.sh build_deploy_demos.sh
#! /bin/bash
set -e
set -x
cd ~/repos/demo-server;
mvn clean install;
nohup java -jar target/demo-server-0.0.1-SNAPSHOT.jar &
cd ~/repos/demo-client;
mvn clean install;
java -jar target/demo-client-0.0.1-SNAPSHOT.jar
Not clear by your question why do you need a custom RestTemplateBuilder
bean, but I believe there are some use-case anyway.你的问题不清楚为什么你需要一个自定义的
RestTemplateBuilder
bean,但我相信无论如何都有一些用例。
So, let's see if making that bean fully similar to what Spring Boot auto-configuration does helps to solve your requirements!因此,让我们看看是否使该 bean 完全类似于 Spring 引导自动配置所做的有助于解决您的要求!
See RestTemplateAutoConfiguration
:请参阅
RestTemplateAutoConfiguration
:
@Bean
@Lazy
@ConditionalOnMissingBean
public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer restTemplateBuilderConfigurer) {
RestTemplateBuilder builder = new RestTemplateBuilder();
return restTemplateBuilderConfigurer.configure(builder);
}
Therefore your custom bean should be similar:因此,您的自定义 bean 应该类似:
@Bean
public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer restTemplateBuilderConfigurer) {
RestTemplateBuilder builder = new RestTemplateBuilder();
return restTemplateBuilderConfigurer.configure(builder);
}
That RestTemplateBuilderConfigurer
delegates to respective customizers. RestTemplateBuilderConfigurer
委托给相应的定制器。 At the moment I see this impls in my classpath:目前我在我的类路径中看到这个 impls:
MockServerRestTemplateCustomizer
ObservationRestTemplateCustomizer
TraceRestTemplateCustomizer
Where ObservationRestTemplateCustomizer
is the one which instruments a RestTempalte
with micrometer-tracing.其中
ObservationRestTemplateCustomizer
是使用测微计跟踪检测RestTempalte
的工具。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.