spring-cloud-sleuth-core-1.3.3.RELEASE-sources.jar

org.springframework.cloud.sleuth.log.Slf4jSpanLogger.java 将Span信息写到MDC,log将MDC的traceId等信息打印

@Override
    public void logStartedSpan(Span parent, Span span) {
        MDC.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
        MDC.put(Span.SPAN_EXPORT_NAME, String.valueOf(span.isExportable()));
        MDC.put(Span.TRACE_ID_NAME, span.traceIdString());
        log("Starting span: {}", span);
        if (parent != null) {
            log("With parent: {}", parent);
            MDC.put(Span.PARENT_ID_NAME, Span.idToHex(parent.getSpanId()));
        }
    }

 

在spring-cloud-sleuth的META-INF里的spring.factories里设置了一下:

org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.sleuth.autoconfig.TraceEnvironmentPostProcessor
 

这样,TraceEnvironmentPostProcessor被配置在了ioc容器初始化之前。spring-cloud-sleuth-core包的org.springframework.cloud.sleuth.annotation.TraceEnvironmentPostProcessor.java

public class TraceEnvironmentPostProcessor implements EnvironmentPostProcessor {

    private static final String PROPERTY_SOURCE_NAME = "defaultProperties";
    private static final String SPRING_AOP_PROXY_TARGET_CLASS = "spring.aop.proxyTargetClass";

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment,
            SpringApplication application) {
        Map<String, Object> map = new HashMap<String, Object>();
        // This doesn't work with all logging systems but it's a useful default so you see
        // traces in logs without having to configure it.
        if (Boolean.parseBoolean(environment.getProperty("spring.sleuth.enabled", "true"))) {
            map.put("logging.pattern.level",
                    "%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]");
        }
        // TODO: Remove this in 2.0.x. For compatibility we always set to true
        if (!environment.containsProperty(SPRING_AOP_PROXY_TARGET_CLASS)) {
            map.put(SPRING_AOP_PROXY_TARGET_CLASS, "true");
        }
        addOrReplace(environment.getPropertySources(), map);
    }
    //...
}

 

在TraceEnvironmentPostProcessor的postProcessEnvironment()方法里保证了两件事情:

1、设置了spring.aop.proxyTargetClass参数为true保证了cglib代理的开启,并加入了日志的追踪打印的模板。
2、而后在配置类TraceAutoConfiguration中生成了Tracer,默认实现为DefaultTraces,作用为正式创建一个工作单元span。

紧随其后的配置类为SleuthAnnotationAutoConfiguration,见spring-cloud-sleuth-core包的org.springframework.cloud.sleuth.annotation.SleuthAnnotationAutoConfiguration.java

@Configuration
@ConditionalOnBean(Tracer.class)
@ConditionalOnProperty(name = "spring.sleuth.annotation.enabled", matchIfMissing = true)
@AutoConfigureAfter(TraceAutoConfiguration.class)
@EnableConfigurationProperties(SleuthAnnotationProperties.class)
public class SleuthAnnotationAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    SpanCreator spanCreator(Tracer tracer) {
        return new DefaultSpanCreator(tracer);
    }
    //...
    @Bean
    SleuthAdvisorConfig sleuthAdvisorConfig() {
        return new SleuthAdvisorConfig();
    }
}

 

此处根据之前生成的Tracer进一步创建了其包装类SpanCreator,并最重要的是生成了代理的配置类SleuthAdvisorConfig。

class SleuthAdvisorConfig  extends AbstractPointcutAdvisor implements BeanFactoryAware {

    private Advice advice;

    private Pointcut pointcut;

    private BeanFactory beanFactory;

    @PostConstruct
    public void init() {
        this.pointcut = buildPointcut();
        this.advice = buildAdvice();
        if (this.advice instanceof BeanFactoryAware) {
            ((BeanFactoryAware) this.advice).setBeanFactory(this.beanFactory);
        }
    }
    //...
}

 

其初始化方法中,完成了切面与切面增强的创建。
其buildPointcut()方法:

private Pointcut buildPointcut() {
        return new AnnotationClassOrMethodOrArgsPointcut();
    }

    /**
     * Checks if a class or a method is is annotated with Sleuth related annotations
     */
    private final class AnnotationClassOrMethodOrArgsPointcut extends
            DynamicMethodMatcherPointcut {

        @Override
        public boolean matches(Method method, Class<?> targetClass, Object... args) {
            return getClassFilter().matches(targetClass);
        }

        @Override public ClassFilter getClassFilter() {
            return new ClassFilter() {
                @Override public boolean matches(Class<?> clazz) {
                    return new AnnotationClassOrMethodFilter(NewSpan.class).matches(clazz) ||
                            new AnnotationClassOrMethodFilter(ContinueSpan.class).matches(clazz);
                }
            };
        }

    }

    private final class AnnotationClassOrMethodFilter extends AnnotationClassFilter {

        private final AnnotationMethodsResolver methodResolver;

        AnnotationClassOrMethodFilter(Class<? extends Annotation> annotationType) {
            super(annotationType, true);
            this.methodResolver = new AnnotationMethodsResolver(annotationType);
        }

        @Override
        public boolean matches(Class<?> clazz) {
            return super.matches(clazz) || this.methodResolver.hasAnnotatedMethods(clazz);
        }

    }

 

显而易见,切面的匹配通过目标类是否满足使用了NewSpan或者ContinueSpan注解。
切面的增强则通过buildAdvice来构造Interceptor来通过代理使用invoke()方法来完成生成span的目的。

    private Advice buildAdvice() {
        return new SleuthInterceptor();
    }
class SleuthInterceptor  implements IntroductionInterceptor, BeanFactoryAware  {

    private static final Log logger = LogFactory.getLog(MethodHandles.lookup().lookupClass());
    private static final String CLASS_KEY = "class";
    private static final String METHOD_KEY = "method";

    private BeanFactory beanFactory;
    private SpanCreator spanCreator;
    private Tracer tracer;
    private SpanTagAnnotationHandler spanTagAnnotationHandler;
    private ErrorParser errorParser;

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        if (method == null) {
            return invocation.proceed();
        }
        Method mostSpecificMethod = AopUtils
                .getMostSpecificMethod(method, invocation.getThis().getClass());
        NewSpan newSpan = SleuthAnnotationUtils.findAnnotation(mostSpecificMethod, NewSpan.class);
        ContinueSpan continueSpan = SleuthAnnotationUtils.findAnnotation(mostSpecificMethod, ContinueSpan.class);
        if (newSpan == null && continueSpan == null) {
            return invocation.proceed();
        }
        Span span = tracer().getCurrentSpan();
        String log = log(continueSpan);
        boolean hasLog = StringUtils.hasText(log);
        try {
            if (newSpan != null) {
                span = spanCreator().createSpan(invocation, newSpan);
            }
            if (hasLog) {
                logEvent(span, log + ".before");
            }
            spanTagAnnotationHandler().addAnnotatedParameters(invocation);
            addTags(invocation, span);
            return invocation.proceed();
        } catch (Exception e) {
            if (logger.isDebugEnabled()) {
                logger.debug("Exception occurred while trying to continue the pointcut", e);
            }
            if (hasLog) {
                logEvent(span, log + ".afterFailure");
            }
            errorParser().parseErrorTags(tracer().getCurrentSpan(), e);
            throw e;
        } finally {
            if (span != null) {
                if (hasLog) {
                    logEvent(span, log + ".after");
                }
                if (newSpan != null) {
                    tracer().close(span);
                }
            }
        }
    }
    //...
}

当通过代理走到此处的invoke()方法说明此时涉及到了与别的服务的调用,需要生成新的spanId,那么就在这里newSpan注解生成新的spanId,如果该方法实现了ContinueSpan注解,那么就在现有的spanId。
如果采用newSpan注解,那么这里需要通过之前的在配置类中生成的tracer的getCurrentSpan()方法获取当前的span。
具体的实现在SpanContextHolder的getCurrentSpan()方法中。

class SpanContextHolder {

    private static final Log log = org.apache.commons.logging.LogFactory
            .getLog(SpanContextHolder.class);
    private static final ThreadLocal<SpanContext> CURRENT_SPAN = new NamedThreadLocal<>(
            "Trace Context");

    /**
     * Get the current span out of the thread context
     */
    static Span getCurrentSpan() {
        return isTracing() ? CURRENT_SPAN.get().span : null;
    }

 

通过ThreadLoacl来获得当前的span,也就是说,当新的trace请求到来时,可以通过ThreadLoacl来存储。
紧接着通过spanCreator的createSpan()方法来证实获得新的span。

@Override public Span createSpan(MethodInvocation pjp, NewSpan newSpanAnnotation) {
   String name = StringUtils.isEmpty(newSpanAnnotation.name()) ?
         pjp.getMethod().getName() : newSpanAnnotation.name();
   String changedName = SpanNameUtil.toLowerHyphen(name);
   if (log.isDebugEnabled()) {
      log.debug("For the class [" + pjp.getThis().getClass() + "] method "
            + "[" + pjp.getMethod().getName() + "] will name the span [" + changedName + "]");
   }
   return createSpan(changedName);
}
 
private Span createSpan(String name) {
   if (this.tracer.isTracing()) {
      return this.tracer.createSpan(name, this.tracer.getCurrentSpan());
   }
   return this.tracer.createSpan(name);
}

 

Span的名字在注解中的名字和方法名中有限选择前者,而后根据通过tracer的createSpan()来获得span。

@Override
public Span createSpan(String name, Span parent) {
   if (parent == null) {
      return createSpan(name);
   }
   return continueSpan(createChild(parent, name));
}

 

如果此时没有任何span存在,那么直接通过createSpan().

@Override
public Span createSpan(String name) {
   return this.createSpan(name, this.defaultSampler);
}
 
@Override
public Span createSpan(String name, Sampler sampler) {
   String shortenedName = SpanNameUtil.shorten(name);
   Span span;
   if (isTracing()) {
      span = createChild(getCurrentSpan(), shortenedName);
   }
   else {
      long id = createId();
      span = Span.builder().name(shortenedName)
            .traceIdHigh(this.traceId128 ? createTraceIdHigh() : 0L)
            .traceId(id)
            .spanId(id).build();
      if (sampler == null) {
         sampler = this.defaultSampler;
      }
      span = sampledSpan(span, sampler);
      this.spanLogger.logStartedSpan(null, span);
   }
   return continueSpan(span);
}

 

这里可以看到spanId的构造,如果当时是首次构建spanId,那么首先会创建一个traceId,作为本次跟踪流 的id。并与第一次的spanID相同。

但是,此时若是已经存在span,也就是说这并不是第一次,那么就没有必要将traceId设为该次创建的spanId,而是在createChild()方法中,记录当前的traceId为原来收到的traceId,并将收到的spanId作为parentId,并将savedSpan指向原来的span,重新生成一个spanId,并将新的span作为当前的span。

在完成了span的创建后,则会经过sample的判断,此次是否要使用span记录,可以根据配置修改sample的类型,如果采用了百分比类型的,那么可能不会记录下来,完全复制一份span,但是把其exportable属性改为false。