通过以下步骤可实现应用的traceId生成和打印,多个微服务都按照下面步骤配置后,通过RestTemplate
调用可传递traceId;使用okhttp调用需要自己实现拦截器才能传递,参见https://blog.csdn.net/eip777/article/details/109602373
项目地址:java-spring-jaeger
第一步:引入依赖
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-spring-jaeger-web-starter</artifactId>
<version>3.3.1</version>
</dependency>
第二步:配置一些参数,具体参数见项目文档
spring.application.name=demo1
opentracing.jaeger.enabled=true
opentracing.jaeger.log-spans=false
#opentracing.jaeger.enable128-bit-traces=true
第三步:增加配置类,官网地址:https://github.com/jaegertracing/jaeger-client-java/blob/master/jaeger-core/README.md
package com.example.demo1;
import io.jaegertracing.internal.JaegerTracer;
import io.jaegertracing.internal.MDCScopeManager;
import io.opentracing.contrib.java.spring.jaeger.starter.TracerBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MDCScopeManagerConfig {
/**
* 推荐此种方式配置,这样可以使用JaegerAutoConfiguration进行自动配置
*
* @return
*/
@Bean
public TracerBuilderCustomizer mdcBuilderCustomizer() {
// 1.8新特性,函数式接口
return builder -> builder.withScopeManager(new MDCScopeManager.Builder().build());
}
// /**
// * 这是官网示例配置,但是无法使用JaegerAutoConfiguration进行自动配置,个人不推荐
// */
// @Bean
// public JaegerTracer mdcBuilderCustomizer2() {
// MDCScopeManager scopeManager = new MDCScopeManager.Builder().build();
// JaegerTracer tracer = new JaegerTracer.Builder("demo1").withTraceId128Bit()
// .withScopeManager(scopeManager).build();
// return tracer;
// }
}
第四步:修改logback日志配置文件
参考官网给出的例子,重点是自定义参数traceId、spanId、sampled,当然这些参数也可以在new MDCScopeManager.Builder()的时候指定。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout
pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg% traceId=%X{traceId} spanId=%X{spanId} sampled=%X{sampled}%n" />
</Console>
</Appenders>
<Loggers>
<Root level="debug" additivity="false">
<AppenderRef ref="console" />
</Root>
</Loggers>
</Configuration>
这样打出来的日志就是这样:
[DEBUG] 2020-06-28 22:25:07.152 [main] LogExample - Your log message traceId=729b37ccf9c1549d spanId=729b37ccf9c1549d sampled=false
注:traceId如果设置为128bit,则类似这样:603817b36de37bf910a5e2ea0a3edfa1
总结:
MDCScopeManager主要作用就是MDC.put(key, value);这样logback打印日志的时候就可以从MDC中取出自定义值。
1、对于上面为什么第二种配置方式无法使用自动配置和第一种配置方式是如何生效的,可以看一下源码JaegerAutoConfiguration.class
主要代码有
@Configuration
@ConditionalOnClass({JaegerTracer.class})
@ConditionalOnMissingBean({Tracer.class}) //注意这个条件
@ConditionalOnProperty(
value = {"opentracing.jaeger.enabled"},
havingValue = "true",
matchIfMissing = true
)
@AutoConfigureBefore({TracerAutoConfiguration.class})
@EnableConfigurationProperties({JaegerConfigurationProperties.class})
public class JaegerAutoConfiguration {
@Autowired(
required = false
)
// 自动注入我们定义的bean
private List<TracerBuilderCustomizer> tracerCustomizers = Collections.emptyList();
public JaegerAutoConfiguration() {
}
@Bean
public Tracer tracer(Sampler sampler, Reporter reporter, Metrics metrics, JaegerConfigurationProperties properties) {
Builder builder = (new Builder(properties.getServiceName())).withReporter(reporter).withSampler(sampler).withTags(properties.determineTags()).withMetrics(metrics);
this.tracerCustomizers.forEach((c) -> {
c.customize(builder); // 实际执行“withScopeManager(new MDCScopeManager.Builder().build())”
});
return builder.build();
}
2、多个微服务间是如何传递traceId的?
系统收到http请求时,使用过滤器检查header是否包含uber-trace-id,不包含则创建,包含则使用。源码见
public class TracingFilter implements Filter {
........省略......
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest)servletRequest;
HttpServletResponse httpResponse = (HttpServletResponse)servletResponse;
if (!this.isTraced(httpRequest, httpResponse)) {
chain.doFilter(httpRequest, httpResponse);
} else {
if (servletRequest.getAttribute(SERVER_SPAN_CONTEXT) != null) {
chain.doFilter(servletRequest, servletResponse);
} else {
// 从header获取traceId。如果是请求源头系统,extractedContext==null,生成span
// 如果是从其他微服务调过来的,extractedContext有值
SpanContext extractedContext = this.tracer.extract(Builtin.HTTP_HEADERS, new HttpServletRequestExtractAdapter(httpRequest));
final Span span = this.tracer.buildSpan(httpRequest.getMethod()).asChildOf(extractedContext).withTag(Tags.SPAN_KIND.getKey(), "server").start();
httpRequest.setAttribute(SERVER_SPAN_CONTEXT, span.context());
Iterator var8 = this.spanDecorators.iterator();
while(var8.hasNext()) {
ServletFilterSpanDecorator spanDecorator = (ServletFilterSpanDecorator)var8.next();
spanDecorator.onRequest(httpRequest, span);
}
try {
// 放入MDC中
Scope scope = this.tracer.activateSpan(span);
........省略......
}
调用下游前如何将traceId存入http请求的header中呢?使用拦截器,源码见
public class TracingRestTemplateInterceptor implements ClientHttpRequestInterceptor {
.......省略......
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] body, ClientHttpRequestExecution execution) throws IOException {
Span span = this.tracer.buildSpan(httpRequest.getMethod().toString()).withTag(Tags.SPAN_KIND.getKey(), "client").start();
// 执行拦截器,将span内容放入header,底层为TextMapCodec#inject
this.tracer.inject(span.context(), Builtin.HTTP_HEADERS, new HttpHeadersCarrier(httpRequest.getHeaders()));
Iterator var6 = this.spanDecorators.iterator();
.......省略......
}
}