问题描述
我正在将 thorntail (2.4.0.Final) 迁移到 Quarkus (1.11.1.Final)。 在测试阶段,我们注意到分布式跟踪不起作用。 跟踪有效(单个组件跟踪),但 uber-trace-id 没有通过 rest 请求标头传递,因此下一个微服务(rest 请求的接收者)正在从零生成 Trace,而没有 span 信息。
在 thorntail 中,它只是通过 ClientBuilder 上的配置完成
import org.eclipse.microprofile.opentracing.ClientTracingRegistrar;
import javax.ws.rs.client.ClientBuilder;
...
ClientBuilder clientBuilder = ClientBuilder.newBuilder();
...
ClientTracingRegistrar.configure(clientBuilder);
比较两者,我注意到 thorntail 有 ClientTracingRegistrarProvider 可用
public class ResteasyClientTracingRegistrarProvider implements ClientTracingRegistrarProvider {
public ResteasyClientTracingRegistrarProvider() {
}
public ClientBuilder configure(ClientBuilder clientBuilder) {
return this.configure(clientBuilder,Executors.newFixedThreadPool(10));
}
public ClientBuilder configure(ClientBuilder clientBuilder,ExecutorService executorService) {
ResteasyClientBuilder resteasyClientBuilder = (ResteasyClientBuilder)clientBuilder;
Tracer tracer = (Tracer)CDI.current().select(Tracer.class,new Annotation[0]).get();
return (ClientBuilder)resteasyClientBuilder.executorService(new TracedExecutorService(executorService,tracer)).register((new Builder(tracer)).withTraceSerialization(false).build());
}
}
带有所有与wildfly相关的配置,如文件 meta-inf/services/org.eclipse.microprofile.opentracing.ClientTracingRegistrarProvider 带有提供程序路径 org.wildfly.swarm.mpopentracing.deployment.ResteasyClientTracingRegistrarProvider
在 Quarkus 中,我们以相同的方式构建休息客户端,但没有这样的提供者。 有没有人知道我们需要改变什么才能拥有这种分布式跟踪功能?
附加信息: 我们正在使用 jaxrs API (javax.ws.rs) 构建休息客户端 并使用依赖项构建应用程序:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-opentracing</artifactId>
</dependency>
预先感谢所有建议和帮助。
解决方法
通常的解决方法如下: 我们有用于休息请求的预处理器和后处理器,它们通过 org.jboss.logging.MDC 将 uber-trace-id 存储在本地线程中
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.4.1.Final</version>
</dependency>
然后在休息客户端我们只是使用这个线程本地值。
代码如下: 如果传入请求头中存在 ubraTraceId,则预匹配是读取和存储 ubraTraceId
import java.nio.charset.StandardCharsets;
import javax.annotation.Priority;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.ext.Provider;
...
@Provider
@PreMatching
@Priority(1)
public class PreMatchingTraceIdRequestFilter implements ContainerRequestFilter {
...
@Override
public void filter(ContainerRequestContext containerRequestContext) {
// Remove uberTraceID from the logging context which can contain a previous request.
// Additionally done in ClearLoggingContextWriterInterceptor
LoggingUtil.clearContext();
String jaegerUberTraceID = decode(containerRequestContext.getHeaderString(HTTP_HEADER_UBER_TRACE_ID));
if (nonNull(jaegerUberTraceID)) {
LoggingUtil.setUberTraceId(jaegerUberTraceID);
//here some logging
}
// else case handled by the PostMatchingTraceIdRequestFilter later in the jax-rs request processing chain.
}
private static String decode(String headerString) {
return StringHelper.decode(String.valueOf(headerString),StandardCharsets.UTF_8.name());
}
}
并且在后期匹配中,我们希望从 Jaeger tracer 新生成的 uberTraceId 并存储它。
import io.jaegertracing.internal.JaegerSpanContext;
import io.opentracing.Tracer;
import javax.annotation.Priority;
import javax.inject.Inject;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.ext.Provider;
import static java.util.Objects.nonNull;
@Provider
// IMPORTANT: don't give this filter a higher priority!
// Cannot be invoked earlier because the Jaeger Client has to initialize the span for this request first.
// Tests revealed that the span is not initialized yet if this filter is given a higher priority!
@Priority(Priorities.USER)
public class PostMatchingTraceIdRequestFilter implements ContainerRequestFilter {
private Tracer tracer;
@Inject
public void setTracer(Tracer tracer) {
this.tracer = tracer;
}
@Override
public void filter(ContainerRequestContext containerRequestContext) {
String jaegerUberTraceId = getJaegerUberTraceID();
if (nonNull(jaegerUberTraceId)) {
LoggingUtil.setUberTraceId(jaegerUberTraceId);
// here some logging
}
// No else since an missing active span on Tracer can happen more often than you think:
// using @Traced(false) for example suppresses the generation of a new span
}
private String getJaegerUberTraceID() {
String uberTraceId = null;
if (nonNull(tracer) && nonNull(tracer.activeSpan())) {
JaegerSpanContext spanContext = (JaegerSpanContext) tracer.activeSpan().context();
uberTraceId =
new StringBuilder()
.append(spanContext.getTraceId())
.append(UBER_TRACE_ID_SEPARATOR)
.append(decode(spanContext.getSpanId()))
.append(UBER_TRACE_ID_SEPARATOR)
.append(decode(spanContext.getParentId()))
.append(UBER_TRACE_ID_SEPARATOR)
.append(spanContext.getFlags())
.toString();
}
return uberTraceId;
}
private static String decode(long longValue) {
return Long.toHexString(longValue);
}
}
LogginUtil 只是 jboss 日志记录中 MDC 的包装器。 它确实
public static void clearContext() {
MDC.clear();
}
public static void setUberTraceId(String uberTraceId) {
MDC.put(MDC_UBER_TRACE_ID,StringUtils.defaultIfEmpty(uberTraceId,StringUtils.EMPTY));
}
public static String getUberTraceId() {
return (String) MDC.get(MDC_UBER_TRACE_ID);
}
public static String getUberTraceIdAndRemoveFromContext() {
final String traceId = getUberTraceId();
clearContext();
return traceId;
}
最后是休息客户端,这里只是从线程本地(通过 MDC)读取 uberTraceId
import javax.ws.rs.client.ClientBuilder;
import org.eclipse.microprofile.opentracing.ClientTracingRegistrar;
...
protected void addUberTraceIdHeader(Invocation.Builder builder) {
String uberTraceId = LoggingUtil.getUberTraceId();
if (isNotBlank(uberTraceId)) {
builder.header(TraceExtractor.HTTP_HEADER_UBER_TRACE_ID,uberTraceId);
} else {
throw new IllegalStateException("Lack of uber trace id");
}
}