3. 功能
-
将跟踪 ID 和跨度 ID 添加到 Slf4J MDC,因此您可以从给定跟踪或跨度中提取所有日志,如以下示例日志所示:
2016-02-02 15:30:57.902 INFO [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ... 2016-02-02 15:30:58.372 ERROR [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ... 2016-02-02 15:31:01.936 INFO [bar,46ab0d418373cbc9,46ab0d418373cbc9,false] 23030 --- [nio-8081-exec-4] ...
注意到MDC中的
[appname,traceId,spanId,exportable]条目:-
spanId: The ID of a specific operation that took place. -
appname: 应用程序名称,它记录了跨度。 -
traceId: The ID of the latency graph that contains the span. -
是否应将日志导出到Zipkin?何时不需要导出span?何时希望在Span中包装一些操作,仅将其写入日志。
-
-
提供对常见分布式跟踪数据模型的抽象: traces、跨度(形成 DAG)、注释和键值注释。 Spring Cloud Sleuth 受 HTrace 的启发,但与 ZipKin(Dapper) 兼容。
-
Sleuth记录用于帮助进行延迟分析的时间信息。 通过使用 Sleuth,您可以确定应用程序中延迟的来源。
-
Sleuth 旨在不会记录过多的日志,也不会导致您的生产应用程序崩溃。为了实现这个目标,Sleuth:
-
传播您的调用图的结构数据,通过带内传播,其余通过带外传播。
-
包含对层(如HTTP)的有倾向性的仪器仪表。
-
包含用于管理卷量的采样策略。
-
可向Zipkin系统报告,用于查询和可视化。
-
-
常见的入口和出口点从 Spring 应用程序( servlet 过滤器、异步端点、 rest 模板、计划操作、消息通道、 Zuul 过滤器和 Feign 客户端)。
-
Sleuth 包括默认逻辑,用于在 HTTP 或消息边界上连接一个链路追踪。 例如,HTTP 传播可以在与 Zipkin兼容的请求头上工作。
-
Sleuth 可以在进程之间传播上下文(也称为行李),因此,如果您在一个跨度上设置行李元素,它会通过 HTTP 或消息传递将其发送到下游其他进程。
-
提供了一种通过注释创建或继续跨度并添加标签和日志的方法。
-
如果
0 在类路径上,应用程序生成并收集与 Zipkin 兼容的跟踪。 默认情况下,它通过 HTTP 将它们发送到运行在本地主机(端口 9411)上的 Zipkin 服务器。 您可以通过设置1 配置服务的位置。-
如果你依赖
spring-rabbit,你的应用程序会将跟踪发送到RabbitMQ代理而不是HTTP。 -
如果依赖于
spring-kafka,并且设置spring.zipkin.sender.type: kafka,您的应用程序会将跟踪发送到Kafka代理而不是HTTP。
-
| 代码 0 已过时,应不再使用。 |
-
Spring Cloud Sleuth 是 OpenTracing 兼容的。
SLF4J 的 MDC 始终设置,Logback 用户会立即在日志中看到跟踪和跨度 ID,如前面所示的示例。其他日志系统必须配置其自己的格式化程序才能获得相同的结果。默认如下:logging.pattern.level 设置为 %5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}](这是 Spring Boot 中 Logback 用户的功能)。如果您不使用 SLF4J,则不会自动应用此模式。 |
3.1. 勇敢的介绍
从版本 2.0.0 开始,Spring Cloud Sleuth 使用Brave作为跟踪库。为了方便起见,我们将 Brave 文档的一部分嵌入此处。 |
在绝大多数情况下,您只需要使用Sleuth提供的Tracer或SpanCustomizer个Brave Bean。下面的文档包含了关于Brave是什么以及它是如何工作的高级概述。 |
Brave 是一个用于将分布式操作的延迟信息捕获并报告给 Zipkin 的库。<br/>大多数用户并不直接使用 Brave。他们更倾向于使用库或框架来代替自己使用 Brave。
此模块包含一个跟踪器,用于创建和连接跨度,这些跨度可以对可能分布式工作的延迟进行建模。 它还包括一些库,可以在网络边界上(例如使用HTTP头)传播跟踪上下文。
3.1.1. 跟踪
最重要的是,你需要一个brave.Tracer,配置为向Zipkin报告。
下面的示例设置通过HTTP(而非Kafka)将跟踪数据(跨度)发送到Zipkin:
class MyClass {
private final Tracer tracer;
// Tracer will be autowired
MyClass(Tracer tracer) {
this.tracer = tracer;
}
void doSth() {
Span span = tracer.newTrace().name("encode").start();
// ...
}
}
| \ 如果您的 span 包含的名称超过 50 个字符,则该名称将截断为 50 个字符。 您的名称必须是明确且具体的。 大名称会导致延迟问题,有时甚至会抛出异常。 \ |
追踪器创建并连接跨度,这些跨度对建模可能分布式的操作延迟。 它可以使用采样来减少过程中的开销,减少发送到Zipkin的数据量,或两者兼有。
<span>由跟踪器返回的跨度在完成时会向 Zipkin 报告数据,如果没有被采样则不执行任何操作。</span><br/><span>启动一个跨度后,您可以注解感兴趣事件或添加包含详细信息或查找键的标签。</span>
跨度具有包含跟踪标识符的上下文,这些标识符将跨度放置在表示分布式操作的树中的正确位置。
3.1.2. 本地追踪
在跟踪从不离开你的进程的代码时,请在作用域范围内运行它。
@Autowired Tracer tracer;
// Start a new trace or a span within an existing trace representing an operation
ScopedSpan span = tracer.startScopedSpan("encode");
try {
// The span is in "scope" meaning downstream code such as loggers can see trace IDs
return encoder.encode();
} catch (RuntimeException | Error e) {
span.error(e); // Unless you handle exceptions, you might not know the operation failed!
throw e;
} finally {
span.finish(); // always finish the span
}
当您需要更多功能或更精细的控制时,请使用Span类型:
@Autowired Tracer tracer;
// Start a new trace or a span within an existing trace representing an operation
Span span = tracer.nextSpan().name("encode").start();
// Put the span in "scope" so that downstream code such as loggers can see trace IDs
try (SpanInScope ws = tracer.withSpanInScope(span)) {
return encoder.encode();
} catch (RuntimeException | Error e) {
span.error(e); // Unless you handle exceptions, you might not know the operation failed!
throw e;
} finally {
span.finish(); // note the scope is independent of the span. Always finish a span.
}
上面两个示例在完成时报告了完全相同的跨度!
在上面的例子中,span 将是新的根 span 或现有跟踪中的下一个子 span。
3.1.3. 自定义跨度
获得一个span后,您可以向其中添加标签。 这些标签可以作为查找键或详细信息使用。 例如,您可以在运行时版本中添加一个标签,如下所示:
span.tag("clnt/finagle.version", "6.36.0");
在向第三方公开自定义跨度的能力时,建议使用brave.SpanCustomizer而不是brave.Span。
前者更容易理解和测试,并且不会用跨度生命周期钩子来诱惑用户。
interface MyTraceCallback {
void request(Request request, SpanCustomizer customizer);
}
由于brave.Span实现了brave.SpanCustomizer,因此可以将其传递给用户,如下面的示例所示:
for (MyTraceCallback callback : userCallbacks) {
callback.request(request, span);
}
3.1.4. 隐式查找当前跨度
有时,您不知道是否正在进行跟踪,也不想让用户执行空检查。brave.CurrentSpanCustomizer通过向任何正在运行的跨度添加数据或删除来解决此问题,如下面的示例所示:
Ex.
// The user code can then inject this without a chance of it being null.
@Autowired SpanCustomizer span;
void userCode() {
span.annotate("tx.started");
...
}
3.1.5. RPC 跟踪
RPC 跟踪通常由拦截器自动完成。在后台,它们会添加与他们在 RPC 操作中的角色相关的标签和事件。
以下示例显示了如何添加客户端跨度:
@Autowired Tracing tracing;
@Autowired Tracer tracer;
// before you send a request, add metadata that describes the operation
span = tracer.nextSpan().name(service + "/" + method).kind(CLIENT);
span.tag("myrpc.version", "1.0.0");
span.remoteServiceName("backend");
span.remoteIpAndPort("172.3.4.1", 8108);
// Add the trace context to the request, so it can be propagated in-band
tracing.propagation().injector(Request::addHeader)
.inject(span.context(), request);
// when the request is scheduled, start the span
span.start();
// if there is an error, tag the span
span.tag("error", error.getCode());
// or if there is an exception
span.error(exception);
// when the response is complete, finish the span
span.finish();
单向追踪
有时,您需要对没有响应的异步操作进行建模。在正常的RPC跟踪中,使用span.finish()表示已收到响应。在单向跟踪中,由于不期望有响应,因此使用span.flush()。
下面的例子显示了客户端如何建模单向操作:
@Autowired Tracing tracing;
@Autowired Tracer tracer;
// start a new span representing a client request
oneWaySend = tracer.nextSpan().name(service + "/" + method).kind(CLIENT);
// Add the trace context to the request, so it can be propagated in-band
tracing.propagation().injector(Request::addHeader)
.inject(oneWaySend.context(), request);
// fire off the request asynchronously, totally dropping any response
request.execute();
// start the client side and flush instead of finish
oneWaySend.start().flush();
下面的例子显示了服务器如何处理单向操作:
@Autowired Tracing tracing;
@Autowired Tracer tracer;
// pull the context out of the incoming request
extractor = tracing.propagation().extractor(Request::getHeader);
// convert that context to a span which you can name and add tags to
oneWayReceive = nextSpan(tracer, extractor.extract(request))
.name("process-request")
.kind(SERVER)
... add tags etc.
// start the server side and flush instead of finish
oneWayReceive.start().flush();
// you should not modify this span anymore as it is complete. However,
// you can create children to represent follow-up work.
next = tracer.newSpan(oneWayReceive.context()).name("step2").start();