54. 自定义
感谢SpanInjector
和SpanExtractor
,您可以自定义 spans 的创建和传播方式。
目前有两种 built-in 方法在进程之间传递跟踪信息:
-
通过 Spring Integration
-
通过 HTTP
Span ID 从 Zipkin-compatible(B3)headers(Message
或 HTTP headers)中提取,以启动或加入现有跟踪。跟踪信息被注入到任何出站请求中,因此下一跳可以提取它们。
编码跟踪 context 的默认方式是通过包含traceId-spanId-sampled
表示法(e.g. 0000000000000005-0000000000000004-1
)的b3
标头完成的。为了向后兼容,如果b3
标头不存在,我们还检查是否存在X-B3
条目,并从那里检索跟踪 context e.g. (X-B3-TraceId: 0000000000000005
,X-B3-SpanId: 0000000000000004
,X-B3-Sampled: 1
)。
与之前版本的 Sleuth 相比,key 的变化是 Sleuth 正在实现 Open Tracing 的TextMap
概念。在 Sleuth 中,它被称为SpanTextMap
。基本上 idea 是任何通信方式(e.g. 消息,http 请求,etc.)都可以通过SpanTextMap
抽象出来。这个抽象定义了如何将数据插入到载体中以及如何从那里检索它。如果你想要的话,感谢这个要检测一个新的 HTTP library,它使用FooRequest
作为发送 HTTP 请求的意思,那么你必须创建一个SpanTextMap
的_imple 实现,它在检索和插入 HTTP headers 方面将 calls 委托给FooRequest
。
54.1 Spring Integration
对于 Spring Integration,有 2 个接口负责从Message
创建 Span。这些是:
-
MessagingSpanTextMapExtractor
-
MessagingSpanTextMapInjector
您可以通过提供自己的 implementation 来覆盖它们。
54.2 HTTP
对于 HTTP,有 2 个接口负责从Message
创建 Span。这些是:
-
HttpSpanExtractor
-
HttpSpanInjector
您可以通过提供自己的 implementation 来覆盖它们。
54.3 示例
让我们假设您拥有的标准 Zipkin 兼容跟踪 HTTP 标头名称
-
用于跟踪 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 出于安全原因,不会将与 trace/span 相关的 headers 添加到 Http 响应中。如果你需要 headers,那么将 headers 注入 Http Response 的自定义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());
}
54.4 TraceFilter
您还可以修改TraceFilter
的行为 - 负责处理输入 HTTP 请求的 component,并根据 HTTP 响应添加标记。您可以通过注册自己的TraceFilter
bean 实例来自定义标记或修改响应 headers。
在下面的 example 中,我们将注册TraceFilter
bean,我们将添加包含当前 Span 的跟踪 ID 的ZIPKIN-TRACE-ID
响应头。此外,我们将使用 key custom
和 value tag
添加 Span 标记。
@Bean
TraceFilter myTraceFilter(BeanFactory beanFactory, final Tracer tracer) {
return new TraceFilter(beanFactory) {
@Override protected void addResponseTags(HttpServletResponse response,
Throwable e) {
// execute the default behaviour
super.addResponseTags(response, e);
// for readability we're returning trace id in a hex form
response.addHeader("ZIPKIN-TRACE-ID",
Span.idToHex(tracer.getCurrentSpan().getTraceId()));
// we can also add some custom tags
tracer.addTag("custom", "tag");
}
};
}
要更改TraceFilter
注册的 order,请设置spring.sleuth.web.filter-order
property。
54.5 Zipkin 中的自定义 SA 标记
有时您想要创建一个手动 Span,它将包含对未检测的外部服务的调用。你可以做的是用peer.service
标签创建一个 span,它将包含你要调用的服务的 value。下面你可以看到一个包含在这样一个 span 中的 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
。
54.6 自定义服务 name
默认情况下 Sleuth 假设当您将 span 发送到 Zipkin 时,您希望 span 的服务 name 等于spring.application.name
value。但情况并非总是如此。在某些情况下,您希望为来自 application 的所有 spans 显式提供不同的服务 name。要实现这一点,只需将以下 property 传递给 application 即可覆盖该 value(example 为foo
service name):
spring.zipkin.service.name: foo
54.7 自定义报告的 spans
在报告 spans 到 e.g 之前。 Zipkin 你可能有兴趣以某种方式修改 span。您可以使用SpanAdjuster
接口实现此目的。
用法示例:
在 Sleuth 中,我们使用固定的 name 生成 spans。有些用户希望根据标签的值修改 name。 SpanAdjuster
接口的实现可用于更改 name。 例:
@Bean
SpanAdjuster customSpanAdjuster() {
return span -> span.toBuilder().name(scrub(span.getName())).build();
}
这将导致在将报告的 span 发送到 Zipkin 之前更改它的 name。
在实际报告完成之前,应该 inject
SpanAdjuster
并允许 span 操作。
54.8 Host 定位器
在 order 中定义与特定 span 对应的 host,我们需要解析 host name 和 port。默认方法是从服务器 properties 中获取它。如果由于某种原因没有设置那些,那么我们试图从网络接口检索 host name。
如果启用了发现 client 并且更喜欢从服务注册表中的已注册实例中检索 host 地址,则必须设置 property(它适用于基于 HTTP 和流的 span 报告)。
spring.zipkin.locator.discovery.enabled: true