Opentelemetry 扩展 - 响应头添加 trace id

727 阅读1分钟

背景

Opentelemetry 简介

本文涉及代码地址

前端发现问题后,提供 trace id,可以大大提高问题排查的效率,因此考虑在请求响应头中增加 trace id

代码实现

对 servlet 和 webflux 做扩展,从而支持 webmvc、 webflux 以及 spring cloud gateway

servlet

拦截 javax.servlet.Filter 和 javax.servlet.http.HttpServlet 的如下方法

@Override
public void transform(TypeTransformer transformer) {

    transformer.applyAdviceToMethod(namedOneOf("doFilter", "service")
                    .and(ElementMatchers.takesArgument(0,
                            named("javax.servlet.ServletRequest")))
                    .and(ElementMatchers.takesArgument(1, named("javax.servlet.ServletResponse")))
                    .and(ElementMatchers.isPublic()),
            "com.github.freshchen.otel.javaagent.extensions.instrumentation.servlet.v3" +
                    ".ResponseTraceIdAdvice");
}

增强代码逻辑如下

public class ResponseTraceIdAdvice {

    @Advice.OnMethodEnter(suppress = Throwable.class)
    public static void onEnter(@Advice.Argument(value = 1) ServletResponse response) {

        if (response instanceof HttpServletResponse) {
            String traceIdHeader = InstrumentationConfig.get()
                    .getString("http.response.trace.id.header");
            if (!StringUtils.isNullOrEmpty(traceIdHeader)) {
                HttpServletResponse httpServletResponse = (HttpServletResponse) response;
                if (!httpServletResponse.containsHeader(traceIdHeader)) {
                    String traceId = Java8BytecodeBridge.currentSpan().getSpanContext().getTraceId();
                    if (!StringUtils.isNullOrEmpty(traceId)) {
                        httpServletResponse.setHeader(traceIdHeader, traceId);
                    }
                }
            }
        }
    }
}

webflux

拦截 org.springframework.web.reactive.HandlerAdapter 的如下方法

@Override
public void transform(TypeTransformer transformer) {
    transformer.applyAdviceToMethod(
            isMethod()
                    .and(isPublic())
                    .and(named("handle"))
                    .and(takesArgument(0, named("org.springframework.web.server.ServerWebExchange")))
                    .and(takesArgument(1, Object.class))
                    .and(takesArguments(2)),
            "com.github.freshchen.otel.javaagent.extensions.instrumentation.webflux.v5_0.server" +
                    ".ResponseTraceIdAdvice");
}

增强代码逻辑如下

public class ResponseTraceIdAdvice {

    @Advice.OnMethodEnter(suppress = Throwable.class)
    public static void methodEnter(
            @Advice.Argument(0) ServerWebExchange exchange) {

        String traceIdHeader = InstrumentationConfig.get()
                .getString("http.response.trace.id.header");
        if (!StringUtils.isNullOrEmpty(traceIdHeader)) {
            HttpHeaders headers = exchange.getResponse().getHeaders();
            if (!headers.containsKey(traceIdHeader)) {
                String traceId = Java8BytecodeBridge.currentSpan().getSpanContext().getTraceId();
                if (!StringUtils.isNullOrEmpty(traceId)) {
                    headers.add(traceIdHeader, traceId);
                }
            }
        }
    }
}

验证

如何使用参照项目 README github.com/freshchen/o…

启动应用增加配置 -Dhttp.response.trace.id.header=tid

servlet 测试结果

http://127.0.0.1:8080/ping

HTTP/1.1 200 
tid: e16cc50a2f58fe641d00ff8358609a8a
Content-Type: text/plain;charset=UTF-8
Content-Length: 4
Date: Fri, 24 Mar 2023 02:58:20 GMT
Keep-Alive: timeout=60
Connection: keep-alive

pong

Response code: 200; Time: 1058ms; Content length: 4 bytes

spring cloud gateway 测试结果

http://127.0.0.1:8080/ping

HTTP/1.1 404 Not Found
tid: be6b2083e6beb97c037a3a2d33e1fbf3
Content-Type: application/json
Content-Length: 133

{
  "timestamp": "2023-03-24T02:59:09.485+00:00",
  "path": "/ping",
  "status": 404,
  "error": "Not Found",
  "message": null,
  "requestId": "ed06b580-1"
}
Response file saved.
> 2023-03-24T105909.404.json

Response code: 404 (Not Found); Time: 637ms; Content length: 133 bytes