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

如何在SyncGraphQlClientInterceptor中获取响应头实现日志追踪?

问题描述

我们的GraphQL服务端会在响应头返回apollo-trace-id,需要在以下两种场景记录该ID以提升可观测性:

  • 服务端返回错误HTTP状态码
  • 响应数据存在错误(如errors块包含无效内容)

现有方案存在明显不足:

  • 使用ClientHttpRequestInterceptor可以处理第一种场景,但无法获取反序列化后的响应体,难以处理第二种场景
  • 使用SyncGraphQlClientInterceptor可以处理第二种场景,但无法访问响应头(GraphQlResponse和HttpSyncGraphQlTransport未传递该信息)

目前有两种不理想的临时方案:

  1. 在ClientHttpRequestInterceptor层面用含ThreadLocal字段的类存储apollo-trace-id,再在SyncGraphQlClientInterceptor中获取
  2. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 12:24:51