Backports BaggagePropagation to 2.2.x (#1647)

This allows integrations of Sleuth to use the same approach for 2.2.x as 3.x:

If you have a custom base propagation format, override the `BaggagePropagation.Factory`
bean instead of `ExtraFieldsPropagation.Factory`

Note: one subtle difference 2.2.x to 3.x is the change in the primary inject format.

2.2.x is
```java
return BaggagePropagation.newFactoryBuilder(B3Propagation.newFactoryBuilder()
                         .injectFormat(B3Propagation.Format.MULTI).build());
```

3.0 is
```java
return BaggagePropagation.newFactoryBuilder(B3Propagation.newFactoryBuilder()
                         .injectFormat(B3Propagation.Format.SINGLE_NO_PARENT).build());
```
per #1607

See https://github.com/spring-cloud/spring-cloud-gcp/issues/2268
This commit is contained in:
Adrian Cole
2020-05-18 23:08:08 +08:00
committed by GitHub
parent 453be43b5a
commit e19e0ca169
6 changed files with 402 additions and 75 deletions

View File

@@ -25,11 +25,8 @@ import brave.Tracer;
import brave.Tracing;
import brave.TracingCustomizer;
import brave.handler.SpanHandler;
import brave.propagation.B3Propagation;
import brave.propagation.CurrentTraceContext;
import brave.propagation.CurrentTraceContextCustomizer;
import brave.propagation.ExtraFieldCustomizer;
import brave.propagation.ExtraFieldPropagation;
import brave.propagation.Propagation;
import brave.propagation.ThreadLocalCurrentTraceContext;
import brave.sampler.Sampler;
@@ -42,7 +39,6 @@ import zipkin2.reporter.Reporter;
import zipkin2.reporter.ReporterMetrics;
import zipkin2.reporter.metrics.micrometer.MicrometerReporterMetrics;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -73,7 +69,8 @@ import org.springframework.util.StringUtils;
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(value = "spring.sleuth.enabled", matchIfMissing = true)
@EnableConfigurationProperties(SleuthProperties.class)
@Import({ SleuthLogAutoConfiguration.class, SamplerAutoConfiguration.class })
@Import({ SleuthLogAutoConfiguration.class, TraceBaggageConfiguration.class,
SamplerAutoConfiguration.class })
// public allows @AutoConfigureAfter(TraceAutoConfiguration)
// for components needing Tracing
public class TraceAutoConfiguration {
@@ -88,9 +85,6 @@ public class TraceAutoConfiguration {
*/
public static final String DEFAULT_SERVICE_NAME = "default";
@Autowired(required = false)
ExtraFieldPropagation.FactoryBuilder extraFieldPropagationFactoryBuilder;
@Bean
@ConditionalOnMissingBean
// NOTE: stable bean name as might be used outside sleuth
@@ -138,51 +132,6 @@ public class TraceAutoConfiguration {
return new DefaultSpanNamer();
}
@Bean
@ConditionalOnMissingBean
Propagation.Factory sleuthPropagation(SleuthProperties sleuthProperties,
List<ExtraFieldCustomizer> extraFieldCustomizers) {
if (extraFieldCustomizers == null) {
extraFieldCustomizers = Collections.emptyList();
}
if (sleuthProperties.getBaggageKeys().isEmpty()
&& sleuthProperties.getPropagationKeys().isEmpty()
&& extraFieldCustomizers.isEmpty()
&& this.extraFieldPropagationFactoryBuilder == null
&& sleuthProperties.getLocalKeys().isEmpty()) {
return B3Propagation.FACTORY;
}
ExtraFieldPropagation.FactoryBuilder factoryBuilder;
if (this.extraFieldPropagationFactoryBuilder != null) {
factoryBuilder = this.extraFieldPropagationFactoryBuilder;
}
else {
factoryBuilder = ExtraFieldPropagation
.newFactoryBuilder(B3Propagation.FACTORY);
}
if (!sleuthProperties.getBaggageKeys().isEmpty()) {
factoryBuilder = factoryBuilder
// for HTTP
.addPrefixedFields("baggage-", sleuthProperties.getBaggageKeys())
// for messaging
.addPrefixedFields("baggage_", sleuthProperties.getBaggageKeys());
}
if (!sleuthProperties.getPropagationKeys().isEmpty()) {
for (String key : sleuthProperties.getPropagationKeys()) {
factoryBuilder = factoryBuilder.addField(key);
}
}
if (!sleuthProperties.getLocalKeys().isEmpty()) {
for (String key : sleuthProperties.getLocalKeys()) {
factoryBuilder = factoryBuilder.addRedactedField(key);
}
}
for (ExtraFieldCustomizer customizer : extraFieldCustomizers) {
customizer.customize(factoryBuilder);
}
return factoryBuilder.build();
}
@Bean
CurrentTraceContext sleuthCurrentTraceContext(CurrentTraceContext.Builder builder,
@Nullable List<CurrentTraceContext.ScopeDecorator> scopeDecorators,

View File

@@ -0,0 +1,180 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.autoconfig;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import brave.baggage.BaggageField;
import brave.baggage.BaggagePropagation;
import brave.baggage.BaggagePropagationConfig.SingleBaggageField;
import brave.baggage.BaggagePropagationCustomizer;
import brave.propagation.B3Propagation;
import brave.propagation.ExtraFieldCustomizer;
import brave.propagation.ExtraFieldPropagation;
import brave.propagation.Propagation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
/**
* {@link Configuration} for {@link BaggagePropagation}.
* <p>
*
* @author Spencer Gibb
* @author Marcin Grzejszczak
* @since 2.0.0
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SleuthProperties.class)
class TraceBaggageConfiguration {
static final Log logger = LogFactory.getLog(TraceBaggageConfiguration.class);
static final String LOCAL_KEYS = "spring.sleuth.local-keys";
static final String BAGGAGE_KEYS = "spring.sleuth.baggage-keys";
static final String PROPAGATION_KEYS = "spring.sleuth.propagation-keys";
// These List<String> beans allow us to get deprecated property values, regardless of
// if they were comma or yaml encoded. This keeps them out of SleuthBaggageProperties
@Bean(BAGGAGE_KEYS)
@ConfigurationProperties(BAGGAGE_KEYS)
List<String> baggageKeys() {
return new ArrayList<>();
}
@Bean(LOCAL_KEYS)
@ConfigurationProperties(LOCAL_KEYS)
List<String> localKeys() {
return new ArrayList<>();
}
@Bean(PROPAGATION_KEYS)
@ConfigurationProperties(PROPAGATION_KEYS)
List<String> propagationKeys() {
return new ArrayList<>();
}
/**
* To override the underlying context format, override this bean and set the delegate
* to what you need. {@link BaggagePropagation.FactoryBuilder} will unwrap itself if
* no fields are configured.
*/
@Bean
@ConditionalOnMissingBean
BaggagePropagation.FactoryBuilder baggagePropagationFactoryBuilder() {
// Default for spring-messaging is on 2.2.x is MULTI, though 3.x it is
// SINGLE_NO_PARENT spring-cloud/spring-cloud-sleuth#1607
return BaggagePropagation.newFactoryBuilder(B3Propagation.newFactoryBuilder()
.injectFormat(B3Propagation.Format.MULTI).build());
}
Propagation.Factory sleuthPropagation(
ExtraFieldPropagation.FactoryBuilder extraFieldPropagationFactoryBuilder,
List<String> baggageKeys, List<String> localKeys,
List<String> propagationKeys,
@Nullable List<ExtraFieldCustomizer> extraFieldCustomizers) {
if (extraFieldCustomizers == null) {
extraFieldCustomizers = Collections.emptyList();
}
ExtraFieldPropagation.FactoryBuilder factoryBuilder;
if (extraFieldPropagationFactoryBuilder != null) {
factoryBuilder = extraFieldPropagationFactoryBuilder;
}
else {
factoryBuilder = ExtraFieldPropagation
.newFactoryBuilder(B3Propagation.FACTORY);
}
if (!baggageKeys.isEmpty()) {
factoryBuilder
// for HTTP
.addPrefixedFields("baggage-", baggageKeys)
// for messaging
.addPrefixedFields("baggage_", baggageKeys);
}
for (String key : propagationKeys) {
factoryBuilder.addField(key);
}
for (String key : localKeys) {
factoryBuilder.addRedactedField(key);
}
for (ExtraFieldCustomizer customizer : extraFieldCustomizers) {
customizer.customize(factoryBuilder);
}
return factoryBuilder.build();
}
@Bean
@ConditionalOnMissingBean
Propagation.Factory sleuthPropagation(
@Nullable ExtraFieldPropagation.FactoryBuilder extraFieldPropagationFactoryBuilder,
@Nullable List<ExtraFieldCustomizer> extraFieldCustomizers,
BaggagePropagation.FactoryBuilder factoryBuilder,
@Qualifier(BAGGAGE_KEYS) List<String> baggageKeys,
@Qualifier(LOCAL_KEYS) List<String> localKeys,
@Qualifier(PROPAGATION_KEYS) List<String> propagationKeys,
@Nullable List<BaggagePropagationCustomizer> baggagePropagationCustomizers) {
boolean useDeprecated = false;
if (extraFieldPropagationFactoryBuilder != null) {
logger.warn("ExtraFieldPropagation.FactoryBuilder is deprecated. "
+ "Please switch to BaggagePropagation.FactoryBuilder");
useDeprecated = true;
}
if (extraFieldCustomizers != null) {
logger.warn("ExtraFieldCustomizer is deprecated. "
+ "Please switch to BaggagePropagationCustomizer");
useDeprecated = true;
}
if (useDeprecated) {
return sleuthPropagation(extraFieldPropagationFactoryBuilder, localKeys,
propagationKeys, baggageKeys, extraFieldCustomizers);
}
for (String fieldName : localKeys) {
factoryBuilder.add(SingleBaggageField.local(BaggageField.create(fieldName)));
}
for (String fieldName : propagationKeys) {
factoryBuilder.add(SingleBaggageField.remote(BaggageField.create(fieldName)));
}
for (String key : baggageKeys) {
factoryBuilder.add(SingleBaggageField.newBuilder(BaggageField.create(key))
.addKeyName("baggage-" + key) // for HTTP
.addKeyName("baggage_" + key) // for messaging
.build());
}
if (baggagePropagationCustomizers != null) {
for (BaggagePropagationCustomizer customizer : baggagePropagationCustomizers) {
customizer.customize(factoryBuilder);
}
}
return factoryBuilder.build();
}
}

View File

@@ -32,8 +32,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
public class SleuthTagPropagationProperties {
/**
* Enables a {@link TagPropagationSpanHandler} that adds extra propagated fields to
* span tags.
* Enables a {@link TagPropagationFinishedSpanHandler} that adds extra propagated
* fields to span tags.
*/
private boolean enabled = true;

View File

@@ -35,8 +35,7 @@ import org.springframework.context.support.GenericApplicationContext;
public class TraceAutoConfigurationPropagationCustomizationTests {
// Default for spring-messaging is on 2.2.x is MULTI, though 3.x it is
// SINGLE_NO_PARENT
// spring-cloud/spring-cloud-sleuth#1607
// SINGLE_NO_PARENT spring-cloud/spring-cloud-sleuth#1607
Propagation.Factory defaultB3Propagation = B3Propagation.newFactoryBuilder()
.injectFormat(Format.MULTI).build();
@@ -56,7 +55,7 @@ public class TraceAutoConfigurationPropagationCustomizationTests {
this.contextRunner.withPropertyValues("spring.sleuth.baggage-keys=my-baggage")
.run((context) -> {
BDDAssertions.then(context.getBean(Propagation.Factory.class))
.hasFieldOrPropertyWithValue("delegate.delegate",
.hasFieldOrPropertyWithValue("delegate",
B3Propagation.FACTORY);
});
}

View File

@@ -16,9 +16,19 @@
package org.springframework.cloud.sleuth.autoconfig;
import java.util.List;
import brave.Tracing;
import brave.baggage.BaggageField;
import brave.baggage.BaggagePropagation;
import brave.baggage.BaggagePropagationConfig.SingleBaggageField;
import brave.baggage.BaggagePropagationCustomizer;
import brave.propagation.B3Propagation;
import brave.propagation.B3SinglePropagation;
import brave.propagation.ExtraFieldPropagation;
import brave.propagation.Propagation;
import brave.propagation.TraceContext;
import brave.propagation.TraceContextOrSamplingFlags;
import brave.sampler.RateLimitingSampler;
import brave.sampler.Sampler;
import io.micrometer.core.instrument.MeterRegistry;
@@ -30,6 +40,7 @@ import zipkin2.reporter.Reporter;
import zipkin2.reporter.ReporterMetrics;
import zipkin2.reporter.metrics.micrometer.MicrometerReporterMetrics;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -119,17 +130,60 @@ public class TraceAutoConfigurationTests {
}));
}
@Test
public void should_use_baggageBean() {
this.contextRunner.withUserConfiguration(WithBaggageBeans.class, Baggage.class)
.run((context -> {
final Baggage bean = context.getBean(Baggage.class);
BDDAssertions.then(bean.fields).containsOnly(
BaggageField.create("country-code"),
BaggageField.create("x-vcap-request-id"));
}));
}
@Test
public void should_combine_baggage_beans_and_properties() {
this.contextRunner.withPropertyValues("spring.sleuth.local-keys=bp")
.withUserConfiguration(WithBaggageBeans.class, Baggage.class)
.run((context -> {
final Baggage bean = context.getBean(Baggage.class);
BDDAssertions.then(bean.fields).containsOnly(
BaggageField.create("country-code"),
BaggageField.create("x-vcap-request-id"),
BaggageField.create("bp"));
}));
}
@Test
public void should_use_baggagePropagationFactoryBuilder_bean() {
// BaggagePropagation.FactoryBuilder unwraps itself if there are no baggage fields
// defined
this.contextRunner
.withUserConfiguration(WithBaggagePropagationFactoryBuilderBean.class)
.run((context -> BDDAssertions
.then(context.getBean(Propagation.Factory.class))
.isSameAs(B3SinglePropagation.FACTORY)));
}
@Test
public void should_use_local_keys_from_properties() {
this.contextRunner.withUserConfiguration(WithLocalKeys.class).run((context -> {
final Propagation.Factory bean = context.getBean(Propagation.Factory.class);
BDDAssertions.then(bean).isInstanceOf(ExtraFieldPropagation.Factory.class);
}));
this.contextRunner.withPropertyValues("spring.sleuth.local-keys=bp")
.run((context -> {
final Propagation.Factory bean = context
.getBean(Propagation.Factory.class);
TraceContext ctx = bean.decorate(
TraceContext.newBuilder().traceId(1L).spanId(2L).build());
BaggageField bp = BaggageField.create("bp");
bp.updateValue(ctx, "accounting");
// If this works, it is configured!
BDDAssertions.then(bp.getValue(ctx)).isEqualTo("accounting");
}));
}
@Test
public void should_use_extraFieldPropagationFactoryBuilder_bean() {
this.contextRunner
this.contextRunner.withPropertyValues("spring.sleuth.local-keys=bp")
.withUserConfiguration(WithExtraFieldPropagationFactoryBuilderBean.class)
.run((context -> {
final Propagation.Factory bean = context
@@ -139,6 +193,63 @@ public class TraceAutoConfigurationTests {
}));
}
/**
* {@link BaggagePropagation.FactoryBuilder} is new: 2.2.x should prefer older on
* conflict.
*/
@Test
public void should_prefer_extraFieldPropagationFactoryBuilder_bean() {
this.contextRunner
.withUserConfiguration(WithExtraFieldPropagationFactoryBuilderBean.class)
.withUserConfiguration(WithBaggagePropagationFactoryBuilderBean.class)
.run((context -> BDDAssertions
.then(context.getBean(Propagation.Factory.class))
.isInstanceOf(ExtraFieldPropagation.Factory.class)));
}
@Configuration
static class Baggage {
List<BaggageField> fields;
@Autowired
Baggage(Tracing tracing) {
// When predefined baggage fields exist, the result !=
// TraceContextOrSamplingFlags.EMPTY
TraceContextOrSamplingFlags emptyExtraction = tracing.propagation()
.extractor((c, k) -> null).extract(Boolean.TRUE);
fields = BaggageField.getAll(emptyExtraction);
}
}
@Configuration
static class WithBaggageBeans {
@Bean
BaggagePropagationCustomizer countryCode() {
return fb -> fb
.add(SingleBaggageField.remote(BaggageField.create("country-code")));
}
@Bean
BaggagePropagationCustomizer requestId() {
return fb -> fb.add(
SingleBaggageField.remote(BaggageField.create("x-vcap-request-id")));
}
}
@Configuration
static class WithBaggagePropagationFactoryBuilderBean {
@Bean
BaggagePropagation.FactoryBuilder baggagePropagationFactoryBuilderBean() {
return BaggagePropagation.newFactoryBuilder(B3SinglePropagation.FACTORY);
}
}
@Configuration
static class WithMeterRegistry {
@@ -169,18 +280,6 @@ public class TraceAutoConfigurationTests {
}
@Configuration
static class WithLocalKeys {
@Bean
SleuthProperties sleuthProperties() {
final SleuthProperties sleuthProperties = new SleuthProperties();
sleuthProperties.getLocalKeys().add("test-key");
return sleuthProperties;
}
}
@Configuration
static class WithExtraFieldPropagationFactoryBuilderBean {

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.autoconfig;
import brave.baggage.BaggageField;
import brave.baggage.BaggagePropagationConfig.SingleBaggageField;
import brave.baggage.BaggagePropagationCustomizer;
import brave.propagation.Propagation;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.assertj.core.api.ListAssert;
import org.assertj.core.groups.Tuple;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
public class TraceBaggageConfigurationTests {
static final String[] EMPTY_ARRAY = {};
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(TraceBaggageConfiguration.class));
@Test
public void shouldCreateLocalFields() {
this.contextRunner.withPropertyValues("spring.sleuth.local-keys=bp")
.run((context) -> assertThatBaggageFieldNameToKeyNames(context)
.containsOnly(tuple("bp", EMPTY_ARRAY)));
}
static ListAssert<Tuple> assertThatBaggageFieldNameToKeyNames(
AssertableApplicationContext context) {
return assertThat(context.getBean(Propagation.Factory.class))
.extracting("configs").asInstanceOf(InstanceOfAssertFactories.ARRAY)
.extracting("field.name", "keyNames.toArray")
.asInstanceOf(InstanceOfAssertFactories.list(Tuple.class));
}
@Test
public void shouldCreateRemoteFields() {
this.contextRunner
.withPropertyValues(
"spring.sleuth.propagation-keys=x-vcap-request-id,country-code")
.run((context) -> assertThatBaggageFieldNameToKeyNames(context)
.containsOnly(
tuple("x-vcap-request-id",
new String[] { "x-vcap-request-id" }),
tuple("country-code", new String[] { "country-code" })));
}
@Test
public void shouldCreateBaggageFields() {
this.contextRunner.withPropertyValues("spring.sleuth.baggage-keys=country-code")
.run((context) -> assertThatBaggageFieldNameToKeyNames(context)
.containsOnly(tuple("country-code", new String[] {
"baggage-country-code", "baggage_country-code" })));
}
@Test
public void canCreateBaggageFieldsWithJavaConfig() {
this.contextRunner.withUserConfiguration(CustomBaggageConfiguration.class)
.run((context) -> assertThatBaggageFieldNameToKeyNames(context)
.containsOnly(tuple("country-code", new String[] {
"baggage-country-code", "baggage_country-code" })));
}
@Configuration
static class CustomBaggageConfiguration {
@Bean
BaggagePropagationCustomizer countryCodeBaggageConfig() {
return fb -> fb.add(
SingleBaggageField.newBuilder(BaggageField.create("country-code"))
.addKeyName("baggage-country-code")
.addKeyName("baggage_country-code").build());
}
}
}