Spring Cloud Sleuth 功能
1. 上下文传播
跟踪使用标头传播从服务连接到服务。 默认格式为 B3。 与数据格式类似,您也可以配置备用标头格式,前提是跟踪和跨度 ID 与 B3 兼容。最值得注意的是,这意味着跟踪 ID 和跨度 ID 是小写十六进制,而不是 UUID。 除了跟踪标识符之外,其他属性 (Baggage) 也可以随请求一起传递。 Remote Baggage 必须预定义,否则可以灵活。
要使用提供的默认值,您可以设置spring.sleuth.propagation.type
财产。
该值可以是一个列表,在这种情况下,您将传播更多的跟踪标头。
我们支持 BraveAWS
,B3
,W3C
propagation 类型。
您可以在此 “作方法” 部分中阅读有关如何提供自定义上下文传播的更多信息。
2. 采样
Spring Cloud Sleuth 将采样决策下推到 tracer 实现。 但是,在某些情况下,您可以在运行时更改采样决策。
其中一种情况是跳过某些客户端 span 的报告。
为此,您可以设置spring.sleuth.web.client.skip-pattern
替换为要跳过的路径模式。
另一种选择是提供您自己的自定义org.springframework.cloud.sleuth.SamplerFunction<`org.springframework.cloud.sleuth.http.HttpRequest>
实现并定义给定的HttpRequest
不应取样。
3. 行李
分布式跟踪的工作原理是将跟踪连接在一起的服务内部和之间传播字段:尤其是 traceId 和 spanId。 包含这些字段的上下文可以选择推送其他需要保持一致的字段,而不管涉及多少服务。 这些额外字段的简单名称是 “Baggage”。
Sleuth 允许您定义允许存在于跟踪上下文中的行李,包括使用哪些 Headers 名称。
以下示例显示了使用 Spring Cloud Sleuth 的 API 设置 Baggage 值:
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 延迟。 在极端情况下,过多的行李可能会由于超出传输级消息或标头容量而使应用程序崩溃。 |
您可以使用 properties 来定义没有特殊配置的字段,例如 name mapping:
-
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 中。 要立即将 extra 字段添加到当前跟踪上下文中的 MDC,请将该字段配置为 flush on update:
// 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 添加条目会大大降低应用程序的性能! |
如果要将 Baggage 条目添加为标签,以便可以通过 Baggage 条目搜索 span,则可以将spring.sleuth.baggage.tag-fields
以及允许的行李钥匙列表。
要禁用该功能,您必须将spring.sleuth.propagation.tag.enabled=false
财产。
3.1. 行李 vs 标签
与跟踪 ID 一样,Baggage 也附加到消息或请求中,通常作为标头。 标签是在 Span 中发送到 Zipkin 的键值对。 默认情况下,Baggage 值不会添加 span,这意味着除非您选择加入,否则无法根据 Baggage 进行搜索。
要使行李也成为标签,请使用该属性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 通过 Bridge中提供的桥与 OpenZipkin Brave 跟踪器集成spring-cloud-sleuth-brave
模块。
在本节中,您可以了解特定的 Brave 集成。
您可以选择直接在代码中使用 Sleuth 的 API 或 Brave API(例如 Sleuth 的Tracer
或 Brave 的Tracer
).
如果您想直接使用此 tracer 实现的 API,请阅读其文档以了解更多信息。
4.1. Brave 基础知识
以下是您可能使用的最核心类型:
-
brave.SpanCustomizer
- 更改当前正在进行的 Span -
brave.Tracer
- 要开始 new span ad-hoc
以下是 OpenZipkin Brave 项目中最相关的链接:
4.2. 勇敢采样
采样仅适用于跟踪后端,例如 Zipkin。 无论采样率如何,跟踪 ID 都会显示在日志中。 采样是一种防止系统过载的方法,它通过持续跟踪某些(但不是全部)请求。
默认速率为每秒 10 条跟踪,由spring.sleuth.sampler.rate
property 并在我们知道 Sleuth 用于日志记录以外的原因时适用。
使用每秒 100 条跟踪以上的速率时要格外小心,因为它可能会使您的跟踪系统过载。
sampler 也可以通过 Java Config 设置,如以下示例所示:
@Bean
public Sampler defaultSampler() {
return Sampler.ALWAYS_SAMPLE;
}
您可以设置 HTTP 标头b3 自1 ,或者在执行消息传递时,您可以设置spanFlags header 设置为1 .
这样做会强制对当前请求进行采样,而不管配置如何。 |
默认情况下,采样器将与刷新范围机制一起使用。
这意味着您可以在运行时更改采样属性,刷新应用程序,这些更改将反映出来。
但是,有时围绕采样器创建代理并从早期调用它(从@PostConstruct
annotated 方法)可能会导致死锁。
在这种情况下,要么显式创建一个采样器 Bean,要么将属性spring.sleuth.sampler.refresh.enabled
自false
以禁用刷新范围支持。
4.3. Brave Baggage Java 配置
如果您需要执行比上述更高级的作,请不要定义属性,而是使用@Bean
config 来获取您使用的 baggage 字段。
-
BaggagePropagationCustomizer
设置 Baggage 字段 -
添加
SingleBaggageField
要控制Baggage
. -
CorrelationScopeCustomizer
设置 MDC 字段 -
添加
SingleCorrelationField
更改Baggage
或者如果更新刷新。
4.4. Brave 自定义
这brave.Tracer
Object 完全由 Sleuth 管理,因此您很少需要影响它。
也就是说,Sleuth 支持许多Customizer
类型,允许您使用自动配置或属性配置 Sleuth 尚未完成的任何作。
如果您将以下选项之一定义为Bean
,Sleuth 将调用它来自定义行为:
-
RpcTracingCustomizer
- 用于 RPC 标记和采样策略 -
HttpTracingCustomizer
- 用于 HTTP 标记和采样策略 -
MessagingTracingCustomizer
- 用于消息传送标记和采样策略 -
CurrentTraceContextCustomizer
- 集成装饰器,如 correlation。 -
BaggagePropagationCustomizer
- 用于在 process 和 over headers 中传播 baggage 字段 -
CorrelationScopeDecoratorCustomizer
- 用于范围修饰,例如 MDC(日志记录)字段关联
4.4.1. Brave 采样自定义
如果需要 client /server 采样,只需注册一个brave.sampler.SamplerFunction<HttpRequest>
并命名 BeansleuthHttpClientSampler
for client sampler 和sleuthHttpServerSampler
对于 Server Sampler。
为了您的方便,@HttpClientSampler
和@HttpServerSampler
注释可用于注入正确的 bean 或通过其静态 String 引用 bean 名称NAME
领域。
查看 Brave 的代码,查看如何制作基于路径的采样器的示例 github.com/openzipkin/brave/tree/master/instrumentation/http#sampling-policy
如果要完全重写HttpTracing
bean 中,您可以使用SkipPatternProvider
接口检索 URLPattern
对于不应采样的 Span。
下面您可以看到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)的基础。
如果需要自定义消息传递跟踪的 producer / consumer 采样,只需注册一个 bean 类型的brave.sampler.SamplerFunction<MessagingRequest>
并命名 BeansleuthProducerSampler
对于 producer sampler 和sleuthConsumerSampler
对于消费者采样器。
为了您的方便,@ProducerSampler
和@ConsumerSampler
注释可用于注入正确的 bean 或通过其静态 String 引用 bean 名称NAME
领域。
前任。
这是一个采样器,每秒跟踪 100 个使用者请求,“alerts” 通道除外。
其他请求将使用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. 将 Span 发送到 Zipkin
Spring Cloud Sleuth 提供了与 OpenZipkin 分布式跟踪系统的各种集成。
无论选择哪种 tracer 实现,只需添加spring-cloud-sleuth-zipkin
添加到 classpath 中,开始向 Zipkin 发送 span。
您可以选择是通过 HTTP 还是消息传递来实现。
您可以在 “how to section” 中阅读更多关于如何做到这一点的信息。
当 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
在 Classpath 上,您可能需要选择要将 span 发送到 Zipkin 的方法。
为此,请将web
,rabbit
,activemq
或kafka
到spring.zipkin.sender.type
财产。
以下示例显示了如何设置web
:
spring.zipkin.sender.type: web
要自定义RestTemplate
通过 HTTP 将 span 发送到 Zipkin,则可以注册ZipkinRestTemplateCustomizer
豆。
@Configuration(proxyBeanMethods = false)
class MyConfig {
@Bean ZipkinRestTemplateCustomizer myCustomizer() {
return new ZipkinRestTemplateCustomizer() {
@Override
void customize(RestTemplate restTemplate) {
// customize the RestTemplate
}
};
}
}
但是,如果您想控制创建RestTemplate
Object 中,您必须创建一个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
财产。
不过,情况并非总是如此。
在某些情况下,您希望为来自应用程序的所有 span 显式提供不同的服务名称。
为此,您可以将以下属性传递给应用程序以覆盖该值(该示例适用于名为myService
):
spring.zipkin.service.name: myService
5.2. 主机定位器
本节介绍如何从服务发现定义主机。 它不是通过服务发现来查找 Zipkin。 |
要定义与特定 span 对应的主机,我们需要解析主机名和端口。 默认方法是从 server 属性中获取这些值。 如果未设置,我们将尝试从网络接口中检索主机名。
如果您启用了发现客户端,并且希望从服务注册表中注册的实例中检索主机地址,则必须将spring.zipkin.locator.discovery.enabled
属性(适用于基于 HTTP 和基于 Stream 的跨度报告),如下所示:
spring.zipkin.locator.discovery.enabled: true
5.3. 自定义报告的 Span
在 Sleuth 中,我们生成具有固定名称的 span。 一些用户希望根据标签的值修改名称。
侦探注册了一个SpanFilter
可以自动跳过给定名称模式的报告 span 的 bean。
物业spring.sleuth.span-filter.span-name-patterns-to-skip
包含 Span 名称的默认跳过模式。
物业spring.sleuth.span-filter.additional-span-name-patterns-to-skip
会将提供的 span 名称模式附加到现有 span 名称模式。
要禁用此功能,只需将spring.sleuth.span-filter.enabled
自false
.
5.3.1. Brave 自定义报告的 span
本部分仅适用于 Brave 跟踪器。 |
在报告 span (例如,向 Zipkin) 报告之前,您可能希望以某种方式修改该 span。
为此,您可以实现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
}
};
}
前面的示例导致将报告的 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
property 或放置自己的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"]
}
}
使用 Logstash 6.1. JSON Logback
通常,您不希望将日志存储在文本文件中,而是存储在 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 配置文件 (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
文件。 -
注释掉了两个额外的 appender:console 和 standard log file。
-
具有与上一节中介绍的日志记录模式相同的日志记录模式。
如果您使用自定义logback-spring.xml ,您必须将spring.application.name 在bootstrap 而不是application property 文件。
否则,您的自定义 logback 文件将无法正确读取该属性。 |
7. 下一步要读什么
如果您对 Spring Cloud Sleuth 的核心功能感到满意,则可以继续阅读 Spring Cloud Sleuth 的集成。