在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。