Spring Cloud Sleuth 功能
1. 上下文传播
跟踪使用标头传播从服务连接到服务。 默认格式为 B3。 与数据格式类似,您也可以配置备用标头格式,前提是跟踪和跨度 ID 与 B3 兼容。最值得注意的是,这意味着跟踪 ID 和跨度 ID 是小写十六进制,而不是 UUID。 除了跟踪标识符之外,其他属性 (Baggage) 也可以随请求一起传递。 Remote Baggage 必须预定义,否则可以灵活。
要使用提供的默认值,您可以设置该属性。
该值可以是一个列表,在这种情况下,您将传播更多的跟踪标头。spring.sleuth.propagation.type
对于 Brave,我们支持 , , 传播类型。AWS
B3
W3C
您可以在此 “操作方法” 部分中阅读有关如何提供自定义上下文传播的更多信息。
2. 采样
Spring Cloud Sleuth 将采样决策下推到 tracer 实现。 但是,在某些情况下,您可以在运行时更改采样决策。
其中一种情况是跳过某些客户端 span 的报告。
为此,您可以设置要跳过的路径模式。
另一种选择是提供您自己的自定义实现并定义何时不应对给定的 Implementations 进行采样。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
为了自动将 baggage 值设置为 Slf4j 的 MDC,您必须使用允许的本地或远程键列表来设置该属性。例如 将 luggage 的值设置为 MDC 中。spring.sleuth.baggage.correlation-fields
spring.sleuth.baggage.correlation-fields=country-code
country-code
请注意,额外的字段将从下一个下游跟踪上下文开始传播并添加到 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 添加条目会大大降低应用程序的性能! |
如果要将行李条目添加为标签,以便可以通过行李条目搜索跨度,可以使用允许的行李键列表设置 的值。
要禁用该功能,您必须传递 property 。spring.sleuth.baggage.tag-fields
spring.sleuth.propagation.tag.enabled=false
3.1. 行李 vs 标签
与跟踪 ID 一样,Baggage 也附加到消息或请求中,通常作为标头。 标签是在 Span 中发送到 Zipkin 的键值对。 默认情况下,Baggage 值不会添加 span,这意味着除非您选择加入,否则无法根据 Baggage 进行搜索。
要使 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 通过模块中提供的桥与 OpenZipkin Brave 跟踪器集成。
在本节中,您可以了解特定的 Brave 集成。spring-cloud-sleuth-brave
您可以选择直接在代码中使用 Sleuth 的 API 或 Brave API(例如 Sleuth 的 API 或 Brave 的 API)。
如果您想直接使用此 tracer 实现的 API,请阅读其文档以了解更多信息。Tracer
Tracer
4.1. Brave 基础知识
以下是您可能使用的最核心类型:
-
brave.SpanCustomizer
- 更改当前正在进行的 Span -
brave.Tracer
- 要开始 new span ad-hoc
以下是 OpenZipkin Brave 项目中最相关的链接:
4.2. 勇敢采样
采样仅适用于跟踪后端,例如 Zipkin。 无论采样率如何,跟踪 ID 都会显示在日志中。 采样是一种防止系统过载的方法,它通过持续跟踪某些(但不是全部)请求。
每秒 10 条跟踪的默认速率由该属性控制,当我们知道 Sleuth 用于日志记录以外的原因时适用。
使用每秒 100 条跟踪以上的速率时要格外小心,因为它可能会使您的跟踪系统过载。spring.sleuth.sampler.rate
sampler 也可以通过 Java Config 设置,如以下示例所示:
@Bean
public Sampler defaultSampler() {
return Sampler.ALWAYS_SAMPLE;
}
您可以将 HTTP 标头设置为 ,或者在执行消息传递时,可以将标头设置为 。
这样做会强制对当前请求进行采样,而不管配置如何。b3 1 spanFlags 1 |
默认情况下,采样器将与刷新范围机制一起使用。
这意味着您可以在运行时更改采样属性,刷新应用程序,这些更改将反映出来。
但是,有时围绕采样器创建 proxy 并从太早(从 annotated method)调用它可能会导致死锁。
在这种情况下,要么显式创建一个采样器 bean,要么将属性设置为以禁用刷新范围支持。@PostConstruct
spring.sleuth.sampler.refresh.enabled
false
4.3. Brave Baggage Java 配置
如果您需要执行比上述更高级的操作,请不要定义 properties,而是使用您使用的 baggage 字段的配置。@Bean
-
BaggagePropagationCustomizer
设置 Baggage 字段 -
添加 a 以控制 .
SingleBaggageField
Baggage
-
CorrelationScopeCustomizer
设置 MDC 字段 -
添加 a 以更改 或 if updates flush 的 MDC 名称。
SingleCorrelationField
Baggage
4.4. Brave 自定义
该对象完全由 sleuth 管理,因此您很少需要影响它。
也就是说,Sleuth 支持多种类型,允许您使用自动配置或属性配置 Sleuth 尚未完成的任何操作。brave.Tracer
Customizer
如果您将以下内容之一定义为 ,Sleuth 将调用它来自定义行为:Bean
-
RpcTracingCustomizer
- 用于 RPC 标记和采样策略 -
HttpTracingCustomizer
- 用于 HTTP 标记和采样策略 -
MessagingTracingCustomizer
- 用于消息传送标记和采样策略 -
CurrentTraceContextCustomizer
- 集成装饰器,如 correlation。 -
BaggagePropagationCustomizer
- 用于在 process 和 over headers 中传播 baggage 字段 -
CorrelationScopeDecoratorCustomizer
- 用于范围修饰,例如 MDC(日志记录)字段关联
4.4.1. Brave 采样自定义
如果需要 Client 端/服务器采样,只需注册一个 Bean 类型,并为 Client 端采样器和服务器采样器命名该 Bean。brave.sampler.SamplerFunction<HttpRequest>
sleuthHttpClientSampler
sleuthHttpServerSampler
为方便起见,可以使用 and 注释来注入正确的 bean 或通过其静态 String 字段引用 bean 名称。@HttpClientSampler
@HttpServerSampler
NAME
查看 Brave 的代码,查看如何制作基于路径的采样器的示例 github.com/openzipkin/brave/tree/master/instrumentation/http#sampling-policy
如果要完全重写 Bean,可以使用该接口检索不应采样的 span 的 URL。
下面您可以看到服务器端内部的用法示例。HttpTracing
SkipPatternProvider
Pattern
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 会自动配置 Bean,该 Bean 用作 Kafka 或 JMS 等消息传递工具的基础。MessagingTracing
如果需要自定义消息传递跟踪的生产者/使用者采样,只需注册一个类型的 Bean,并为 producer sampler 和 consumer sampler 命名该 bean。brave.sampler.SamplerFunction<MessagingRequest>
sleuthProducerSampler
sleuthConsumerSampler
为方便起见,可以使用 and 注释来注入正确的 bean 或通过其静态 String 字段引用 bean 名称。@ProducerSampler
@ConsumerSampler
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();
}
}
4.6. 勇敢的 Opentracing
您可以通过桥接器与 Brave 和 OpenTracing 集成。
只需将其添加到 Classpath 中,OpenTracing 就会自动设置。io.opentracing.brave:brave-opentracing
Tracer
5. 将 Span 发送到 Zipkin
Spring Cloud Sleuth 提供了与 OpenZipkin 分布式跟踪系统的各种集成。
无论选择哪种 tracer 实现,只需添加到 Classpath 中即可开始向 Zipkin 发送 span。
您可以选择是通过 HTTP 还是消息传递来实现。
您可以在 “how to section” 中阅读更多关于如何做到这一点的信息。spring-cloud-sleuth-zipkin
当 span 关闭时,它会通过 HTTP 发送到 Zipkin。通信是异步的。
您可以通过设置属性来配置 URL,如下所示:spring.zipkin.baseUrl
spring.zipkin.baseUrl: https://192.168.99.100:9411/
如果要通过服务发现查找 Zipkin,可以在 URL 中传递 Zipkin 的服务 ID,如以下示例中的服务 ID 所示:zipkinserver
spring.zipkin.baseUrl: https://zipkinserver/
要禁用此功能,只需设置为 。spring.zipkin.discovery-client-enabled
false
启用 Discovery Client 功能后,Sleuth 用于查找 Zipkin 服务器的 URL。
这意味着您可以设置负载均衡配置。LoadBalancerClient
如果你在 Classpath 中有 , , 或 together,则可能需要选择要将 span 发送到 zipkin 的方法。
为此,请将 、 或 设置为 属性。
以下示例显示了设置发件人类型 :web
rabbit
activemq
kafka
web
rabbit
activemq
kafka
spring.zipkin.sender.type
web
spring.zipkin.sender.type: web
要自定义通过 HTTP 将 span 发送到 Zipkin,您可以注册 bean。RestTemplate
ZipkinRestTemplateCustomizer
@Configuration(proxyBeanMethods = false)
class MyConfig {
@Bean ZipkinRestTemplateCustomizer myCustomizer() {
return new ZipkinRestTemplateCustomizer() {
@Override
void customize(RestTemplate restTemplate) {
// customize the RestTemplate
}
};
}
}
但是,如果要控制创建对象的整个过程,则必须创建一个 type 为 的 bean。RestTemplate
zipkin2.reporter.Sender
@Bean Sender myRestTemplateSender(ZipkinProperties zipkin,
ZipkinRestTemplateCustomizer zipkinRestTemplateCustomizer) {
RestTemplate restTemplate = mySuperCustomRestTemplate();
zipkinRestTemplateCustomizer.customize(restTemplate);
return myCustomSender(zipkin, restTemplate);
}
默认情况下,api 路径将设置为或取决于编码器版本。如果要使用自定义 api 路径,可以使用以下属性(空大小写,设置 “”)进行配置:api/v2/spans
api/v1/spans
spring.zipkin.api-path: v2/path2
5.1. 自定义服务名称
默认情况下,Sleuth 假定当您将 span 发送到 Zipkin 时,您希望 span 的服务名称等于属性的值。
不过,情况并非总是如此。
在某些情况下,您希望为来自应用程序的所有 span 显式提供不同的服务名称。
为此,您可以将以下属性传递给应用程序以覆盖该值(该示例适用于名为 ):spring.application.name
myService
spring.zipkin.service.name: myService
5.2. 主机定位器
本节介绍如何从服务发现定义主机。 它不是通过服务发现来查找 Zipkin。 |
要定义与特定 span 对应的主机,我们需要解析主机名和端口。 默认方法是从 server 属性中获取这些值。 如果未设置,我们将尝试从网络接口中检索主机名。
如果您启用了发现客户端,并且希望从服务注册表中注册的实例中检索主机地址,则必须设置该属性(它适用于基于 HTTP 和基于 Stream 的跨度报告),如下所示:spring.zipkin.locator.discovery.enabled
spring.zipkin.locator.discovery.enabled: true
5.3. 自定义报告的 Span
在 Sleuth 中,我们生成具有固定名称的 span。 一些用户希望根据标签的值修改名称。
Sleuth 注册了一个 bean,该 bean 可以自动跳过给定名称模式的报告范围。
该属性包含 span 名称的默认跳过模式。
该属性会将提供的 span 名称模式附加到现有 span 名称模式。
要禁用此功能,只需设置为 。SpanFilter
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 自定义报告的 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 的名称更改为 , (例如,更改为 Zipkin)。foo bar
5.4. 覆盖 Zipkin 的自动配置
Spring Cloud Sleuth 从版本 2.1.0 开始支持将跟踪发送到多个跟踪系统。为了使其正常工作,每个跟踪系统都需要有一个 和 。
如果要覆盖提供的 bean,则需要为它们指定一个特定名称。
为此,您可以分别使用 和 。Reporter<Span>
Sender
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 使用变量配置日志记录上下文,这些变量包括服务名称(或者如果未设置前一个)、span ID () 和跟踪 ID ()。
这些工具可帮助您将日志与分布式跟踪连接起来,并允许您选择使用哪些工具对服务进行故障排除。%{spring.zipkin.service.name}
%{spring.application.name}
%{spanId}
%{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 为 。
此日志配置由 Sleuth 自动设置。
您可以通过 property 禁用 Sleuth 或放置您自己的 property 来禁用它。5e8eeec48b08e26882aba313eb08f0a4
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"]
}
}
使用 Logstash 6.1. JSON Logback
通常,您不希望将日志存储在文本文件中,而是存储在 Logstash 可以立即选择的 JSON 文件中。
为此,您必须执行以下操作(为了可读性,我们在 notation 中传递依赖项)。groupId:artifactId:version
依赖项设置
-
确保 Logback 位于 Classpath () 上。
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 文件将无法正确读取该属性。logback-spring.xml spring.application.name bootstrap application |
7. 下一步要读什么
如果您对 Spring Cloud Sleuth 的核心功能感到满意,则可以继续阅读 Spring Cloud Sleuth 的集成。