Springboot propagators (#3506)

* set propagators in sdk builder

* add test for propagators

* introduce CompositeTextMapPropagator

* simplify composite TextMapPropagator creation, use SDK names for PropagationType

* Update instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationAutoConfiguration.java

Co-authored-by: Mateusz Rzeszutek <mrzeszutek@splunk.com>

* Update instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationAutoConfigurationTest.java

Co-authored-by: Mateusz Rzeszutek <mrzeszutek@splunk.com>

* replace enum with string, remove unnecessary noop propagator addition

* add warning and test for unsupported value

Co-authored-by: Mateusz Rzeszutek <mrzeszutek@splunk.com>
This commit is contained in:
Anna Nosek 2021-07-13 06:40:09 +02:00 committed by GitHub
parent ac85c3cbd8
commit e46ae82e94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 351 additions and 1 deletions

View File

@ -21,6 +21,8 @@ dependencies {
compileOnly("org.springframework.boot:spring-boot-starter-webflux:${versions["org.springframework.boot"]}")
compileOnly("io.opentelemetry:opentelemetry-extension-annotations")
compileOnly("io.opentelemetry:opentelemetry-extension-trace-propagators")
compileOnly("io.opentelemetry:opentelemetry-extension-aws")
compileOnly("io.opentelemetry:opentelemetry-exporter-logging")
compileOnly("io.opentelemetry:opentelemetry-exporter-jaeger")
compileOnly("io.opentelemetry:opentelemetry-exporter-otlp")
@ -39,6 +41,8 @@ dependencies {
testImplementation("io.opentelemetry:opentelemetry-sdk")
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
testImplementation("io.opentelemetry:opentelemetry-extension-annotations")
testImplementation("io.opentelemetry:opentelemetry-extension-trace-propagators")
testImplementation("io.opentelemetry:opentelemetry-extension-aws")
testImplementation("io.opentelemetry:opentelemetry-exporter-logging")
testImplementation("io.opentelemetry:opentelemetry-exporter-jaeger")
testImplementation("io.opentelemetry:opentelemetry-exporter-otlp")

View File

@ -7,6 +7,7 @@ package io.opentelemetry.instrumentation.spring.autoconfigure;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.TracerProvider;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
@ -36,6 +37,7 @@ public class OpenTelemetryAutoConfiguration {
@ConditionalOnMissingBean
public OpenTelemetry openTelemetry(
SamplerProperties samplerProperties,
ObjectProvider<ContextPropagators> propagatorsProvider,
ObjectProvider<List<SpanExporter>> spanExportersProvider) {
SdkTracerProviderBuilder tracerProviderBuilder = SdkTracerProvider.builder();
@ -48,6 +50,12 @@ public class OpenTelemetryAutoConfiguration {
tracerProviderBuilder
.setSampler(Sampler.traceIdRatioBased(samplerProperties.getProbability()))
.build();
return OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).buildAndRegisterGlobal();
ContextPropagators propagators = propagatorsProvider.getIfAvailable(ContextPropagators::noop);
return OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.setPropagators(propagators)
.buildAndRegisterGlobal();
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.propagators;
import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.extension.aws.AwsXrayPropagator;
import io.opentelemetry.extension.trace.propagation.B3Propagator;
import io.opentelemetry.extension.trace.propagation.JaegerPropagator;
import io.opentelemetry.extension.trace.propagation.OtTracePropagator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.util.ClassUtils;
/** Factory of composite {@link TextMapPropagator}. Defaults to W3C and BAGGAGE. */
public final class CompositeTextMapPropagatorFactory {
private static final Logger logger =
LoggerFactory.getLogger(CompositeTextMapPropagatorFactory.class);
static TextMapPropagator getCompositeTextMapPropagator(
BeanFactory beanFactory, List<String> types) {
Set<TextMapPropagator> propagators = new HashSet<>();
for (String type : types) {
switch (type) {
case "b3":
if (isOnClasspath("io.opentelemetry.extension.trace.propagation.B3Propagator")) {
propagators.add(
beanFactory
.getBeanProvider(B3Propagator.class)
.getIfAvailable(B3Propagator::injectingSingleHeader));
}
break;
case "b3multi":
if (isOnClasspath("io.opentelemetry.extension.trace.propagation.B3Propagator")) {
propagators.add(
beanFactory
.getBeanProvider(B3Propagator.class)
.getIfAvailable(B3Propagator::injectingMultiHeaders));
}
break;
case "jaeger":
if (isOnClasspath("io.opentelemetry.extension.trace.propagation.JaegerPropagator")) {
propagators.add(
beanFactory
.getBeanProvider(JaegerPropagator.class)
.getIfAvailable(JaegerPropagator::getInstance));
}
break;
case "ottrace":
if (isOnClasspath("io.opentelemetry.extension.trace.propagation.OtTracerPropagator")) {
propagators.add(
beanFactory
.getBeanProvider(OtTracePropagator.class)
.getIfAvailable(OtTracePropagator::getInstance));
}
break;
case "xray":
if (isOnClasspath("io.opentelemetry.extension.aws.AwsXrayPropagator")) {
propagators.add(
beanFactory
.getBeanProvider(AwsXrayPropagator.class)
.getIfAvailable(AwsXrayPropagator::getInstance));
}
break;
case "tracecontext":
propagators.add(W3CTraceContextPropagator.getInstance());
break;
case "baggage":
propagators.add(W3CBaggagePropagator.getInstance());
break;
default:
logger.warn("Unsupported type of propagator: {}", type);
break;
}
}
return TextMapPropagator.composite(propagators);
}
private static boolean isOnClasspath(String clazz) {
return ClassUtils.isPresent(clazz, null);
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.propagators;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/** Configures {@link ContextPropagators} bean for propagation. */
@Configuration
@EnableConfigurationProperties(PropagationProperties.class)
@AutoConfigureBefore(OpenTelemetryAutoConfiguration.class)
@ConditionalOnProperty(prefix = "otel.propagation", name = "enabled", matchIfMissing = true)
public class PropagationAutoConfiguration {
@Bean
@ConditionalOnMissingBean
ContextPropagators contextPropagators(ObjectProvider<List<TextMapPropagator>> propagators) {
List<TextMapPropagator> mapPropagators = propagators.getIfAvailable(Collections::emptyList);
if (mapPropagators.isEmpty()) {
return ContextPropagators.noop();
}
return ContextPropagators.create(TextMapPropagator.composite(mapPropagators));
}
@Configuration
static class PropagatorsConfiguration {
@Bean
TextMapPropagator compositeTextMapPropagator(
BeanFactory beanFactory, PropagationProperties properties) {
return CompositeTextMapPropagatorFactory.getCompositeTextMapPropagator(
beanFactory, properties.getType());
}
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.propagators;
import java.util.Arrays;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
/** Configuration for propagators. */
@ConfigurationProperties(prefix = "otel.propagation")
public final class PropagationProperties {
private List<String> type = Arrays.asList("tracecontext", "baggage");
public List<String> getType() {
return type;
}
public void setType(List<String> type) {
this.type = type;
}
}

View File

@ -3,6 +3,7 @@ io.opentelemetry.instrumentation.spring.autoconfigure.exporters.jaeger.JaegerSpa
io.opentelemetry.instrumentation.spring.autoconfigure.exporters.otlp.OtlpGrpcSpanExporterAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.exporters.zipkin.ZipkinSpanExporterAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.exporters.logging.LoggingSpanExporterAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.propagators.PropagationAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.resttemplate.RestTemplateAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.webclient.WebClientAutoConfiguration,\

View File

@ -0,0 +1,114 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.propagators;
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
class PropagationAutoConfigurationTest {
private final ApplicationContextRunner contextRunner =
new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(
OpenTelemetryAutoConfiguration.class, PropagationAutoConfiguration.class));
@AfterEach
void tearDown() {
GlobalOpenTelemetry.resetForTest();
}
@Test
@DisplayName("when propagation is ENABLED should initialize PropagationAutoConfiguration bean")
void shouldBeConfigured() {
this.contextRunner
.withPropertyValues("otel.propagation.enabled=true")
.run(context -> assertThat(context.containsBean("propagationAutoConfiguration")).isTrue());
}
@Test
@DisplayName(
"when propagation is DISABLED should NOT initialize PropagationAutoConfiguration bean")
void shouldNotBeConfigured() {
this.contextRunner
.withPropertyValues("otel.propagation.enabled=false")
.run(context -> assertThat(context.containsBean("propagationAutoConfiguration")).isFalse());
}
@Test
@DisplayName(
"when propagation enabled property is MISSING should initialize PropagationAutoConfiguration bean")
void noProperty() {
this.contextRunner.run(
context -> assertThat(context.containsBean("propagationAutoConfiguration")).isTrue());
}
@Test
@DisplayName("when no propagators are defined should contain default propagators")
void shouldContainDefaults() {
this.contextRunner.run(
context ->
assertThat(
context.getBean("compositeTextMapPropagator", TextMapPropagator.class).fields())
.contains("traceparent", "baggage"));
}
@Test
@DisplayName("when propagation is set to b3 should contain only b3 propagator")
void shouldContainB3() {
this.contextRunner
.withPropertyValues("otel.propagation.type=b3")
.run(
context -> {
TextMapPropagator compositePropagator =
context.getBean("compositeTextMapPropagator", TextMapPropagator.class);
assertThat(compositePropagator.fields())
.contains("b3")
.doesNotContain("baggage", "traceparent");
});
}
@Test
@DisplayName("when propagation is set to unsupported value should create an empty propagator")
void shouldCreateNoop() {
this.contextRunner
.withPropertyValues("otel.propagation.type=invalid")
.run(
context -> {
TextMapPropagator compositePropagator =
context.getBean("compositeTextMapPropagator", TextMapPropagator.class);
assertThat(compositePropagator.fields()).isEmpty();
});
}
@Test
@DisplayName("when propagation is set to some values should contain only supported values")
void shouldContainOnlySupported() {
this.contextRunner
.withPropertyValues("otel.propagation.type=invalid,b3")
.run(
context -> {
TextMapPropagator compositePropagator =
context.getBean("compositeTextMapPropagator", TextMapPropagator.class);
assertThat(compositePropagator.fields()).containsExactly("b3");
});
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.propagators;
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration;
import java.util.Arrays;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
public class PropagationPropertiesTest {
private final ApplicationContextRunner contextRunner =
new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(
OpenTelemetryAutoConfiguration.class, PropagationAutoConfiguration.class));
@AfterEach
void tearDown() {
GlobalOpenTelemetry.resetForTest();
}
@Test
@DisplayName("when propagation is SET should set PropagationProperties with given propagators")
void hasType() {
this.contextRunner
.withPropertyValues("otel.propagation.type=xray,b3")
.run(
context -> {
PropagationProperties propertiesBean = context.getBean(PropagationProperties.class);
assertThat(propertiesBean.getType()).isEqualTo(Arrays.asList("xray", "b3"));
});
}
@Test
@DisplayName("when propagation is DEFAULT should set PropagationProperties to default values")
void hasDefaultTypes() {
this.contextRunner.run(
context ->
assertThat(context.getBean(PropagationProperties.class).getType())
.containsExactly("tracecontext", "baggage"));
}
}