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。