如何在SyncGraphQlClientInterceptor中获取响应头实现日志追踪?
问题描述
我们的GraphQL服务端会在响应头返回apollo-trace-id,需要在以下两种场景记录该ID以提升可观测性:
- 服务端返回错误HTTP状态码
- 响应数据存在错误(如
errors块包含无效内容)
现有方案存在明显不足:
- 使用
ClientHttpRequestInterceptor可以处理第一种场景,但无法获取反序列化后的响应体,难以处理第二种场景 - 使用
SyncGraphQlClientInterceptor可以处理第二种场景,但无法访问响应头(GraphQlResponse和HttpSyncGraphQlTransport未传递该信息)
目前有两种不理想的临时方案:
- 在
ClientHttpRequestInterceptor层面用含ThreadLocal字段的类存储apollo-trace-id,再在SyncGraphQlClientInterceptor中获取 - 在
ClientHttpRequestInterceptor层面额外反序列化响应体并记录trace ID
需要更优的解决方案。
更优解决方案
核心思路
通过请求属性传递trace ID,结合两种拦截器的优势:用ClientHttpRequestInterceptor提取响应头的apollo-trace-id并存入请求属性,再用SyncGraphQlClientInterceptor从请求属性中获取trace ID,同时处理HTTP错误和响应体错误两种场景。
具体实现
1. 实现请求拦截器提取并存储trace ID
public class TraceIdRequestInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { ClientHttpResponse response = execution.execute(request, body); // 提取响应头中的apollo-trace-id String traceId = response.getHeaders().getFirst("apollo-trace-id"); if (traceId != null) { // 将trace ID存入请求属性,供后续拦截器使用 request.getAttributes().put("apolloTraceId", traceId); } // 同时标记是否为HTTP错误状态码 int statusCode = response.getStatusCode().value(); if (statusCode >= 400) { request.getAttributes().put("isHttpError", true); } return response; } }
2. 实现GraphQL客户端拦截器处理两种场景
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.graphql.client.GraphQlRequest; import org.springframework.graphql.client.GraphQlResponse; import org.springframework.graphql.client.SyncGraphQlClientInterceptor; public class TraceIdLoggingInterceptor implements SyncGraphQlClientInterceptor { private final Logger logger = LoggerFactory.getLogger(TraceIdLoggingInterceptor.class); @Override public GraphQlResponse intercept(GraphQlRequest request, Chain chain) { GraphQlResponse response = chain.next(request); String traceId = (String) request.getAttributes().get("apolloTraceId"); // 场景1:处理HTTP错误状态码 Boolean isHttpError = (Boolean) request.getAttributes().get("isHttpError"); if (Boolean.TRUE.equals(isHttpError)) { logger.error("GraphQL请求返回HTTP错误. Trace ID: {}", traceId); return response; } // 场景2:处理响应体中的errors块 if (!response.getErrors().isEmpty()) { logger.error("GraphQL响应包含业务错误. Trace ID: {}", traceId); // 可选:打印错误详情 response.getErrors().forEach(error -> logger.error("错误详情: {}", error.getMessage())); } return response; } }
3. 配置GraphQL客户端
将两个拦截器注册到HttpSyncGraphQlClient中:
import org.springframework.graphql.client.HttpSyncGraphQlClient; public class GraphQlClientConfig { public SyncGraphQlClient createGraphQlClient() { return HttpSyncGraphQlClient.builder() .url("http://your-graphql-service-endpoint") .clientHttpRequestInterceptor(new TraceIdRequestInterceptor()) .interceptor(new TraceIdLoggingInterceptor()) .build(); } }
方案优势
- 规避
ThreadLocal的线程安全风险(尤其是异步调用场景下的内存泄露问题) - 无需额外反序列化响应体,复用Spring GraphQL的现有处理流程
- 统一处理两种错误场景,代码结构清晰、可维护性强
内容的提问来源于stack exchange,提问作者morohon
相关产品推荐
相关产品推荐

