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容器初始化之前。

 

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()方法里保证了两件事情,设置了spring.aop.proxyTargetClass参数为true保证了cglib代理的开启,并加入了日志的追踪打印的模板。

 

而后在配置类TraceAutoConfiguration中生成了Tracer,默认实现为DefaultTraces,作用为正式创建一个工作单元span。

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

@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();
}
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();
}


@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()方法中。

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。

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值