背景
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