为了springboot更好的集成jaeger-client,我自定义了一个starter,并且集成了dubbo链路追踪的功能,现在分享给大家,为祖国的开源事业做贡献。
为什么我要自定义jaeger-client-starter?
市场是其实有现成的jaeger-client-starter,但是并没有集成dubbo的链路追踪,满足不了的项目的需求,还需要自己开发,但是我相信这类需求还是很大的,有必要集成一下dubbo,所以自定义了简单的starter,满足基本需求没有问题。
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-spring-jaeger-web-starter</artifactId>
<version>3.3.1</version>
</dependency>
废话不多说,直接上代码:
- 自定义了@EnableTrace的注解
- 在config中实现加载JaegerTracer
- 集成dubbo的链路追踪
@EnableTrace的注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import(JaegerClientConfig.class)
@Documented
public @interface EnableTrace {
}
JaegerClientConfig的实现
代码主要通过已agent方式来上报的,将代理的host和port设置到环境变量中,然后通过System.getenv("JAEGER_SERVICE_NAME")的方式获取变量
@Configuration
@ConditionalOnProperty(value = "nb.tracer", havingValue = "true", matchIfMissing = true)
@AutoConfigureBefore(io.opentracing.contrib.spring.tracer.configuration.TracerAutoConfiguration.class)
@Slf4j
public class JaegerClientConfig {
@Bean
public io.opentracing.Tracer tracer() {
//获取系统变量
log.info("load tracer env..");
String jaeger_service_name = System.getenv("JAEGER_SERVICE_NAME");
String jaeger_agent_host = System.getenv("JAEGER_AGENT_HOST");
if(jaeger_service_name != null && jaeger_agent_host != null){
Integer jaeger_agent_port = Integer.valueOf(System.getenv("JAEGER_AGENT_PORT"));
Integer jaeger_sampler_param = Integer.valueOf(System.getenv("JAEGER_SAMPLER_PARAM"));
log.info("load jaeger_service_name: {}", jaeger_service_name);
log.info("load jaeger_agent_host: {}", jaeger_agent_host);
log.info("load jaeger_agent_port: {}", jaeger_agent_port);
log.info("load jaeger_sampler_param:{}", jaeger_sampler_param);
log.info("tracer load successfully ....");
io.jaegertracing.Configuration config = new io.jaegertracing.Configuration(jaeger_service_name);
io.jaegertracing.Configuration.SenderConfiguration sender = new io.jaegertracing.Configuration.SenderConfiguration();
sender.withAgentHost(jaeger_agent_host);
sender.withAgentPort(jaeger_agent_port);
config.withSampler(new io.jaegertracing.Configuration.SamplerConfiguration().withType("const").withParam(jaeger_sampler_param));
config.withReporter(new io.jaegertracing.Configuration.ReporterConfiguration().withSender(sender).withMaxQueueSize(10000).withLogSpans(true));
B3TextMapCodec b3Codec = new B3TextMapCodec.Builder().build();
JaegerTracer tracer = config.getTracerBuilder().registerInjector(Format.Builtin.HTTP_HEADERS, b3Codec)
.registerExtractor(Format.Builtin.HTTP_HEADERS, b3Codec)
.registerInjector(Format.Builtin.TEXT_MAP, b3Codec)
.registerExtractor(Format.Builtin.TEXT_MAP, b3Codec)
.build();
GlobalTracer.registerIfAbsent(tracer);
return tracer;
}else{
log.info("tracer load fail ....");
return null;
}
}
}
dubbo相关的代码太多 贴主要代码
package com.nb.spring.jaeger.client.starter.jaegerclientstarter.dubbo;
import com.alibaba.dubbo.rpc.RpcContext;
import com.google.gson.Gson;
import io.opentracing.Span;
import io.opentracing.SpanContext;
import io.opentracing.Tracer;
import io.opentracing.propagation.Format;
import io.opentracing.propagation.TextMap;
import io.opentracing.propagation.TextMapAdapter;
import io.opentracing.tag.Tags;
import io.opentracing.util.GlobalTracer;
import lombok.extern.slf4j.Slf4j;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@Slf4j
public class DubboTraceUtil {
public static void printContextByHeader(RpcContext rpcContext, String header) {
log.info("ctx local on [{}], uber-trace-id = {}", header, rpcContext.get("uber-trace-id"));
log.info("ctx remote on [{}], uber-trace-id = {}", header, rpcContext.getAttachment("uber-trace-id"));
}
public static void printContextInfo(RpcContext rpcContext) {
log.info("ctx local on uber-trace-id = {}", rpcContext.get("uber-trace-id"));
log.info("ctx remote on uber-trace-id = {}", rpcContext.getAttachment("uber-trace-id"));
}
public static void attachTraceToRemoteCtx(Span span, final RpcContext rpcContext) {
Tracer tracer = GlobalTracer.get();
if (tracer != null) {
tracer.inject(span.context(), Format.Builtin.TEXT_MAP, new TextMap() {
@Override
public void put(String key, String value) {
rpcContext.setAttachment(key, value);
}
@Override
public Iterator<Map.Entry<String, String>> iterator() {
throw new UnsupportedOperationException("TextMapInjectAdapter should only be used with Tracer.inject()");
}
});
} else {
log.warn("failed to get tracer.");
}
}
public static Span extractTraceFromRemoteCtx(final RpcContext rpcContext) {
return extractTraceFromCtx(rpcContext, Boolean.FALSE);
}
public static void attachTraceToLocalCtx(Span span, final RpcContext rpcContext) {
Tracer tracer = GlobalTracer.get();
if (tracer != null) {
tracer.inject(span.context(), Format.Builtin.TEXT_MAP, new TextMap() {
@Override
public void put(String key, String value) {
rpcContext.set(key, value);
}
@Override
public Iterator<Map.Entry<String, String>> iterator() {
throw new UnsupportedOperationException("TextMapInjectAdapter should only be used with Tracer.inject()");
}
});
} else {
log.warn("failed to get tracer.");
}
}
public static Span extractTraceFromLocalCtx(final RpcContext rpcContext) {
return extractTraceFromCtx(rpcContext, Boolean.TRUE);
}
private static Span extractTraceFromCtx(final RpcContext rpcContext, Boolean isLocalCtx) {
Span span = null;
Tracer tracer = GlobalTracer.get();
if (tracer != null) {
log.info("serviceName = {}", rpcContext.getUrl().getServiceKey());
log.info("methodName = {}", rpcContext.getMethodName());
log.info("arguments = {}", new Gson().toJson(rpcContext.getArguments()));
String apiName = String.format("%s:%s", rpcContext.getUrl().getServiceKey(), rpcContext.getMethodName());
Tracer.SpanBuilder spanBuilder = tracer.buildSpan(apiName);
try {
TextMap textMap = null;
if (isLocalCtx) {
Map<String, Object> values = rpcContext.get();
Map<String, String> contexts = new HashMap<>();
for (Map.Entry<String, Object> value : values.entrySet()) {
contexts.put(value.getKey(), value.getValue().toString());
}
textMap = new TextMapAdapter(contexts);
} else {
textMap = new TextMapAdapter(rpcContext.getAttachments());
}
SpanContext spanContext = tracer.extract(Format.Builtin.TEXT_MAP, textMap);
if (spanContext != null) {
spanBuilder.asChildOf(spanContext);
}
} catch (Exception e) {
spanBuilder.withTag("Error", "extract from request fail, error msg:" + e.getMessage());
}
span = spanBuilder.start();
// dubbo方法调用的参数
String arguments = "";
try {
arguments = new Gson().toJson(rpcContext.getArguments());
} catch (Exception ignored) {
}
span.setTag("arguments", arguments);
if (rpcContext.isConsumerSide()) {
Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_CLIENT);
} else if (rpcContext.isProviderSide()) {
Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_SERVER);
}
// TODO record db trace
Tags.DB_INSTANCE.set(span, "db-instance");
InetSocketAddress remoteAddress = rpcContext.getRemoteAddress();
if (remoteAddress != null) {
Tags.PEER_PORT.set(span, remoteAddress.getPort());
Tags.PEER_HOSTNAME.set(span, remoteAddress.getHostName());
Tags.PEER_SERVICE.set(span, rpcContext.getUrl().getServiceKey());
InetAddress inetAddress = remoteAddress.getAddress();
if (inetAddress instanceof Inet4Address) {
Tags.PEER_HOST_IPV4.set(span, remoteAddress.getHostString());
} else if (inetAddress instanceof Inet6Address) {
Tags.PEER_HOST_IPV6.set(span, remoteAddress.getHostString());
} else {
Tags.PEER_HOST_IPV4.set(span, remoteAddress.getHostString());
}
}
} else {
log.warn("failed to get tracer.");
}
return span;
}
}
使用说明
- 导入自定义的starter
- 在主类Application中标注@EnableTrace
- 为了区分环境,我们可以通过配置来实现
nb.tracer=true //开启链路追踪
nb.tracer=false //关闭链路追踪,建议local环境下置为false
- 实现dubbo接口之间的trace,对生产者和消费者增加自定义filter
消费者:com.nb.spring.jaeger.client.starter.dubbo.RpcConsumerTraceFilter
生产者:com.nb.spring.jaeger.client.starter.dubbo.RpcProviderTraceFilter
5.增加过滤器
dubbo.provider.filter=RpcProviderTraceFilter
dubbo.consumer.filter=RpcConsumerTraceFilter
成功案例: