自定义

优质
小牛编辑
145浏览
2023-12-01

感谢SpanInjectorSpanExtractor,您可以自定义spans的创建和传播方式。

目前有两种在进程之间传递跟踪信息的内置方式:

  • 通过Spring Integration
  • 通过HTTP

Span ids从Zipkin兼容(B3)头(Message或HTTP头)中提取,以启动或加入现有跟踪。跟踪信息被注入到任何出站请求中,所以下一跳可以提取它们。

与以前版本的Sleuth相比,重要的变化是Sleuth正在实施Open Tracing的TextMap概念。在Sleuth,它被称为SpanTextMap。基本上这个想法是通过SpanTextMap可以抽象出任何通信手段(例如消息,http请求等)。这个抽象定义了如何将数据插入到载体中以及如何从那里检索数据。感谢这样,如果您想要使用一个使用FooRequest作为发送HTTP请求的平均值的新HTTP库,那么您必须创建一个SpanTextMap的实现,它将调用委托给FooRequest检索和插入HTTP标头。

Spring Integration

对于Spring Integration,有2个接口负责从Message创建Span。这些是:

  • MessagingSpanTextMapExtractor
  • MessagingSpanTextMapInjector

您可以通过提供自己的实现来覆盖它们。

HTTP

对于HTTP,有2个接口负责从Message创建Span。这些是:

  • HttpSpanExtractor
  • HttpSpanInjector

您可以通过提供自己的实现来覆盖它们。

我们假设,而不是标准的Zipkin兼容的跟踪HTTP头名称

  • for trace id - correlationId
  • for span id - mySpanId

这是SpanExtractor的一个例子

static class CustomHttpSpanExtractor implements HttpSpanExtractor {
	@Override public Span joinTrace(SpanTextMap carrier) {
		Map<String, String> map = TextMapUtil.asMap(carrier);
		long traceId = Span.hexToId(map.get("correlationid"));
		long spanId = Span.hexToId(map.get("myspanid"));
		// extract all necessary headers
		Span.SpanBuilder builder = Span.builder().traceId(traceId).spanId(spanId);
		// build rest of the Span
		return builder.build();
	}
}
static class CustomHttpSpanInjector implements HttpSpanInjector {
	@Override
	public void inject(Span span, SpanTextMap carrier) {
		carrier.put("correlationId", span.traceIdString());
		carrier.put("mySpanId", Span.idToHex(span.getSpanId()));
	}
}

你可以这样注册:

@Bean
HttpSpanInjector customHttpSpanInjector() {
	return new CustomHttpSpanInjector();
}
@Bean
HttpSpanExtractor customHttpSpanExtractor() {
	return new CustomHttpSpanExtractor();
}

Spring Cloud为了安全起见,Sleuth不会将跟踪/跨度相关的标头添加到Http响应。如果您需要标题,那么将标题注入Http响应的自定义SpanInjector,并且可以使用以下方式添加一个使用此标签的Servlet过滤器:

static class CustomHttpServletResponseSpanInjector extends ZipkinHttpSpanInjector {
	@Override
	public void inject(Span span, SpanTextMap carrier) {
		super.inject(span, carrier);
		carrier.put(Span.TRACE_ID_NAME, span.traceIdString());
		carrier.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
	}
}
static class HttpResponseInjectingTraceFilter extends GenericFilterBean {
	private final Tracer tracer;
	private final HttpSpanInjector spanInjector;
	public HttpResponseInjectingTraceFilter(Tracer tracer, HttpSpanInjector spanInjector) {
		this.tracer = tracer;
		this.spanInjector = spanInjector;
	}
	@Override
	public void doFilter(ServletRequest request, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
		HttpServletResponse response = (HttpServletResponse) servletResponse;
		Span currentSpan = this.tracer.getCurrentSpan();
		this.spanInjector.inject(currentSpan, new HttpServletResponseTextMap(response));
		filterChain.doFilter(request, response);
	}
	 class HttpServletResponseTextMap implements SpanTextMap {
		 private final HttpServletResponse delegate;
		 HttpServletResponseTextMap(HttpServletResponse delegate) {
			 this.delegate = delegate;
		 }
		 @Override
		 public Iterator<Map.Entry<String, String>> iterator() {
			 Map<String, String> map = new HashMap<>();
			 for (String header : this.delegate.getHeaderNames()) {
				map.put(header, this.delegate.getHeader(header));
			 }
			 return map.entrySet().iterator();
		 }
		 @Override
		 public void put(String key, String value) {
			this.delegate.addHeader(key, value);
		 }
	 }
}

你可以这样注册:

@Bean HttpSpanInjector customHttpServletResponseSpanInjector() {
	return new CustomHttpServletResponseSpanInjector();
}
@Bean
HttpResponseInjectingTraceFilter responseInjectingTraceFilter(Tracer tracer) {
	return new HttpResponseInjectingTraceFilter(tracer, customHttpServletResponseSpanInjector());
}

Zipkin中的自定义SA标签

有时你想创建一个手动Span,将一个电话包裹到一个没有被检测的外部服务。您可以做的是创建一个带有peer.service标签的跨度,其中包含要调用的服务的值。下面你可以看到一个调用Redis的例子,它被包装在这样一个跨度里。

org.springframework.cloud.sleuth.Span newSpan = tracer.createSpan("redis");
try {
	newSpan.tag("redis.op", "get");
	newSpan.tag("lc", "redis");
	newSpan.logEvent(org.springframework.cloud.sleuth.Span.CLIENT_SEND);
	// call redis service e.g
	// return (SomeObj) redisTemplate.opsForHash().get("MYHASH", someObjKey);
} finally {
	newSpan.tag("peer.service", "redisService");
	newSpan.tag("peer.ipv4", "1.2.3.4");
	newSpan.tag("peer.port", "1234");
	newSpan.logEvent(org.springframework.cloud.sleuth.Span.CLIENT_RECV);
	tracer.close(newSpan);
}
重要记住不要添加peer.service标签和SA标签!您只需添加peer.service

自定义服务名称

默认情况下,Sleuth假设当您将跨度发送到Zipkin时,您希望跨度的服务名称等于spring.application.name值。这并不总是这样。在某些情况下,您希望为您应用程序中的所有spans提供不同的服务名称。要实现这一点,只需将以下属性传递给应用程序即可覆盖该值(foo服务名称的示例):

spring.zipkin.service.name: foo

主机定位器

为了定义与特定跨度对应的主机,我们需要解析主机名和端口。默认方法是从服务器属性中获取它。如果由于某些原因没有设置,那么我们正在尝试从网络接口检索主机名。

如果您启用了发现客户端,并且更愿意从服务注册表中注册的实例检索主机地址,那么您必须设置属性(适用于基于HTTP和Stream的跨度报告)。

spring.zipkin.locator.discovery.enabled: true