如何使用 Spring Boot 3.4 从自定义标头传播跟踪

如何使用 Spring Boot 3.4 从自定义标头传播跟踪
如何使用 Spring Boot 3.4 从自定义标头传播跟踪

在 Spring Boot 3.4 中处理自定义标头跟踪

想象一下,您有一个 Spring Boot 3.4 Web 服务与两个客户端无缝协作。第一个客户端使用 Spring Boot 3+,使跟踪传播变得轻而易举。无需任何额外的努力,您就可以获得美丽的端到端跟踪连续性🪄。原木看起来干净且相互连接,就像魔术一样。

然而,当客户端二发挥作用时,事情发生了转变。他们发送自定义标头,例如“ot-custom-traceid”和“ot-custom-spanid”,而不是标准跟踪标头。虽然这些自定义标头包含有效的跟踪信息,但 Spring Boot 无法传播这些跟踪。结果呢?您失去了将客户端跟踪与服务器端日志连接起来的能力。

这造成了可观察性差距。对于客户端一,您可以看到跨服务的请求的完整路径。对于客户端二,您只能看到服务器端日志,缺少关键客户端跟踪。这就像看到半个拼图​​——你知道缺少了一些东西,但无法将它们拼凑在一起。 😓

在本文中,我们将探讨如何解决这个问题不依赖 Spring Cloud Sleuth,忠于 Spring Boot 3.4 生态系统。最后,您将了解如何传播并继续来自自定义标头的跟踪,从而确保整个系统的无缝可观察性。

命令 使用示例
MDC.put 此命令将键值对添加到 映射诊断上下文 (MDC),从而允许在日志中包含自定义跟踪 ID。例如,MDC.put(“traceId”,“12345”)。
MDC.clear 处理请求后清除 MDC 中的所有条目,以避免请求之间的跟踪污染。例如,MDC.clear()。
OncePerRequestFilter Spring Boot 过滤器,确保每个 HTTP 请求仅执行一次过滤器逻辑,非常适合跟踪标头。示例:公共类 CustomTraceFilter 扩展了 OncePerRequestFilter。
filterChain.doFilter 继续到链中的下一个过滤器,确保请求继续通过其他过滤器。例如,filterChain.doFilter(请求,响应)。
RestTemplate.getInterceptors() 检索 RestTemplate 实例的拦截器列表,允许添加自定义拦截器。示例:restTemplate.getInterceptors().add(new CustomInterceptor())。
ClientHttpRequestInterceptor 用于拦截传出 HTTP 请求并添加自定义标头的接口。例如,实现 ClientHttpRequestInterceptor 来插入跟踪 ID。
HttpServletRequest.getHeader 从传入请求中提取特定 HTTP 标头的值。示例:request.getHeader(“ot-custom-traceid”)。
FilterRegistrationBean 在 Spring Boot 应用程序中注册自定义过滤器。例如:registrationBean.setFilter(new CustomTraceFilter())。
MockMvc.perform 在 Spring Boot 应用程序的单元测试中模拟 HTTP 请求。示例:mockMvc.perform(get("/test-endpoint").header("ot-custom-traceid", "12345"))。
ClientHttpRequestExecution.execute 使用提供的请求正文和标头执行拦截的 HTTP 请求。示例:execution.execute(request, body)。

Spring Boot 中的自定义标头跟踪传播

解决此问题的关键组件之一是 CustomTraceFilter。该过滤器扩展了 每个请求过滤一次 类,确保跟踪标头逻辑对于每个 HTTP 请求仅运行一次。在全局修改请求或响应时,Spring Boot 中的过滤器非常有用。例如,如果客户端发送如下跟踪信息 ot-自定义-traceid 或者 ot-定制-spanid 在自定义标头中,此过滤器拦截请求,提取这些标头,并将它们传播到 映射诊断上下文 (MDC)。通过将跟踪 ID 添加到 MDC,我们确保这些标识符在请求处理期间生成的日志中可见。

MDC 是 SLF4J 和 Logback 等日志框架的关键部分。它允许我们存储当前线程的上下文信息,例如自定义跟踪 ID。使用类似命令 MDC.putMDC.清除,我们确保日志系统包含跟踪详细信息并避免并发请求之间的污染。例如,如果客户端二发送“ot-custom-traceid”为“8f7ebd8a73f9a8f50e6a00a87a20952a”,则该 ID 存储在 MDC 中并包含在所有下游日志中,从而创建一致的跟踪路径。

另一方面,对于传出 HTTP 请求,RestTemplate 拦截器起着至关重要的作用。通过实施 客户端HttpRequest拦截器,我们可以将相同的跟踪标头(“ot-custom-traceid”和“ot-custom-spanid”)附加到传出请求。这可以确保应用程序调用其他微服务时保持跟踪连续性。例如,当服务器处理跟踪 ID“8f7ebd8a73f9a8f50e6a00a87a20952a”的请求时,它会将此 ID 附加到传出标头,以便下游服务可以无缝识别和传播跟踪。

最后,使用 MockMvc 编写的单元测试通过模拟 HTTP 请求并验证标头传播来验证整个设置。在实际应用中,测试对于确保正确处理跟踪标头至关重要。例如,通过发送带有自定义标头的 GET 请求并检查响应或日志,我们可以确认过滤器和拦截器是否按预期工作。这种全面的方法解决了这一挑战,无需依赖 Spring Cloud Sleuth 等遗留依赖项。最终,过滤器、拦截器和 MDC 的组合即使在客户端使用自定义标头时也能确保跟踪连续性,从而使系统强大且完全可观察。 🌟

在 Spring Boot 3.4 中传播自定义跟踪标头

使用 Java 与 Spring Boot 3.4 和 Micrometer 进行后端处理

// Solution 1: Extract and Propagate Custom Trace Headers Manually
// Import necessary Spring Boot and Micrometer libraries
import org.slf4j.MDC;
import org.springframework.http.HttpHeaders;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class CustomTraceFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws IOException {
        String traceId = request.getHeader("ot-custom-traceid");
        String spanId = request.getHeader("ot-custom-spanid");
        try {
            if (traceId != null) {
                MDC.put("traceId", traceId); // Add traceId to Mapped Diagnostic Context
            }
            if (spanId != null) {
                MDC.put("spanId", spanId);
            }
            filterChain.doFilter(request, response); // Continue request processing
        } finally {
            MDC.clear(); // Ensure MDC is cleared after processing
        }
    }
}

// Register the filter in your configuration class
@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean<CustomTraceFilter> traceFilter() {
        FilterRegistrationBean<CustomTraceFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new CustomTraceFilter());
        registrationBean.addUrlPatterns("/*");
        return registrationBean;
    }
}

自定义跟踪标头传播的单元测试

使用 JUnit 和 MockMvc 进行测试以验证跟踪标头传播

// Import necessary libraries
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest
public class CustomTraceFilterTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testCustomTraceHeaders() throws Exception {
        mockMvc.perform(get("/test-endpoint")
                .header("ot-custom-traceid", "12345")
                .header("ot-custom-spanid", "67890"))
                .andExpect(status().isOk());
    }
}

使用 RestTemplate 在 HTTP 请求中传播自定义标头

使用 RestTemplate 拦截器在传出请求中添加自定义标头

// Import necessary libraries
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;

public class CustomHeaderInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        HttpHeaders headers = request.getHeaders();
        headers.add("ot-custom-traceid", "12345");
        headers.add("ot-custom-spanid", "67890");
        return execution.execute(request, body);
    }
}

// Register the interceptor with RestTemplate
@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getInterceptors().add(new CustomHeaderInterceptor());
        return restTemplate;
    }
}

在 Spring Boot 3.4 中使用 OpenTelemetry 处理自定义标头跟踪

使用 Spring Boot 3.4 时,从自定义标头传播跟踪的另一种强大方法是集成 开放式遥测。 OpenTelemetry 是一个开源可观测性框架,可帮助无缝检测、收集和导出跟踪。它提供了提取和注入跟踪上下文的机制,包括自定义标头,例如 ot-自定义-traceidot-定制-spanid,进入您的应用程序。通过利用 OpenTelemetry 的 TextMapPropagator,您可以弥合非标准客户端和可观测系统之间的差距。

要在 Spring Boot 3.4 中使用 OpenTelemetry,可以实现自定义传播器来从自定义标头中提取跟踪信息并将其附加到当前跟踪上下文。例如,当您的服务器收到来自客户端二的传入请求时,OpenTelemetry 可以解析自定义标头并重建原始跟踪上下文。这可确保下游服务看到相同的跟踪 ID,从而实现端到端可见性。与 Spring Cloud Sleuth 等旧解决方案不同,OpenTelemetry 是轻量级的,并且符合现代可观测性标准。

通过将 OpenTelemetry 的传播器与 Micrometer 相结合,您可以使用跟踪信息丰富您的指标和日志记录。想象一下,在您的可观察性工具中无缝地查看来自客户端一和客户端二的请求的跟踪。 OpenTelemetry 自动支持与 Prometheus、Zipkin 或 Jaeger 的集成,使您能够集中跟踪可视化。这种方法确保即使涉及自定义标头,也不会丢失跟踪数据,并且调试变得更加容易。 🚀

有关在 Spring Boot 中传播自定义跟踪的常见问题

  1. 如何在 Spring Boot 中手动提取自定义跟踪标头?
  2. 您可以使用 request.getHeader("custom-header") 手动获取特定标头并使用 MDC.put("traceId", value) 将其添加到 MDC。
  3. 使用 OpenTelemetry 进行自定义跟踪传播有什么好处?
  4. OpenTelemetry 提供了一种现代的、供应商中立的方法来跨微服务传播跟踪,包括自定义标头。
  5. 我可以在 Spring Boot 中使用 RestTemplate 传播自定义标头吗?
  6. 是的,通过实现 ClientHttpRequestInterceptor,您可以将 traceid 和 spanid 等自定义标头附加到传出请求。
  7. 如何注册过滤器以捕获全局标头?
  8. 您可以创建一个扩展 OncePerRequestFilter 的过滤器,并使用 FilterRegistrationBean 注册它以捕获所有端点的标头。
  9. 我可以使用哪些工具来可视化 Spring Boot 的跟踪?
  10. Zipkin、Jaeger 和 Prometheus 等工具可以与 Spring Boot 和 OpenTelemetry 集成,以可视化端到端跟踪。

确保无缝跟踪连续性

在现代系统中,处理自定义跟踪标头对于可靠的可观察性至关重要。通过使用过滤器和拦截器,您可以捕获客户端提供的跟踪信息并将其在您的服务中正确传播。这可以避免日志碎片和丢失痕迹。 🔍

Spring Boot 3.4 与 Micrometer 或 OpenTelemetry 相结合,可以提供强大的解决方案,而无需依赖 Spring Cloud Sleuth 等旧工具。无论您是处理客户端一的标准标头还是客户端二的自定义标头,实施这些技术都可以有效地弥补跟踪差距。 🚀

来源和参考文献
  1. Spring Boot 官方文档:跟踪上下文的传播。 Spring Boot 文档
  2. 适用于 Java 开发人员的 OpenTelemetry:跟踪传播指南。 OpenTelemetry Java
  3. 微米可观测性文档:集成自定义跟踪标头。 千分尺可观测性
  4. SLF4J 日志记录 API:映射诊断上下文 (MDC) 用例。 SLF4J 手册