Spring Cloud Sleuth 功能
1. 上下文传播
跟踪使用标头传播从一个服务连接到另一个服务。 默认格式为 B3。 与数据格式类似,您也可以配置备用标头格式,前提是跟踪和跨度 ID 与 B3 兼容。最值得注意的是,这意味着跟踪 ID 和跨度 ID 是小写十六进制,而不是 UUID。 除了跟踪标识符之外,还可以与请求一起传递其他属性(行李)。 远程行李必须是预定义的,但否则是灵活的。
要使用提供的默认值,您可以将spring.sleuth.propagation.type
财产。
该值可以是一个列表,在这种情况下,您将传播更多跟踪标头。
对于 Brave,我们支持AWS
,B3
,W3C
传播类型。
您可以在此“作方法部分”中阅读有关如何提供自定义上下文传播的更多信息。
2. 抽样
Spring Cloud Sleuth 将采样决策推送到跟踪器实现。 但是,在某些情况下,您可以在运行时更改采样决策。
其中一种情况是跳过某些客户端跨度的报告。
为此,您可以将spring.sleuth.web.client.skip-pattern
与要跳过的路径模式。
另一种选择是提供您自己的自定义org.springframework.cloud.sleuth.SamplerFunction<`org.springframework.cloud.sleuth.http.HttpRequest>
实现并定义当给定的HttpRequest
不应取样。
3. 行李
分布式跟踪的工作原理是在将跟踪连接在一起的服务内部和跨服务之间传播字段:尤其是 traceId 和 spanId。 保存这些字段的上下文可以选择推送其他需要保持一致的字段,而不管涉及许多服务。 这些额外字段的简单名称是“行李”。
Sleuth 允许您定义允许在跟踪上下文中存在哪些行李,包括使用哪些标头名称。
以下示例显示了使用 Spring Cloud Sleuth 的 API 设置行李值:
try (Tracer.SpanInScope ws = this.tracer.withSpan(initialSpan)) {
BaggageInScope businessProcess = this.tracer.createBaggage(BUSINESS_PROCESS).set("ALM");
BaggageInScope countryCode = this.tracer.createBaggage(COUNTRY_CODE).set("FO");
try {
目前对行李物品的数量或尺寸没有限制。 请记住,太多可能会降低系统吞吐量或增加 RPC 延迟。 在极端情况下,由于超过传输级消息或标头容量,过多的行李可能会使应用程序崩溃。 |
您可以使用属性来定义没有特殊配置的字段,例如名称映射:
-
spring.sleuth.baggage.remote-fields
是要接受并传播到远程服务的标头名称列表。 -
spring.sleuth.baggage.local-fields
是要在本地传播的名称列表
这些键不适用前缀。 您设置的内容实际上就是使用的内容。
在这些属性中设置的名称将导致Baggage
同名的。
为了自动将行李值设置为 Slf4j 的 MDC,您必须将spring.sleuth.baggage.correlation-fields
属性,其中包含允许的本地或远程密钥列表。例如spring.sleuth.baggage.correlation-fields=country-code
将设置country-code
行李进入 MDC。
请注意,从下一个下游跟踪上下文开始,额外字段将传播并添加到 MDC。 要立即将额外的字段添加到当前跟踪上下文中的 MDC,请将该字段配置为在更新时刷新:
// configuration
@Bean
BaggageField countryCodeField() {
return BaggageField.create("country-code");
}
@Bean
ScopeDecorator mdcScopeDecorator() {
return MDCScopeDecorator.newBuilder()
.clear()
.add(SingleCorrelationField.newBuilder(countryCodeField())
.flushOnUpdate()
.build())
.build();
}
// service
@Autowired
BaggageField countryCodeField;
countryCodeField.updateValue("new-value");
请记住,向 MDC 添加条目会大大降低应用程序的性能! |
如果要将行李条目添加为标签,以便能够通过行李条目搜索跨度,可以将spring.sleuth.baggage.tag-fields
并附有允许携带的行李钥匙清单。
要禁用该功能,您必须将spring.sleuth.propagation.tag.enabled=false
财产。
3.1. 行李与标签
与跟踪 ID 一样,Baggage 附加到消息或请求中,通常作为标头。 标签是在 Span 中发送到 Zipkin 的键值对。 默认情况下,行李值不会添加跨度,这意味着除非您选择加入,否则您无法根据行李进行搜索。
要使行李也标记,请使用spring.sleuth.baggage.tag-fields
这样:
spring:
sleuth:
baggage:
foo: bar
remoteFields:
- country-code
- x-vcap-request-id
tagFields:
- country-code
4. OpenZipkin Brave Tracer 集成
Spring Cloud Sleuth 通过spring-cloud-sleuth-brave
模块。
在本节中,您可以阅读有关特定 Brave 集成的信息。
您可以选择直接在代码中使用 Sleuth 的 API 或 Brave API(例如,Sleuth 的Tracer
或 Brave'sTracer
).
如果您想直接使用此跟踪器实现的 API,请阅读他们的文档以了解更多信息。
4.1. 勇敢的基础知识
以下是您可能使用的最核心类型:
-
brave.SpanCustomizer
- 更改当前正在进行的跨度 -
brave.Tracer
- 开始临时新跨度
以下是 OpenZipkin Brave 项目中最相关的链接:
4.2. 勇敢采样
采样仅适用于跟踪后端,例如 Zipkin。 无论采样率如何,跟踪 ID 都会显示在日志中。 采样是一种防止系统过载的方法,通过持续跟踪一些(但不是全部)请求。
每秒 10 条跟踪的默认速率由spring.sleuth.sampler.rate
属性,并适用于我们知道 Sleuth 用于日志记录以外的原因时。
使用每秒 100 条以上的跟踪速度要格外小心,因为它可能会使您的跟踪系统过载。
采样器也可以通过 Java Config 设置,如以下示例所示:
@Bean
public Sampler defaultSampler() {
return Sampler.ALWAYS_SAMPLE;
}
您可以设置 HTTP 标头b3 自1 ,或者,在进行消息传递时,您可以将spanFlags header 到1 .
这样做会强制对当前请求进行采样,而不管配置如何。 |
默认情况下,采样器将使用刷新作用域机制。
这意味着您可以在运行时更改采样属性,刷新应用程序,更改将被反映。
然而,有时围绕采样器创建代理并过早调用它的事实(从@PostConstruct
注释方法)可能会导致死锁。
在这种情况下,显式创建采样器 bean,或将属性spring.sleuth.sampler.refresh.enabled
自false
以禁用刷新范围支持。
4.3. Brave Baggage Java 配置
如果您需要执行比上述更高级的作,请不要定义属性,而是使用@Bean
配置。
-
BaggagePropagationCustomizer
设置行李字段 -
添加一个
SingleBaggageField
控制Baggage
. -
CorrelationScopeCustomizer
设置 MDC 字段 -
添加一个
SingleCorrelationField
更改Baggage
或者如果更新刷新。
4.4. 勇敢的自定义
这brave.Tracer
对象完全由 Sleuth 管理,因此您很少需要影响它。
也就是说,Sleuth 支持许多Customizer
类型,允许您使用自动配置或属性配置 Sleuth 尚未完成的任何作。
如果将以下项之一定义为Bean
,Sleuth 将调用它来自定义行为:
-
RpcTracingCustomizer
- 用于 RPC 标记和采样策略 -
HttpTracingCustomizer
- 用于 HTTP 标记和采样策略 -
MessagingTracingCustomizer
- 用于消息标记和采样策略 -
CurrentTraceContextCustomizer
- 集成装饰器,例如关联。 -
BaggagePropagationCustomizer
- 用于传播进程中和头上的行李字段 -
CorrelationScopeDecoratorCustomizer
- 用于示波器装饰,例如 MDC(日志记录)字段相关
4.4.1. Brave 采样自定义
如果需要客户端/服务器采样,只需注册一个类型为brave.sampler.SamplerFunction<HttpRequest>
并命名 beansleuthHttpClientSampler
用于客户端采样器和sleuthHttpServerSampler
用于服务器采样器。
为方便起见,该@HttpClientSampler
和@HttpServerSampler
注释可用于注入正确的 bean 或通过其静态字符串引用 bean 名称NAME
领域。
查看 Brave 的代码,了解如何制作基于路径的采样器 github.com/openzipkin/brave/tree/master/instrumentation/http#sampling-policy 示例
如果您想完全重写HttpTracing
bean 您可以使用SkipPatternProvider
接口来检索 URLPattern
对于不应采样的跨度。
下面你可以看到一个用法示例SkipPatternProvider
在服务器端,Sampler<HttpRequest>
.
@Configuration(proxyBeanMethods = false)
class Config {
@Bean(name = HttpServerSampler.NAME)
SamplerFunction<HttpRequest> myHttpSampler(SkipPatternProvider provider) {
Pattern pattern = provider.skipPattern();
return request -> {
String url = request.path();
boolean shouldSkip = pattern.matcher(url).matches();
if (shouldSkip) {
return false;
}
return null;
};
}
}
4.5. 勇敢的消息传递
Sleuth 会自动配置MessagingTracing
bean,它作为消息传递工具(如 Kafka 或 JMS)的基础。
如果需要自定义消息传递跟踪的生产者/消费者采样,只需注册一个类型为brave.sampler.SamplerFunction<MessagingRequest>
并命名 beansleuthProducerSampler
用于生产者采样器和sleuthConsumerSampler
用于消费者采样器。
为方便起见,该@ProducerSampler
和@ConsumerSampler
注释可用于注入正确的 bean 或通过其静态字符串引用 bean 名称NAME
领域。
前任。
这是一个采样器,每秒跟踪 100 个使用者请求,“警报”通道除外。
其他请求将使用Tracing
元件。
@Configuration(proxyBeanMethods = false)
class Config {
@Bean(name = ConsumerSampler.NAME)
SamplerFunction<MessagingRequest> myMessagingSampler() {
return MessagingRuleSampler.newBuilder().putRule(channelNameEquals("alerts"), Sampler.NEVER_SAMPLE)
.putRule(Matchers.alwaysMatch(), RateLimitingSampler.create(100)).build();
}
}
5. 向 Zipkin 发送 Spans
Spring Cloud Sleuth 提供了与 OpenZipkin 分布式跟踪系统的各种集成。
无论选择哪种示踪剂实现,只需添加spring-cloud-sleuth-zipkin
到类路径以开始向 Zipkin 发送 span。
您可以选择是通过 HTTP 还是消息传递来执行此作。
您可以在“作方法部分”中阅读有关如何执行此作的更多信息。
当 span 关闭时,它会通过 HTTP 发送到 Zipkin。通信是异步的。
您可以通过设置spring.zipkin.baseUrl
属性,如下所示:
spring.zipkin.baseUrl: https://192.168.99.100:9411/
如果要通过服务发现来查找 Zipkin,可以在 URL 内部传递 Zipkin 的服务 ID,如以下示例所示zipkinserver
服务 ID:
spring.zipkin.baseUrl: https://zipkinserver/
要禁用此功能,只需将spring.zipkin.discovery-client-enabled
自false
.
启用 Discovery Client 功能后,Sleuth 使用LoadBalancerClient
以查找 Zipkin 服务器的 URL。
这意味着您可以设置负载均衡配置。
如果您有web
,rabbit
,activemq
或kafka
在类路径上,您可能需要选择要将 span 发送到 Zipkin 的方法。
为此,请将web
,rabbit
,activemq
或kafka
到spring.zipkin.sender.type
财产。
以下示例显示了如何设置web
:
spring.zipkin.sender.type: web
要自定义RestTemplate
通过 HTTP 向 Zipkin 发送 span,您可以注册ZipkinRestTemplateCustomizer
豆。
@Configuration(proxyBeanMethods = false)
class MyConfig {
@Bean ZipkinRestTemplateCustomizer myCustomizer() {
return new ZipkinRestTemplateCustomizer() {
@Override
void customize(RestTemplate restTemplate) {
// customize the RestTemplate
}
};
}
}
但是,如果您想控制创建RestTemplate
对象,你必须创建一个zipkin2.reporter.Sender
类型。
@Bean Sender myRestTemplateSender(ZipkinProperties zipkin,
ZipkinRestTemplateCustomizer zipkinRestTemplateCustomizer) {
RestTemplate restTemplate = mySuperCustomRestTemplate();
zipkinRestTemplateCustomizer.customize(restTemplate);
return myCustomSender(zipkin, restTemplate);
}
默认情况下,api path 将设置为api/v2/spans
或api/v1/spans
取决于编码器版本。如果要使用自定义 API 路径,可以使用以下属性进行配置(空写,设置“”):
spring.zipkin.api-path: v2/path2
5.1. 自定义服务名称
默认情况下,Sleuth 假设当您向 Zipkin 发送 span 时,您希望该 span 的服务名称等于spring.application.name
财产。 但是,情况并非总是如此。在某些情况下,您希望为来自应用程序的所有跨度显式提供不同的服务名称。为此,您可以将以下属性传递给应用程序以覆盖该值(该示例适用于名为myService
):
spring.zipkin.service.name: myService
5.2. 主机定位器
本节是关于从服务发现定义主机的。它不是关于通过服务发现来查找 Zipkin。 |
要定义与特定跨度相对应的主机,我们需要解析主机名和端口。默认方法是从服务器属性中获取这些值。如果未设置这些值,我们尝试从网络接口中检索主机名。
如果您启用了发现客户端,并且希望从服务注册表中的已注册实例中检索主机地址,则必须将spring.zipkin.locator.discovery.enabled
属性(适用于基于 HTTP 和基于流的跨度报告),如下所示:
spring.zipkin.locator.discovery.enabled: true
5.3. 自定义报告的跨度
在 Sleuth 中,我们生成具有固定名称的跨度。一些用户希望根据标签的值修改名称。
侦探注册了一个SpanFilter
可以自动跳过给定名称模式的报告跨度的 bean。属性spring.sleuth.span-filter.span-name-patterns-to-skip
包含跨度名称的默认跳过模式。属性spring.sleuth.span-filter.additional-span-name-patterns-to-skip
将提供的跨度名称模式附加到现有模式。要禁用此功能,只需将spring.sleuth.span-filter.enabled
自false
.
5.3.1. Brave 自定义报告的跨度
本节仅适用于勇敢的示踪剂。 |
在报告跨度(例如,向 Zipkin)之前,您可能希望以某种方式修改该跨度。
您可以通过实现SpanHandler
.
以下示例显示如何注册两个实现SpanHandler
:
@Bean
SpanHandler handlerOne() {
return new SpanHandler() {
@Override
public boolean end(TraceContext traceContext, MutableSpan span, Cause cause) {
span.name("foo");
return true; // keep this span
}
};
}
@Bean
SpanHandler handlerTwo() {
return new SpanHandler() {
@Override
public boolean end(TraceContext traceContext, MutableSpan span, Cause cause) {
span.name(span.name() + " bar");
return true; // keep this span
}
};
}
前面的示例导致将报告的跨度的名称更改为foo bar
,就在报告之前(例如,向 Zipkin)。
5.4. 覆盖 Zipkin 的自动配置
Spring Cloud Sleuth 从版本 2.1.0 开始支持向多个跟踪系统发送跟踪。为了使其正常工作,每个跟踪系统都需要有一个Reporter<Span>
和Sender
.
如果要覆盖提供的 bean,则需要为它们指定一个特定的名称。
为此,您可以分别使用ZipkinAutoConfiguration.REPORTER_BEAN_NAME
和ZipkinAutoConfiguration.SENDER_BEAN_NAME
.
@Configuration(proxyBeanMethods = false)
protected static class MyConfig {
@Bean(ZipkinAutoConfiguration.REPORTER_BEAN_NAME)
Reporter<zipkin2.Span> myReporter(@Qualifier(ZipkinAutoConfiguration.SENDER_BEAN_NAME) MySender mySender) {
return AsyncReporter.create(mySender);
}
@Bean(ZipkinAutoConfiguration.SENDER_BEAN_NAME)
MySender mySender() {
return new MySender();
}
static class MySender extends Sender {
private boolean spanSent = false;
boolean isSpanSent() {
return this.spanSent;
}
@Override
public Encoding encoding() {
return Encoding.JSON;
}
@Override
public int messageMaxBytes() {
return Integer.MAX_VALUE;
}
@Override
public int messageSizeInBytes(List<byte[]> encodedSpans) {
return encoding().listSizeInBytes(encodedSpans);
}
@Override
public Call<Void> sendSpans(List<byte[]> encodedSpans) {
this.spanSent = true;
return Call.create(null);
}
}
}
6. 日志集成
Sleuth 使用变量配置日志记录上下文,包括服务名称 (%{spring.zipkin.service.name}
或%{spring.application.name}
如果未设置前一个),则跨度 ID (%{spanId}
) 和跟踪 ID (%{traceId}
).
这些可帮助您将日志与分布式跟踪连接起来,并允许您选择使用哪些工具来对服务进行故障排除。
找到任何有错误的日志后,可以在消息中查找跟踪 ID。 将其粘贴到分布式跟踪系统中以可视化整个跟踪,无论第一个请求最终命中了多少服务。
backend.log: 2020-04-09 17:45:40.516 ERROR [backend,5e8eeec48b08e26882aba313eb08f0a4,dcc1df555b5777b3] 97203 --- [nio-9000-exec-1] o.s.c.s.i.web.ExceptionLoggingFilter : Uncaught exception thrown
frontend.log:2020-04-09 17:45:40.574 ERROR [frontend,5e8eeec48b08e26882aba313eb08f0a4,82aba313eb08f0a4] 97192 --- [nio-8081-exec-2] o.s.c.s.i.web.ExceptionLoggingFilter : Uncaught exception thrown
在上面,您会注意到跟踪 ID 是5e8eeec48b08e26882aba313eb08f0a4
例如。
此日志配置由 Sleuth 自动设置。
您可以通过以下方式禁用 Sleuth 来禁用它spring.sleuth.enabled=false
财产或放置自己的logging.pattern.level
财产。

如果您想使用 Logstash,以下列表显示了 Logstash 的 Grok 模式:
filter {
# pattern matching logback pattern
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }
}
date {
match => ["timestamp", "ISO8601"]
}
mutate {
remove_field => ["timestamp"]
}
}
如果要将 Grok 与 Cloud Foundry 中的日志一起使用,则必须使用以下模式: |
filter {
# pattern matching logback pattern
grok {
match => { "message" => "(?m)OUT\s+%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }
}
date {
match => ["timestamp", "ISO8601"]
}
mutate {
remove_field => ["timestamp"]
}
}
6.1. JSON 使用 Logstash 进行登录
通常,您不希望将日志存储在文本文件中,而是存储在 Logstash 可以立即选择的 JSON 文件中。
为此,您必须执行以下作(为了可读性,我们将groupId:artifactId:version
符号)。
依赖项设置
-
确保 Logback 位于类路径 (
ch.qos.logback:logback-core
). -
添加 Logstash Logback 编码。 例如,要使用 version
4.6
加net.logstash.logback:logstash-logback-encoder:4.6
.
日志备份设置
请考虑以下 Logback 配置文件 (logback-spring.xml) 示例。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<springProperty scope="context" name="springAppName" source="spring.application.name"/>
<!-- Example for logging into the build folder of your project -->
<property name="LOG_FILE" value="${BUILD_FOLDER:-build}/${springAppName}"/>
<!-- You can override this to have a custom pattern -->
<property name="CONSOLE_LOG_PATTERN"
value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<!-- Appender to log to console -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- Minimum logging level to be presented in the console logs-->
<level>DEBUG</level>
</filter>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- Appender to log to file -->
<appender name="flatfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- Appender to log to file in a JSON format -->
<appender name="logstash" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}.json</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}.json.%d{yyyy-MM-dd}.gz</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>UTC</timeZone>
</timestamp>
<pattern>
<pattern>
{
"timestamp": "@timestamp",
"severity": "%level",
"service": "${springAppName:-}",
"trace": "%X{traceId:-}",
"span": "%X{spanId:-}",
"pid": "${PID:-}",
"thread": "%thread",
"class": "%logger{40}",
"rest": "%message"
}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="console"/>
<!-- uncomment this to have also JSON logs -->
<!--<appender-ref ref="logstash"/>-->
<!--<appender-ref ref="flatfile"/>-->
</root>
</configuration>
该 Logback 配置文件:
-
以 JSON 格式将应用程序中的信息记录到
build/${spring.application.name}.json
文件。 -
注释掉了两个额外的附加器:控制台和标准日志文件。
-
具有与上一节中介绍的日志记录模式相同的日志记录模式。
如果您使用自定义logback-spring.xml ,则必须通过spring.application.name 在bootstrap 而不是application 属性文件。
否则,您的自定义日志备份文件无法正确读取该属性。 |
7. 接下来要读什么
如果您对 Spring Cloud Sleuth 的核心功能感到满意,您可以继续阅读有关 Spring Cloud Sleuth 集成的信息。