spring cloud sleuth在spring中創建span


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM