之前一篇文章中说到当我们放弃 spring-cloud-sleuth
这个组件时,会面临两个问题。首先是日志中无法显示traceId和spanId这些链路信息,其次是不能在用 spring-cloud-sleuth
所提供的方式进行链路传值。现在就让我们来解决这两个问题。上篇回顾
spring-cloud-sleuth
是将traceId等链路信息保存在 slf4j
的MDC(Mapped Diagnostic Contexts)中,然后通过%X{traceId}这种方式将traceId提取出来,比如打印到控制台的默认格式是:
%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(%5p [${spring.application.name:-},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}} 复制代码
opentracing
中提供了 ThreadLocalScopeManager 这个类来管理调用上下文中的Span,我们可以继承该类将traceId设置到MDC中。
public class MDCScopeManager extends ThreadLocalScopeManager { @Override public Scope activate(Span span, boolean finishOnClose) { return new ScopeWrapper(super.activate(span, finishOnClose)); } @Override public Scope activate(Span span) { return new ScopeWrapper(super.activate(span)); } private static class ScopeWrapper implements Scope { private final Scope scope; private final String previousTraceId; private final String previousSpanId; private final String previousParentSpanId; private final String previousSampled; ScopeWrapper(Scope scope) { this.scope = scope; this.previousTraceId = lookup("traceId"); this.previousSpanId = lookup("spanId"); this.previousParentSpanId = lookup("parentSpanId"); this.previousSampled = lookup("traceSampled"); JaegerSpanContext ctx = (JaegerSpanContext) scope.span().context(); String traceId = ctx.getTraceId(); String spanId = Long.toHexString(ctx.getSpanId()); String sampled = String.valueOf(ctx.isSampled()); String parentSpanId = Long.toHexString(ctx.getParentId()); replace("traceId", traceId); replace("spanId", spanId); replace("parentSpanId", parentSpanId); replace("traceSampled", sampled); } @Override public void close() { this.scope.close(); replace("traceId", previousTraceId); replace("spanId", previousSpanId); replace("parentSpanId", previousParentSpanId); replace("traceSampled", previousSampled); } @Override public Span span() { return this.scope.span(); } } private static String lookup(String key) { return MDC.get(key); } private static void replace(String key, String value) { if (value == null) { MDC.remove(key); } else { MDC.put(key, value); } } } 复制代码
然后把这个类定义成Bean,这样就能把它绑定到当前的tracer中去:
@Bean public TracerBuilderCustomizer mdcBuilderCustomizer() { return builder -> builder.withScopeManager(new MDCScopeManager()); } 复制代码
然后利用 %X{traceId}
这种方式设置打印格式,启动程序后就能在控制台中看到输出了:
是不是和 spring-cloud-sleuth
提供的方式一样~~~
opentracing
中提供了baggage元素来做跨进程的kv传递,我们可以利用baggage来传递我们需要传递的值。(注意:同时他也会产生巨大的开销,请小心使用此特性)
public class TraceContext { public static void setField(String key, String value) { if (GlobalTracer.isRegistered() && StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) { Tracer tracer = GlobalTracer.get(); tracer.activeSpan().setBaggageItem(key, value); } } public static String getFiled(String key, String defaultValue) { if (GlobalTracer.isRegistered() && StringUtils.isNotBlank(key)) { Tracer tracer = GlobalTracer.get(); return tracer.activeSpan().getBaggageItem(key); } return defaultValue; } } 复制代码
项目中需要依赖 opentracing-concurrent
:
<dependency> <groupId>io.opentracing.contrib</groupId> <artifactId>opentracing-concurrent</artifactId> <version>0.4.0</version> </dependency> 复制代码
然后可以通过 TraceRunnable
来创建带有trace的线程
new Thread(new TracedRunnable(() -> { //线程中干活.... }, GlobalTracer.get())); 复制代码
Spring环境中也可以用 @Autowired
来获取tracer
@Autowired private Tracer tracer; 复制代码