Table of contents
1 Spring Boot 3 既 micrometer tracing
Spring Boot 3 既其中一個比較大既改變,就係唔再支援 Spring Boot Sleuth(又或者話 Spring Boot Sleuth 唔支援 Spring Boot 3)。
!!!! IMPORTANT !!!!
Spring Cloud Sleuth’s last minor version is 3.1. You can check the 3.1.x branch for the latest commits.
Spring Cloud Sleuth will not work with Spring Boot 3.x onward. The last major version of Spring Boot that Sleuth will support is 2.x.
The core of this project got moved to
Micrometer Tracing project and the instrumentations will be moved to
Micrometer and all respective projects (no longer all instrumentations will be done in a single repository.
即係話,如果我地想繼續喺 HTTP requests 度加入 trace ID、span ID,我地要改用 micrometer tracing。
2 定義
喺 distributed tracing 裡面,我地有 trace ID、span ID 呢兩個 terms。
2.1 Trace ID
Trace ID 代表緊一個 distributed 既 request workflow,而呢個 request 會訪問好幾個 back-end microservices。利用同一個 trace ID 可以幫我地串連呢啲 back-end microservices,方便我地將同一個 request 既所有 microservices 既所有 application logs 搵出黎 troubleshoot 或者 debug。
進入一個新既 request workflow 既第一個 microservice 會負責生成 trace ID,然後當佢 send HTTP request 去佢既 downstream microservice 既時候,佢會喺個 HTTP request 加入一個 traceparent
request header,而個 header value 既格式應該要符合 W3C 既 Trace Context 標準。
2.2 Span ID
Span ID 代表緊一個 request workflow 裡面既其中一小個 operation,係某一個 microservice 既某一個 operation unit。
3 動手寫
3.1 Maven dependencies
所有 microservices 都要有以下既 Maven dependencies:
1<parent>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-starter-parent</artifactId>
4 <version>3.2.3</version>
5</parent>
6
7<dependency>
8 <groupId>io.micrometer</groupId>
9 <artifactId>micrometer-registry-prometheus</artifactId>
10</dependency>
11<dependency>
12 <groupId>io.micrometer</groupId>
13 <artifactId>micrometer-tracing-bridge-brave</artifactId>
14</dependency>
15
16<dependency>
17 <groupId>net.logstash.logback</groupId>
18 <artifactId>logstash-logback-encoder</artifactId>
19 <version>7.2</version>
20</dependency>
如果需要用 WebClient
去 call downstream microservice 既話就加:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>
如果需要用 OpenFeign 去 call downstream microservice 既話就加:
1<dependency>
2 <groupId>org.springframework.cloud</groupId>
3 <artifactId>spring-cloud-starter-openfeign</artifactId>
4</dependency>
5<dependency>
6 <groupId>io.github.openfeign</groupId>
7 <artifactId>feign-micrometer</artifactId>
8</dependency>
3.2 寫 Java code
3.2.1 Spring RestTemplate
如果要令到 RestTemplate
識得喺 request header 度 forward micrometer tracing 既 data 去 downstream microservice 度,就要用 RestTemplateBuilder
bean 去創建 RestTemplate
:
1@Configuration
2public class RestTemplateConfig {
3
4 @Bean
5 RestTemplate restTemplate(RestTemplateBuilder builder) {
6
7 final RestTemplate restTemplate = builder
8 .setConnectTimeout(Duration.ofSeconds(10))
9 .basicAuthentication("username", "password")
10 .defaultHeader("header1", "header1-value")
11 .defaultHeader("header2", "header2-value")
12 .build();
13
14 // 如果加入左 jackson-dataformat-xml
15 // 令 RestTemplate 唔好喺 Accept request header 度加入 application/xml 或相關既 values
16 restTemplate.getMessageConverters()
17 .removeIf(e -> e.getClass()==MappingJackson2XmlHttpMessageConverter.class);
18
19 return restTemplate;
20 }
21}
3.2.2 Spring Webflux WebClient
如果要令到 WebClient
識得喺 request header 度 forward micrometer tracing 既 data 去 downstream microservice 度,就要用 WebClient.Builder
bean 去創建 WebClient
:
1@Configuration
2public class WebClientConfig {
3
4 @Bean
5 WebClient webClient(WebClient.Builder builder) {
6
7 final HttpClient httpClient = HttpClient.newBuilder()
8 .version(Version.HTTP_1_1)
9 .connectTimeout(Duration.ofSeconds(10))
10 .build();
11
12 final WebClient client = builder
13 .clientConnector(new JdkClientHttpConnector(httpClient))
14 .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
15 .defaultHeader("header1", "header1-value")
16 .defaultHeader("header2", "header2-value")
17 .build();
18
19 return client;
20 }
21}
3.2.3 Spring 6.1 RestClient
如果要令到 RestClient
識得喺 request header 度 forward micrometer tracing 既 data 去 downstream microservice 度,就要用 RestClient.Builder
bean 去創建 RestClient
:
1@Configuration
2public class RestClientConfig {
3
4 @Bean
5 RestClient restClient(RestClient.Builder builder) {
6
7 final RestClient client = builder
8 .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
9 .defaultHeader("header1", "header1-value")
10 .defaultHeader("header2", "header2-value")
11 .build();
12
13 return client;
14 }
15}
3.2.4 Spring Cloud OpenFeign
如果要令到 OpenFeign 識得喺 request header 度 forward micrometer tracing 既 data 去目標既 microservice 度,只需要喺 pom.xml
加入 io.github.openfeign:feign-micrometer
既 Maven dependency。
呢個 dependency 既版本由以下既 dependency management BOMs 控制。
org.springframework.cloud:spring-cloud-dependencies
org.springframework.cloud:spring-cloud-openfeign-dependencies
io.github.openfeign:feign-bom
3.3 Logback 配置
最後一步,我地需要喺 logback.xml
裡面加入 trace ID 以及 span ID 既 MDC fields,咁喺 application logs 度就可以睇到佢地。
因為係 distributed microservices 既關係,我地如果用好似 ELK 咁既 centralized logging platform 既話,一定要可以分得到唔同既 microservices 既 logs,咁就要喺 logback.xml
既 encoders 度配置好:
- Host name,可以直接用
${HOSTNAME:-}
或者 ${hostname:-}
。
- Spring application name,可以加入以下既配置,之後就可以用
${app_name:-}
:
3.3.1 自定義 encoder pattern
如果要喺自定義既 encoder pattern 裡面加入 trace ID 以及 span ID,我地可以用:
%X{traceId:-}
%X{spanId:-}
例子:
<!-- 喺 appender 裡面 -->
<encoder>
<pattern>[${hostname:-} ${app_name:-}] [%X{traceId:-} %X{spanId:-}] %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} (%file:%line\) - %msg%n</pattern>
</encoder>
註:
- 呢度既
:-
係用黎分隔 key、value 既字符,佢既前面係 MDC key,後面係 value。
- 佢同 Spring 既
:
一樣意思,只不過係 Logback 揀左用 :-
。
%X{key:-}
既意思係如果 key
呢個 MDC key 未有 data 既話就會出 empty string(唔好誤以為會出 -
)。
%X{key:-foo}
既意思係如果 key
呢個 MDC key 未有 data 既話就會出 foo
。
3.3.2 Logstash Logback Encoder
如果係用 Logstash Logback Encoder,亦即係 net.logstash.logback:logstash-logback-encoder
既 Maven dependency,咁我地就要用佢既 includeMdcKeyName
XML element 去加入 trace ID 以及 span ID:
1<!-- 喺 appender 裡面 -->
2<encoder class="net.logstash.logback.encoder.LogstashEncoder">
3 <includeContext>false</includeContext>
4 <includeMdcKeyName>traceId</includeMdcKeyName>
5 <includeMdcKeyName>spanId</includeMdcKeyName>
6 <customFields>{ "host": "${hostname:-}", "app_name": "${app_name:-}" }</customFields>
7</encoder>
加左之後,每一個 log entry 既 JSON data 裡面都會多左 traceId
以及 spanId
既 key-value pairs。
4 測試
4.1 本地測試
- 我地需要 set up 至少兩個 Spring Boot microservices。
- 全部 microservices 都要 expose 至少一個 HTTP REST endpoints。
- 除左最後一個只會被 call 但唔會 initiate call 既 microservice,我地要為所有作為 upstream 既其他 microservices 寫 HTTP call 既 code。
- HTTP REST call flow:Service A ➜ Service B ➜ Service C 等等。
- HTTP REST call 既 implementation 可以係
RestTemplate
、WebClient
、RestClient
或者 OpenFeign。
- 用 cURL call Service A 既 HTTP REST endpoint。
- 檢查所有 microservices 既 console logs。
- 應該會見到只有一個
32
個位長度 alphanumeric 既 trace ID。
- 應該會見到每個 microservice 都有啲唔同既
16
個位長度 alphanumeric 既 span IDs。
- 再用 cURL call Service A 既 HTTP REST endpoint。
- 再檢查所有 microservices 既 console logs。
- 應該會見到一個完全唔同既 trace ID。
- 應該會見到每個 microservice 又有啲唔同既 span IDs。
4.2 測試發送 logs 去 ELK
- 我地需要 set up 至少兩個 Spring Boot microservices。
- 我地要配置好全部 microservices 既
logback.xml
,將 <appender>
既 <destination>
配置為 127.0.0.1:5000
,咁啲 logs 就會發送到本地部署既 Logstash。
- 全部 microservices 都要 expose 至少一個 HTTP REST endpoints。
- 除左最後一個只會被 call 但唔會 initiate call 既 microservice,我地要為所有作為 upstream 既其他 microservices 寫 HTTP call 既 code。
- HTTP REST call flow:Service A ➜ Service B ➜ Service C 等等。
- HTTP REST call 既 implementation 可以係
RestTemplate
、WebClient
、RestClient
或者 OpenFeign。
- 用 cURL call Service A 既 HTTP REST endpoint。
- 打開 http://localhost:5601 檢查我地喺 Kibana 既 data view。
- 應該會見到只有一個
32
個位長度 alphanumeric 既 trace ID。
- 應該會見到每個 microservice 都有啲唔同既
16
個位長度 alphanumeric 既 span IDs。
- 再用 cURL call Service A 既 HTTP REST endpoint。
- 再打開 http://localhost:5601 檢查我地喺 Kibana 既 data view。
- 應該會見到一個完全唔同既 trace ID。
- 應該會見到每個 microservice 又有啲唔同既 span IDs。
- 我地可以撳落
traceId
column 既某一個 cell value 隔籬既 ⊕
號(「Filter for this traceId」),咁就可以睇曬所有同呢個 trace ID 有關係既 logs。
5 參考資料