spring cloud sleuth是用来解决分布式中服务的跟踪。span 和trace的图解如下:
在日志中打印traceId
通过ThreadLocal 、MDC进行实现。
通过实现RestTemplate拦截器接口:ClientHttpRequestInterceptor
private void setInterceptors(HttpTracing httpTracing) {
this.template.setInterceptors(Arrays.<ClientHttpRequestInterceptor>asList(
TracingClientHttpRequestInterceptor.create(httpTracing)));
}
参数传递一般通过拦截器的形式进行注入:
@Aspect
public class TraceSchedulingAspect {
private static final Log log = LogFactory.getLog(TraceSchedulingAspect.class);
private final Tracer tracer;
private final Pattern skipPattern;
private final TraceKeys traceKeys;
public TraceSchedulingAspect(Tracer tracer, Pattern skipPattern,
TraceKeys traceKeys) {
this.tracer = tracer;
log.info("TraceSchedulingAspect traceId " + tracer.toString());
this.skipPattern = skipPattern;
this.traceKeys = traceKeys;
}
@Around("execution (@org.springframework.scheduling.annotation.Scheduled * *.*(..))")
public Object traceBackgroundThread(final ProceedingJoinPoint pjp) throws Throwable {
if (this.skipPattern.matcher(pjp.getTarget().getClass().getName()).matches()) {
return pjp.proceed();
}
String spanName = SpanNameUtil.toLowerHyphen(pjp.getSignature().getName());
Span span = startOrContinueRenamedSpan(spanName);
log.info("traceBackgroundThread spanId:" + span);
try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) {
span.tag(this.traceKeys.getAsync().getPrefix() +
this.traceKeys.getAsync().getClassNameKey(), pjp.getTarget().getClass().getSimpleName());
span.tag(this.traceKeys.getAsync().getPrefix() +
this.traceKeys.getAsync().getMethodNameKey(), pjp.getSignature().getName());
return pjp.proceed();
} finally {
span.finish();
}
}
private Span startOrContinueRenamedSpan(String spanName) {
Span currentSpan = this.tracer.currentSpan();
if (currentSpan != null) {
return currentSpan.name(spanName);
}
return this.tracer.nextSpan().name(spanName);
}
}
tracer.currentSpan()
/** * Returns the current span in scope or null if there isn't one. * 返回当前线程作用域的span,如果不存在返回null * When entering user code, prefer {@link #currentSpanCustomizer()} as it is a stable type and * will never return null. toSpan(currentContext);使用上下文创建一个span */
@Nullable public Span currentSpan() {
TraceContext currentContext = currentTraceContext.get();
return currentContext != null ? toSpan(currentContext) : null;
}
nextSpan()
/** Returns a new child span if there's a {@link #currentSpan()} or a new trace if there isn't. */
public Span nextSpan() {
TraceContext parent = currentTraceContext.get();
return parent == null ? newTrace() : newChild(parent);
}
tracer.withSpanInScope()
设置span的作用域到线程上下文中:默认实现:
/** * Makes the given span the "current span" and returns an object that exits that scope on close. * 返回一个特定范围的对象。在对象关闭之前,一直保存这。 * Calls to {@link #currentSpan()} and {@link #currentSpanCustomizer()} will affect this span * until the return value is closed. * * The most convenient way to use this method is via the try-with-resources idiom. * * Ex. *
{@code * // Assume a framework interceptor uses this method to set the inbound span as current * try (SpanInScope ws = tracer.withSpanInScope(span)) { * return inboundRequest.invoke(); * } finally { * span.finish(); * } * * // An unrelated framework interceptor can now lookup the correct parent for outbound requests * Span parent = tracer.currentSpan() * Span span = tracer.nextSpan().name("outbound").start(); // parent is implicitly looked up * try (SpanInScope ws = tracer.withSpanInScope(span)) { * return outboundRequest.invoke(); * } finally { * span.finish(); * } * }
* * Note: While downstream code might affect the span, calling this method, and calling close on * the result have no effect on the input. For example, calling close on the result does not * finish the span. Not only is it safe to call close, you must call close to end the scope, or * risk leaking resources associated with the scope. * * @param span span to place into scope or null to clear the scope */
public SpanInScope withSpanInScope(@Nullable Span span) {
return new SpanInScope(currentTraceContext.newScope(span != null ? span.context() : null));
}
@Override public Scope newScope(@Nullable TraceContext currentSpan) {
final TraceContext previous = local.get();
local.set(currentSpan);
class DefaultCurrentTraceContextScope implements Scope {
@Override public void close() {
local.set(previous);
}
}
return new DefaultCurrentTraceContextScope();
}
}
Runnables、@Async Support、线程池的支持:
重点TraceRunnable 类:
**
* Runnable that passes Span between threads. The Span name is
* taken either from the passed value or from the {@link SpanNamer}
* interface.
*
* @author Spencer Gibb
* @author Marcin Grzejszczak
* @since 1.0.0
*/
public class TraceRunnable implements Runnable {
/** * Since we don't know the exact operation name we provide a default * name for the Span */
private static final String DEFAULT_SPAN_NAME = "async";
private final Tracer tracer;
private final Runnable delegate;
private final Span span;
private final ErrorParser errorParser;
public TraceRunnable(Tracer tracer, SpanNamer spanNamer, ErrorParser errorParser, Runnable delegate) {
this(tracer, spanNamer, errorParser, delegate, null);
}
public TraceRunnable(Tracer tracer, SpanNamer spanNamer, ErrorParser errorParser, Runnable delegate, String name) {
this.tracer = tracer;
this.delegate = delegate;
String spanName = name != null ? name : spanNamer.name(delegate, DEFAULT_SPAN_NAME);
//在主线程中执行
this.span = this.tracer.nextSpan().name(spanName);
this.errorParser = errorParser;
}
@Override
public void run() {
//在子线程中执行,进行传递
Throwable error = null;
try (SpanInScope ws = this.tracer.withSpanInScope(this.span.start())) {
this.delegate.run();
} catch (RuntimeException | Error e) {
error = e;
throw e;
} finally {
this.errorParser.parseErrorTags(this.span.customizer(), error);
this.span.finish();
}
}
}
this.tracer.nextSpan()
/** Returns a new child span if there's a {@link #currentSpan()} or a new trace if there isn't. */
public Span nextSpan() {
TraceContext parent = currentTraceContext.get();
return parent == null ? newTrace() : newChild(parent);
}
在上下文传递时,Trace和Span ID是必传项,Baggage是个可选性。Buggage是一个key,value的map结构。在HTTP的请求头中,如果请求头开始:baggage-,则Spring sleuth可以理解。
Span initialSpan = this.tracer.nextSpan().name("span").start();
ExtraFieldPropagation.set(initialSpan.context(), "foo", "bar");
ExtraFieldPropagation.set(initialSpan.context(), "UPPER_CASE", "someValue");
}
Span的中的tag是对特定span的tag。tag不是共享的,即不去传递。
参考:
https://www.baeldung.com/spring-cloud-sleuth-single-application