From 52d1436dc61ecdf3853f544afa3b8092c6c180f4 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 1 Sep 2022 17:17:43 +0200 Subject: [PATCH 1/2] Add support for MDC, Context Propagation (via B3 and W3C), and Baggage See gh-32480 --- .../tracing/BraveAutoConfiguration.java | 138 +++++++++++-- .../OpenTelemetryAutoConfiguration.java | 140 +++++++++++++- .../tracing/TracingProperties.java | 128 ++++++++++++ ...itional-spring-configuration-metadata.json | 4 + .../BaggageAutoConfigurationTests.java | 182 ++++++++++++++++++ .../tracing/BraveAutoConfigurationTests.java | 73 ++++++- .../OpenTelemetryAutoConfigurationTests.java | 100 +++++++++- 7 files changed, 744 insertions(+), 21 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BaggageAutoConfigurationTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java index 92c1269a74..ee702d6d1a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java @@ -22,6 +22,14 @@ import brave.Tracer; import brave.Tracing; import brave.Tracing.Builder; import brave.TracingCustomizer; +import brave.baggage.BaggageField; +import brave.baggage.BaggagePropagation; +import brave.baggage.BaggagePropagationConfig; +import brave.baggage.BaggagePropagationCustomizer; +import brave.baggage.CorrelationScopeConfig; +import brave.baggage.CorrelationScopeCustomizer; +import brave.baggage.CorrelationScopeDecorator; +import brave.context.slf4j.MDCScopeDecorator; import brave.handler.SpanHandler; import brave.http.HttpClientHandler; import brave.http.HttpClientRequest; @@ -34,6 +42,7 @@ import brave.propagation.B3Propagation; import brave.propagation.CurrentTraceContext; import brave.propagation.CurrentTraceContext.ScopeDecorator; import brave.propagation.CurrentTraceContextCustomizer; +import brave.propagation.Propagation; import brave.propagation.Propagation.Factory; import brave.propagation.ThreadLocalCurrentTraceContext; import brave.sampler.Sampler; @@ -41,20 +50,28 @@ import io.micrometer.tracing.brave.bridge.BraveBaggageManager; import io.micrometer.tracing.brave.bridge.BraveCurrentTraceContext; import io.micrometer.tracing.brave.bridge.BraveHttpClientHandler; import io.micrometer.tracing.brave.bridge.BraveHttpServerHandler; +import io.micrometer.tracing.brave.bridge.BravePropagator; import io.micrometer.tracing.brave.bridge.BraveTracer; +import io.micrometer.tracing.brave.bridge.W3CPropagation; +import org.slf4j.MDC; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 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; import org.springframework.core.env.Environment; /** * {@link EnableAutoConfiguration Auto-configuration} for Brave. * * @author Moritz Halbritter + * @author Marcin Grzejszczak * @since 3.0.0 */ @AutoConfiguration(before = MicrometerTracingAutoConfiguration.class) @@ -63,6 +80,8 @@ import org.springframework.core.env.Environment; @ConditionalOnEnabledTracing public class BraveAutoConfiguration { + private static final BraveBaggageManager BRAVE_BAGGAGE_MANAGER = new BraveBaggageManager(); + /** * Default value for application name if {@code spring.application.name} is not set. */ @@ -105,12 +124,6 @@ public class BraveAutoConfiguration { return builder.build(); } - @Bean - @ConditionalOnMissingBean - public Factory bravePropagationFactory() { - return B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.SINGLE_NO_PARENT).build(); - } - @Bean @ConditionalOnMissingBean public Sampler braveSampler(TracingProperties properties) { @@ -137,15 +150,14 @@ public class BraveAutoConfiguration { @Bean @ConditionalOnMissingBean - BraveTracer braveTracerBridge(brave.Tracer tracer, CurrentTraceContext currentTraceContext, - BraveBaggageManager braveBaggageManager) { - return new BraveTracer(tracer, new BraveCurrentTraceContext(currentTraceContext), braveBaggageManager); + BraveTracer braveTracerBridge(brave.Tracer tracer, CurrentTraceContext currentTraceContext) { + return new BraveTracer(tracer, new BraveCurrentTraceContext(currentTraceContext), BRAVE_BAGGAGE_MANAGER); } @Bean @ConditionalOnMissingBean - BraveBaggageManager braveBaggageManager() { - return new BraveBaggageManager(); + BravePropagator bravePropagator(Tracing tracing) { + return new BravePropagator(tracing); } @Bean @@ -162,4 +174,108 @@ public class BraveAutoConfiguration { return new BraveHttpClientHandler(httpClientHandler); } + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(value = "management.tracing.baggage.enabled", havingValue = "false", matchIfMissing = true) + static class BraveNoBaggageConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(value = "management.tracing.propagation.type", havingValue = "W3C", + matchIfMissing = true) + Factory w3cPropagationNoBaggageFactory() { + return new W3CPropagation(BRAVE_BAGGAGE_MANAGER, List.of()); // TODO: Use + // snapshots + // of + // tracing + // to not + // use + // baggage + // for W3C + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(value = "management.tracing.propagation.type", havingValue = "B3") + Factory b3PropagationNoBaggageFactory() { + return B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.SINGLE_NO_PARENT).build(); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(value = "management.tracing.baggage.enabled", matchIfMissing = true) + static class BraveBaggageConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(value = "management.tracing.propagation.type", havingValue = "W3C", + matchIfMissing = true) + BaggagePropagation.FactoryBuilder w3cPropagationFactory() { + return BaggagePropagation.newFactoryBuilder(new W3CPropagation(BRAVE_BAGGAGE_MANAGER, List.of())); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(value = "management.tracing.propagation.type", havingValue = "B3") + BaggagePropagation.FactoryBuilder b3PropagationFactory() { + return BaggagePropagation.newFactoryBuilder( + B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.SINGLE_NO_PARENT).build()); + } + + @Bean + @ConditionalOnMissingBean + Propagation.Factory micrometerTracingPropagationWithBaggage(BaggagePropagation.FactoryBuilder factoryBuilder, + TracingProperties tracingProperties, + ObjectProvider> baggagePropagationCustomizers) { + List remoteFields = tracingProperties.getBaggage().getRemoteFields(); + for (String fieldName : remoteFields) { + factoryBuilder.add(BaggagePropagationConfig.SingleBaggageField.remote(BaggageField.create(fieldName))); + } + baggagePropagationCustomizers.ifAvailable( + (customizers) -> customizers.forEach((customizer) -> customizer.customize(factoryBuilder))); + return factoryBuilder.build(); + } + + @Bean + @ConditionalOnMissingBean(CorrelationScopeDecorator.class) + @ConditionalOnBean(CorrelationScopeDecorator.Builder.class) + @ConditionalOnProperty(value = "management.tracing.baggage.correlation.enabled", matchIfMissing = true) + ScopeDecorator correlationFieldsCorrelationScopeDecorator(TracingProperties properties, + ObjectProvider> correlationScopeCustomizers, + CorrelationScopeDecorator.Builder builder) { + List correlationFields = properties.getBaggage().getCorrelation().getFields(); + for (String field : correlationFields) { + builder.add(CorrelationScopeConfig.SingleCorrelationField.newBuilder(BaggageField.create(field)) + .flushOnUpdate().build()); + } + correlationScopeCustomizers + .ifAvailable((customizers) -> customizers.forEach((customizer) -> customizer.customize(builder))); + return builder.build(); + } + + @Bean + @ConditionalOnMissingBean(CorrelationScopeDecorator.class) + @ConditionalOnBean(CorrelationScopeDecorator.Builder.class) + @ConditionalOnProperty(value = "management.tracing.baggage.correlation.enabled", havingValue = "false") + ScopeDecorator noCorrelationFieldsCorrelationScopeDecorator(CorrelationScopeDecorator.Builder builder, + ObjectProvider> correlationScopeCustomizers) { + correlationScopeCustomizers + .ifAvailable((customizers) -> customizers.forEach((customizer) -> customizer.customize(builder))); + return builder.build(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class CorrelationScopeDecoratorConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnClass(MDC.class) + CorrelationScopeDecorator.Builder mdcCorrelationScopeDecoratorBuilder() { + return MDCScopeDecorator.newBuilder(); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java index 74bc43a06a..55a17e86b9 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java @@ -23,17 +23,27 @@ import java.util.stream.Collectors; import io.micrometer.tracing.SamplerFunction; import io.micrometer.tracing.otel.bridge.DefaultHttpClientAttributesGetter; import io.micrometer.tracing.otel.bridge.DefaultHttpServerAttributesExtractor; +import io.micrometer.tracing.otel.bridge.EventListener; +import io.micrometer.tracing.otel.bridge.EventPublishingContextWrapper; import io.micrometer.tracing.otel.bridge.OtelBaggageManager; import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; import io.micrometer.tracing.otel.bridge.OtelHttpClientHandler; import io.micrometer.tracing.otel.bridge.OtelHttpServerHandler; +import io.micrometer.tracing.otel.bridge.OtelPropagator; import io.micrometer.tracing.otel.bridge.OtelTracer; import io.micrometer.tracing.otel.bridge.OtelTracer.EventPublisher; +import io.micrometer.tracing.otel.bridge.Slf4JBaggageEventListener; +import io.micrometer.tracing.otel.bridge.Slf4JEventListener; +import io.micrometer.tracing.otel.propagation.BaggageTextMapPropagator; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.ContextStorage; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.extension.trace.propagation.B3Propagator; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.SdkTracerProvider; @@ -43,14 +53,17 @@ import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; import io.opentelemetry.sdk.trace.samplers.Sampler; import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +import org.slf4j.MDC; import org.springframework.boot.SpringBootVersion; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 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; import org.springframework.core.env.Environment; /** @@ -103,7 +116,6 @@ public class OpenTelemetryAutoConfiguration { } @Bean - @ConditionalOnMissingBean SpanProcessor otelSpanProcessor(List spanExporter) { return SpanProcessor.composite(spanExporter.stream() .map((exporter) -> BatchSpanProcessor.builder(exporter).build()).collect(Collectors.toList())); @@ -118,21 +130,27 @@ public class OpenTelemetryAutoConfiguration { @Bean @ConditionalOnMissingBean OtelTracer micrometerOtelTracer(Tracer tracer, EventPublisher eventPublisher, - OtelCurrentTraceContext otelCurrentTraceContext) { + OtelCurrentTraceContext otelCurrentTraceContext, TracingProperties properties) { return new OtelTracer(tracer, otelCurrentTraceContext, eventPublisher, - new OtelBaggageManager(otelCurrentTraceContext, List.of(), List.of())); + new OtelBaggageManager(otelCurrentTraceContext, properties.getBaggage().getRemoteFields(), List.of())); } @Bean @ConditionalOnMissingBean - EventPublisher otelTracerEventPublisher() { - return (event) -> { - }; + OtelPropagator otelPropagator(ContextPropagators contextPropagators, Tracer tracer) { + return new OtelPropagator(contextPropagators, tracer); } @Bean @ConditionalOnMissingBean - OtelCurrentTraceContext otelCurrentTraceContext() { + EventPublisher otelTracerEventPublisher(List eventListeners) { + return new OTelEventPublisher(eventListeners); + } + + @Bean + @ConditionalOnMissingBean + OtelCurrentTraceContext otelCurrentTraceContext(EventPublisher publisher) { + ContextStorage.addWrapper(new EventPublishingContextWrapper(publisher)); return new OtelCurrentTraceContext(); } @@ -150,4 +168,112 @@ public class OpenTelemetryAutoConfiguration { new DefaultHttpServerAttributesExtractor()); } + @Configuration(proxyBeanMethods = false) + static class PropagationConfiguration { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(B3Propagator.class) + static class B3NoBaggagePropagatorConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(value = "management.tracing.propagation.type", havingValue = "B3") + B3Propagator b3TextMapPropagator() { + return B3Propagator.injectingSingleHeader(); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(value = "management.tracing.baggage.enabled", havingValue = "false") + static class W3CNoBaggagePropagatorConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(value = "management.tracing.propagation.type", havingValue = "W3C", + matchIfMissing = true) + W3CTraceContextPropagator w3cTextMapPropagatorWithoutBaggage() { + return W3CTraceContextPropagator.getInstance(); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(value = "management.tracing.baggage.enabled", matchIfMissing = true) + static class W3CBaggagePropagatorConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(value = "management.tracing.propagation.type", havingValue = "W3C", + matchIfMissing = true) + TextMapPropagator w3cTextMapPropagatorWithBaggage() { + return TextMapPropagator.composite(W3CTraceContextPropagator.getInstance(), + W3CBaggagePropagator.getInstance()); + } + + } + + } + + @Configuration(proxyBeanMethods = false) + static class MicrometerTracingPropagationConfiguration { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(value = "management.tracing.baggage.enabled", matchIfMissing = true) + static class BaggagePropagatorConfiguration { + + @Bean + @ConditionalOnProperty(value = "management.tracing.propagation.type", havingValue = "B3") + BaggageTextMapPropagator b3BaggageTextMapPropagator(TracingProperties properties, + OtelCurrentTraceContext otelCurrentTraceContext) { + return new BaggageTextMapPropagator(properties.getBaggage().getRemoteFields(), new OtelBaggageManager( + otelCurrentTraceContext, properties.getBaggage().getRemoteFields(), List.of())); + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(MDC.class) + static class Slf4jConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(value = "management.tracing.baggage.correlation.enabled", matchIfMissing = true) + Slf4JBaggageEventListener otelSlf4JBaggageEventListener(TracingProperties tracingProperties) { + return new Slf4JBaggageEventListener(tracingProperties.getBaggage().getCorrelation().getFields()); + } + + } + + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(MDC.class) + static class Slf4jConfiguration { + + @Bean + @ConditionalOnMissingBean + Slf4JEventListener otelSlf4JEventListener() { + return new Slf4JEventListener(); + } + + } + + static class OTelEventPublisher implements EventPublisher { + + private final List listeners; + + OTelEventPublisher(List listeners) { + this.listeners = listeners; + } + + @Override + public void publishEvent(Object event) { + for (EventListener listener : this.listeners) { + listener.onEvent(event); + } + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java index 56ce52a93a..51a801045e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java @@ -16,6 +16,9 @@ package org.springframework.boot.actuate.autoconfigure.tracing; +import java.util.ArrayList; +import java.util.List; + import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -32,10 +35,28 @@ public class TracingProperties { */ private final Sampling sampling = new Sampling(); + /** + * Baggage configuration. + */ + private final Baggage baggage = new Baggage(); + + /** + * Propagation configuration. + */ + private final Propagation propagation = new Propagation(); + public Sampling getSampling() { return this.sampling; } + public Baggage getBaggage() { + return this.baggage; + } + + public Propagation getPropagation() { + return this.propagation; + } + public static class Sampling { /** @@ -53,4 +74,111 @@ public class TracingProperties { } + public static class Baggage { + + /** + * Whether to enable Micrometer Tracing baggage propagation. + */ + private boolean enabled; + + /** + * Correlation configuration. + */ + private Correlation correlation = new Correlation(); + + /** + * List of fields that are referenced the same in-process as it is on the wire. + * For example, the field "x-vcap-request-id" would be set as-is including the + * prefix. + */ + private List remoteFields = new ArrayList<>(); + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Correlation getCorrelation() { + return this.correlation; + } + + public void setCorrelation(Correlation correlation) { + this.correlation = correlation; + } + + public List getRemoteFields() { + return this.remoteFields; + } + + public void setRemoteFields(List remoteFields) { + this.remoteFields = remoteFields; + } + + public static class Correlation { + + /** + * Whether to enable correlation of the baggage context with logging contexts. + */ + private boolean enabled = true; + + /** + * List of fields that should be correlated with the logging context. That + * means that these fields would end up as key-value pairs in e.g. MDC. + */ + private List fields = new ArrayList<>(); + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public List getFields() { + return this.fields; + } + + public void setFields(List fields) { + this.fields = fields; + } + + } + + } + + public static class Propagation { + + /** + * Tracing context propagation types. + */ + private PropagationType type = PropagationType.W3C; + + public PropagationType getType() { + return this.type; + } + + public void setType(PropagationType type) { + this.type = type; + } + + enum PropagationType { + + /** + * B3 propagation type. + */ + B3, + + /** + * W3C propagation type. + */ + W3C + + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index dfe63ad4b0..3af7c2ab55 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -2086,6 +2086,10 @@ "replacement": "management.trace.http.include", "level": "error" } + }, + { + "name": "management.tracing.propagation.type", + "defaultValue": "W3C" } ], "hints": [ diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BaggageAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BaggageAutoConfigurationTests.java new file mode 100644 index 0000000000..f104fad7d5 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BaggageAutoConfigurationTests.java @@ -0,0 +1,182 @@ +/* + * Copyright 2012-2022 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.boot.actuate.autoconfigure.tracing; + +import java.util.function.Supplier; + +import io.micrometer.tracing.BaggageInScope; +import io.micrometer.tracing.BaggageManager; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; +import io.opentelemetry.context.Context; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.slf4j.MDC; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.ApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for Baggage configuration. + * + * @author Marcin Grzejszczak + */ +class BaggageAutoConfigurationTests { + + static final String COUNTRY_CODE = "country-code"; + static final String BUSINESS_PROCESS = "bp"; + + @BeforeEach + @AfterEach + void setup() { + MDC.clear(); + } + + @ParameterizedTest + @EnumSource(AutoConfig.class) + void shouldSetEntriesToMdcFromSpanWithBaggage(AutoConfig autoConfig) { + autoConfig.get().run((context) -> { + Tracer tracer = tracer(context); + Span span = createSpan(tracer); + assertThatTracingContextIsInitialized(autoConfig); + try (Tracer.SpanInScope scope = tracer.withSpan(span.start()); + BaggageInScope fo = context.getBean(BaggageManager.class).createBaggage(COUNTRY_CODE) + .set(span.context(), "FO"); + BaggageInScope bp = context.getBean(BaggageManager.class).createBaggage(BUSINESS_PROCESS) + .set(span.context(), "ALM")) { + assertThat(MDC.get("traceId")).isEqualTo(span.context().traceId()); + assertThat(MDC.get(COUNTRY_CODE)).isEqualTo("FO"); + assertThat(MDC.get(BUSINESS_PROCESS)).isEqualTo("ALM"); + } + finally { + span.end(); + } + + assertThatMdcContainsUnsetTraceId(); + assertThat(MDC.get(COUNTRY_CODE)).isNull(); + assertThat(MDC.get(BUSINESS_PROCESS)).isNull(); + }); + } + + @ParameterizedTest + @EnumSource(AutoConfig.class) + void shouldRemoveEntriesFromMdcForNullSpan(AutoConfig autoConfig) { + autoConfig.get().run((context) -> { + Tracer tracer = tracer(context); + Span span = createSpan(tracer); + assertThatTracingContextIsInitialized(autoConfig); + try (Tracer.SpanInScope scope = tracer.withSpan(span.start()); + BaggageInScope fo = context.getBean(BaggageManager.class).createBaggage(COUNTRY_CODE) + .set(span.context(), "FO")) { + assertThat(MDC.get("traceId")).isEqualTo(span.context().traceId()); + assertThat(MDC.get(COUNTRY_CODE)).isEqualTo("FO"); + + try (Tracer.SpanInScope scope2 = tracer.withSpan(null)) { + assertThatMdcContainsUnsetTraceId(); + assertThat(MDC.get(COUNTRY_CODE)).isNullOrEmpty(); + } + + assertThat(MDC.get("traceId")).isEqualTo(span.context().traceId()); + assertThat(MDC.get(COUNTRY_CODE)).isEqualTo("FO"); + } + finally { + span.end(); + } + assertThatMdcContainsUnsetTraceId(); + assertThat(MDC.get(COUNTRY_CODE)).isNullOrEmpty(); + }); + } + + private Span createSpan(Tracer tracer) { + return tracer.nextSpan().name("span"); + } + + private Tracer tracer(ApplicationContext context) { + return context.getBean(Tracer.class); + } + + private void assertThatTracingContextIsInitialized(AutoConfig autoConfig) { + if (autoConfig == AutoConfig.OTEL_B3) { + assertThat(Context.current()).isEqualTo(Context.root()); + } + } + + private void assertThatMdcContainsUnsetTraceId() { + assertThat(isInvalidBraveTraceId() || isInvalidOtelTraceId()).isTrue(); + } + + private boolean isInvalidBraveTraceId() { + return MDC.get("traceId") == null; + } + + private boolean isInvalidOtelTraceId() { + return MDC.get("traceId").equals("00000000000000000000000000000000"); + } + + enum AutoConfig implements Supplier { + + BRAVE_W3C { + @Override + public ApplicationContextRunner get() { + return new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(BraveAutoConfiguration.class)).withPropertyValues( + "management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", + "management.tracing.baggage.correlation.fields=country-code,bp"); + } + }, + + OTEL_W3C { + @Override + public ApplicationContextRunner get() { + return new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + .withPropertyValues( + "management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", + "management.tracing.baggage.correlation.fields=country-code,bp"); + } + }, + + BRAVE_B3 { + @Override + public ApplicationContextRunner get() { + return new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(BraveAutoConfiguration.class)) + .withPropertyValues("management.tracing.propagation.type=B3", + "management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", + "management.tracing.baggage.correlation.fields=country-code,bp"); + } + }, + + OTEL_B3 { + @Override + public ApplicationContextRunner get() { + return new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + .withPropertyValues("management.tracing.propagation.type=B3", + "management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", + "management.tracing.baggage.correlation.fields=country-code,bp"); + } + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java index cf8d945709..baecb0bb3b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java @@ -18,6 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.tracing; import brave.Tracer; import brave.Tracing; +import brave.baggage.BaggagePropagation; import brave.http.HttpClientHandler; import brave.http.HttpClientRequest; import brave.http.HttpClientResponse; @@ -26,6 +27,7 @@ import brave.http.HttpServerRequest; import brave.http.HttpServerResponse; import brave.http.HttpTracing; import brave.propagation.CurrentTraceContext; +import brave.propagation.Propagation; import brave.propagation.Propagation.Factory; import brave.sampler.Sampler; import io.micrometer.tracing.brave.bridge.BraveBaggageManager; @@ -66,9 +68,10 @@ class BraveAutoConfigurationTests { assertThat(context).hasSingleBean(HttpServerHandler.class); assertThat(context).hasSingleBean(HttpClientHandler.class); assertThat(context).hasSingleBean(BraveTracer.class); - assertThat(context).hasSingleBean(BraveBaggageManager.class); assertThat(context).hasSingleBean(BraveHttpServerHandler.class); assertThat(context).hasSingleBean(BraveHttpClientHandler.class); + assertThat(context).hasSingleBean(Propagation.Factory.class); + assertThat(context).hasSingleBean(BaggagePropagation.FactoryBuilder.class); }); } @@ -99,10 +102,22 @@ class BraveAutoConfigurationTests { assertThat(context).hasSingleBean(BraveHttpServerHandler.class); assertThat(context).hasBean("customBraveHttpClientHandler"); assertThat(context).hasSingleBean(BraveHttpClientHandler.class); + assertThat(context).hasBean("customHttpServerHandler"); + assertThat(context).hasSingleBean(HttpServerHandler.class); + assertThat(context).hasBean("customHttpClientHandler"); + assertThat(context).hasSingleBean(HttpClientHandler.class); }); } @Test + void shouldSupplyMicrometerBeans() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(BraveTracer.class); + assertThat(context).hasSingleBean(BraveHttpServerHandler.class); + assertThat(context).hasSingleBean(BraveHttpClientHandler.class); + }); + } + void shouldNotSupplyBeansIfBraveIsMissing() { this.contextRunner.withClassLoader(new FilteredClassLoader("brave")) .run((context) -> assertThat(context).doesNotHaveBean(BraveAutoConfiguration.class)); @@ -114,12 +129,68 @@ class BraveAutoConfigurationTests { .run((context) -> assertThat(context).doesNotHaveBean(BraveAutoConfiguration.class)); } + @Test + void shouldSupplyW3CPropagationFactoryByDefault() { + this.contextRunner.run((context) -> { + assertThat(context).hasBean("w3cPropagationFactory"); + assertThat(context).hasSingleBean(BaggagePropagation.FactoryBuilder.class); + }); + } + + @Test + void shouldSupplyB3PropagationFactoryViaProperty() { + this.contextRunner.withPropertyValues("management.tracing.propagation.type=B3").run((context) -> { + assertThat(context).hasBean("b3PropagationFactory"); + assertThat(context).hasSingleBean(BaggagePropagation.FactoryBuilder.class); + }); + } + @Test void shouldNotSupplyBeansIfTracingIsDisabled() { this.contextRunner.withPropertyValues("management.tracing.enabled=false") .run((context) -> assertThat(context).doesNotHaveBean(BraveAutoConfiguration.class)); } + @Test + void shouldNotSupplyMdcCorrelationScopeWhenMdcNotOnClasspath() { + this.contextRunner.withClassLoader(new FilteredClassLoader("org.slf4j")).run((context) -> { + assertThat(context).doesNotHaveBean("mdcCorrelationScopeDecoratorBuilder"); + assertThat(context).doesNotHaveBean("correlationScopeDecorator"); + }); + } + + @Test + void shouldNotSupplyCorrelationScopeDecoratorIfBaggageDisabled() { + this.contextRunner.withPropertyValues("management.tracing.baggage.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean("correlationScopeDecorator")); + } + + @Test + void shouldSupplyW3CWithoutBaggageByDefaultIfBaggageDisabled() { + this.contextRunner.withPropertyValues("management.tracing.baggage.enabled=false") + .run((context) -> assertThat(context).hasBean("w3cPropagationNoBaggageFactory")); + } + + @Test + void shouldSupplyB3WithoutBaggageIfBaggageDisabledAndB3Picked() { + this.contextRunner + .withPropertyValues("management.tracing.baggage.enabled=false", + "management.tracing.propagation.type=B3") + .run((context) -> assertThat(context).hasBean("b3PropagationNoBaggageFactory")); + } + + @Test + void shouldNotSupplyCorrelationScopeDecoratorIfBaggageCorrelationDisabled() { + this.contextRunner.withPropertyValues("management.tracing.baggage.correlation.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean("correlationFieldsCorrelationScopeDecorator")); + } + + @Test + void shouldSupplyMdcCorrelationScopeDecoratorIfBaggageCorrelationDisabled() { + this.contextRunner.withPropertyValues("management.tracing.baggage.correlation.enabled=false") + .run((context) -> assertThat(context).hasBean("mdcCorrelationScopeDecoratorBuilder")); + } + @Configuration(proxyBeanMethods = false) private static class CustomConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java index 23440f28b1..1c50fb33ca 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java @@ -21,15 +21,21 @@ import io.micrometer.tracing.otel.bridge.OtelHttpClientHandler; import io.micrometer.tracing.otel.bridge.OtelHttpServerHandler; import io.micrometer.tracing.otel.bridge.OtelTracer; import io.micrometer.tracing.otel.bridge.OtelTracer.EventPublisher; +import io.micrometer.tracing.otel.bridge.Slf4JBaggageEventListener; +import io.micrometer.tracing.otel.bridge.Slf4JEventListener; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.extension.trace.propagation.B3Propagator; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SpanProcessor; import io.opentelemetry.sdk.trace.samplers.Sampler; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Answers; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.FilteredClassLoader; @@ -63,7 +69,6 @@ class OpenTelemetryAutoConfigurationTests { assertThat(context).hasSingleBean(SdkTracerProvider.class); assertThat(context).hasSingleBean(ContextPropagators.class); assertThat(context).hasSingleBean(Sampler.class); - assertThat(context).hasSingleBean(SpanProcessor.class); assertThat(context).hasSingleBean(Tracer.class); }); } @@ -108,12 +113,73 @@ class OpenTelemetryAutoConfigurationTests { assertThat(context).hasBean("customSampler"); assertThat(context).hasSingleBean(Sampler.class); assertThat(context).hasBean("customSpanProcessor"); - assertThat(context).hasSingleBean(SpanProcessor.class); assertThat(context).hasBean("customTracer"); assertThat(context).hasSingleBean(Tracer.class); }); } + @Test + void shouldSupplyBaggageAndSlf4jEventListenersWhenMdcOnClasspath() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(Slf4JEventListener.class); + assertThat(context).hasSingleBean(Slf4JBaggageEventListener.class); + }); + } + + @Test + void shouldSupplySlf4jEventListenersWhenMdcOnClasspathAndBaggageCorrelationDisabled() { + this.contextRunner.withPropertyValues("management.tracing.baggage.correlation.enabled=false").run((context) -> { + assertThat(context).hasSingleBean(Slf4JEventListener.class); + assertThat(context).doesNotHaveBean(Slf4JBaggageEventListener.class); + }); + } + + @Test + void shouldSupplySlf4jEventListenersWhenMdcOnClasspathAndBaggageDisabled() { + this.contextRunner.withPropertyValues("management.tracing.baggage.enabled=false").run((context) -> { + assertThat(context).hasSingleBean(Slf4JEventListener.class); + assertThat(context).doesNotHaveBean(Slf4JBaggageEventListener.class); + }); + } + + @Test + void shouldNotSupplySlf4jEventListenersWhenMdcNotOnClasspath() { + this.contextRunner.withClassLoader(new FilteredClassLoader("org.slf4j")).run((context) -> { + assertThat(context).doesNotHaveBean(Slf4JEventListener.class); + assertThat(context).doesNotHaveBean(Slf4JBaggageEventListener.class); + }); + } + + @Test + void shouldSupplyB3PropagationIfPropagationPropertySet() { + this.contextRunner.withPropertyValues("management.tracing.propagation.type=B3").run((context) -> { + assertThat(context).hasSingleBean(B3Propagator.class); + assertThat(context).hasBean("b3TextMapPropagator"); + assertThat(context).doesNotHaveBean(W3CTraceContextPropagator.class); + }); + } + + @Test + void shouldSupplyW3CPropagationWithBaggageByDefault() { + this.contextRunner.run((context) -> assertThat(context).hasBean("w3cTextMapPropagatorWithBaggage")); + } + + @Test + void shouldSupplyW3CPropagationWithoutBaggageWhenDisabled() { + this.contextRunner.withPropertyValues("management.tracing.baggage.enabled=false") + .run((context) -> assertThat(context).hasBean("w3cTextMapPropagatorWithoutBaggage")); + } + + @Test + void shouldSupplyB3PropagationWithoutBaggageWhenBaggageDisabledAndB3PropagationEnabled() { + this.contextRunner.withPropertyValues("management.tracing.baggage.enabled=false", + "management.tracing.propagation.type=B3").run((context) -> { + assertThat(context).hasBean("b3TextMapPropagator"); + assertThat(context).hasSingleBean(B3Propagator.class); + assertThat(context).doesNotHaveBean("w3cTextMapPropagatorWithoutBaggage"); + }); + } + @Configuration(proxyBeanMethods = false) private static class CustomConfiguration { @@ -174,4 +240,34 @@ class OpenTelemetryAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + private static class OpenTelemetryConfiguration { + + @Bean + OpenTelemetry openTelemetry() { + return mock(OpenTelemetry.class, Answers.RETURNS_MOCKS); + } + + } + + @Configuration(proxyBeanMethods = false) + private static class ContextPropagatorsConfiguration { + + @Bean + ContextPropagators contextPropagators() { + return mock(ContextPropagators.class, Answers.RETURNS_MOCKS); + } + + } + + @Configuration(proxyBeanMethods = false) + private static class CustomFactoryConfiguration { + + @Bean + TextMapPropagator customPropagationFactory() { + return mock(TextMapPropagator.class); + } + + } + } From 36a4b36ccbe92c361361d4f2337a8bdfd0938b0b Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 26 Sep 2022 17:27:27 +0100 Subject: [PATCH 2/2] Polish "Add support for MDC, Context Propagation (via B3 and W3C), and Baggage" See gh-32480 --- .../tracing/BraveAutoConfiguration.java | 137 +++++++-------- .../OpenTelemetryAutoConfiguration.java | 157 ++++++++---------- .../tracing/TracingProperties.java | 2 +- ...> BaggagePropagationIntegrationTests.java} | 5 +- .../tracing/BraveAutoConfigurationTests.java | 58 ++++--- .../OpenTelemetryAutoConfigurationTests.java | 88 +++++++--- 6 files changed, 235 insertions(+), 212 deletions(-) rename spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/{BaggageAutoConfigurationTests.java => BaggagePropagationIntegrationTests.java} (97%) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java index ee702d6d1a..670f7caf87 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java @@ -24,6 +24,7 @@ import brave.Tracing.Builder; import brave.TracingCustomizer; import brave.baggage.BaggageField; import brave.baggage.BaggagePropagation; +import brave.baggage.BaggagePropagation.FactoryBuilder; import brave.baggage.BaggagePropagationConfig; import brave.baggage.BaggagePropagationCustomizer; import brave.baggage.CorrelationScopeConfig; @@ -42,7 +43,6 @@ import brave.propagation.B3Propagation; import brave.propagation.CurrentTraceContext; import brave.propagation.CurrentTraceContext.ScopeDecorator; import brave.propagation.CurrentTraceContextCustomizer; -import brave.propagation.Propagation; import brave.propagation.Propagation.Factory; import brave.propagation.ThreadLocalCurrentTraceContext; import brave.sampler.Sampler; @@ -53,18 +53,17 @@ import io.micrometer.tracing.brave.bridge.BraveHttpServerHandler; import io.micrometer.tracing.brave.bridge.BravePropagator; import io.micrometer.tracing.brave.bridge.BraveTracer; import io.micrometer.tracing.brave.bridge.W3CPropagation; -import org.slf4j.MDC; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 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; +import org.springframework.core.annotation.Order; import org.springframework.core.env.Environment; /** @@ -175,29 +174,17 @@ public class BraveAutoConfiguration { } @Configuration(proxyBeanMethods = false) - @ConditionalOnProperty(value = "management.tracing.baggage.enabled", havingValue = "false", matchIfMissing = true) + @ConditionalOnProperty(value = "management.tracing.baggage.enabled", havingValue = "false") static class BraveNoBaggageConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(value = "management.tracing.propagation.type", havingValue = "W3C", - matchIfMissing = true) - Factory w3cPropagationNoBaggageFactory() { - return new W3CPropagation(BRAVE_BAGGAGE_MANAGER, List.of()); // TODO: Use - // snapshots - // of - // tracing - // to not - // use - // baggage - // for W3C - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(value = "management.tracing.propagation.type", havingValue = "B3") - Factory b3PropagationNoBaggageFactory() { - return B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.SINGLE_NO_PARENT).build(); + Factory propagationFactory(TracingProperties tracing) { + return switch (tracing.getPropagation().getType()) { + case B3 -> + B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.SINGLE_NO_PARENT).build(); + case W3C -> new W3CPropagation(BRAVE_BAGGAGE_MANAGER, List.of()); + }; } } @@ -206,74 +193,70 @@ public class BraveAutoConfiguration { @ConditionalOnProperty(value = "management.tracing.baggage.enabled", matchIfMissing = true) static class BraveBaggageConfiguration { - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(value = "management.tracing.propagation.type", havingValue = "W3C", - matchIfMissing = true) - BaggagePropagation.FactoryBuilder w3cPropagationFactory() { - return BaggagePropagation.newFactoryBuilder(new W3CPropagation(BRAVE_BAGGAGE_MANAGER, List.of())); + private final TracingProperties tracingProperties; + + BraveBaggageConfiguration(TracingProperties tracingProperties) { + this.tracingProperties = tracingProperties; } @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(value = "management.tracing.propagation.type", havingValue = "B3") - BaggagePropagation.FactoryBuilder b3PropagationFactory() { - return BaggagePropagation.newFactoryBuilder( - B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.SINGLE_NO_PARENT).build()); + BaggagePropagation.FactoryBuilder propagationFactoryBuilder( + ObjectProvider baggagePropagationCustomizers) { + Factory delegate = switch (this.tracingProperties.getPropagation().getType()) { + case B3 -> + B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.SINGLE_NO_PARENT).build(); + case W3C -> new W3CPropagation(BRAVE_BAGGAGE_MANAGER, List.of()); + }; + FactoryBuilder builder = BaggagePropagation.newFactoryBuilder(delegate); + baggagePropagationCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); + return builder; + } + + @Bean + @Order(0) + BaggagePropagationCustomizer remoteFieldsBaggagePropagationCustomizer() { + return (builder) -> { + List remoteFields = this.tracingProperties.getBaggage().getRemoteFields(); + for (String fieldName : remoteFields) { + builder.add(BaggagePropagationConfig.SingleBaggageField.remote(BaggageField.create(fieldName))); + } + }; } @Bean @ConditionalOnMissingBean - Propagation.Factory micrometerTracingPropagationWithBaggage(BaggagePropagation.FactoryBuilder factoryBuilder, - TracingProperties tracingProperties, - ObjectProvider> baggagePropagationCustomizers) { - List remoteFields = tracingProperties.getBaggage().getRemoteFields(); - for (String fieldName : remoteFields) { - factoryBuilder.add(BaggagePropagationConfig.SingleBaggageField.remote(BaggageField.create(fieldName))); - } - baggagePropagationCustomizers.ifAvailable( - (customizers) -> customizers.forEach((customizer) -> customizer.customize(factoryBuilder))); + Factory propagationFactory(BaggagePropagation.FactoryBuilder factoryBuilder) { return factoryBuilder.build(); } - @Bean - @ConditionalOnMissingBean(CorrelationScopeDecorator.class) - @ConditionalOnBean(CorrelationScopeDecorator.Builder.class) - @ConditionalOnProperty(value = "management.tracing.baggage.correlation.enabled", matchIfMissing = true) - ScopeDecorator correlationFieldsCorrelationScopeDecorator(TracingProperties properties, - ObjectProvider> correlationScopeCustomizers, - CorrelationScopeDecorator.Builder builder) { - List correlationFields = properties.getBaggage().getCorrelation().getFields(); - for (String field : correlationFields) { - builder.add(CorrelationScopeConfig.SingleCorrelationField.newBuilder(BaggageField.create(field)) - .flushOnUpdate().build()); - } - correlationScopeCustomizers - .ifAvailable((customizers) -> customizers.forEach((customizer) -> customizer.customize(builder))); - return builder.build(); - } - - @Bean - @ConditionalOnMissingBean(CorrelationScopeDecorator.class) - @ConditionalOnBean(CorrelationScopeDecorator.Builder.class) - @ConditionalOnProperty(value = "management.tracing.baggage.correlation.enabled", havingValue = "false") - ScopeDecorator noCorrelationFieldsCorrelationScopeDecorator(CorrelationScopeDecorator.Builder builder, - ObjectProvider> correlationScopeCustomizers) { - correlationScopeCustomizers - .ifAvailable((customizers) -> customizers.forEach((customizer) -> customizer.customize(builder))); - return builder.build(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class CorrelationScopeDecoratorConfiguration { - @Bean @ConditionalOnMissingBean - @ConditionalOnClass(MDC.class) - CorrelationScopeDecorator.Builder mdcCorrelationScopeDecoratorBuilder() { - return MDCScopeDecorator.newBuilder(); + CorrelationScopeDecorator.Builder mdcCorrelationScopeDecoratorBuilder( + ObjectProvider correlationScopeCustomizers) { + CorrelationScopeDecorator.Builder builder = MDCScopeDecorator.newBuilder(); + correlationScopeCustomizers.forEach((customizer) -> customizer.customize(builder)); + return builder; + } + + @Bean + @Order(0) + @ConditionalOnProperty(prefix = "management.tracing.baggage.correlation", name = "enabled", + matchIfMissing = true) + CorrelationScopeCustomizer correlationFieldsCorrelationScopeCustomizer() { + return (builder) -> { + List correlationFields = this.tracingProperties.getBaggage().getCorrelation().getFields(); + for (String field : correlationFields) { + builder.add(CorrelationScopeConfig.SingleCorrelationField.newBuilder(BaggageField.create(field)) + .flushOnUpdate().build()); + } + }; + } + + @Bean + @ConditionalOnMissingBean(CorrelationScopeDecorator.class) + ScopeDecorator correlationScopeDecorator(CorrelationScopeDecorator.Builder builder) { + return builder.build(); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java index 55a17e86b9..c5efef0e6c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java @@ -53,8 +53,8 @@ import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; import io.opentelemetry.sdk.trace.samplers.Sampler; import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; -import org.slf4j.MDC; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.SpringBootVersion; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -83,6 +83,12 @@ public class OpenTelemetryAutoConfiguration { */ private static final String DEFAULT_APPLICATION_NAME = "application"; + private final TracingProperties tracingProperties; + + OpenTelemetryAutoConfiguration(TracingProperties tracingProperties) { + this.tracingProperties = tracingProperties; + } + @Bean @ConditionalOnMissingBean OpenTelemetry openTelemetry(SdkTracerProvider sdkTracerProvider, ContextPropagators contextPropagators) { @@ -92,32 +98,31 @@ public class OpenTelemetryAutoConfiguration { @Bean @ConditionalOnMissingBean - SdkTracerProvider otelSdkTracerProvider(Environment environment, List spanProcessors, + SdkTracerProvider otelSdkTracerProvider(Environment environment, ObjectProvider spanProcessors, Sampler sampler) { String applicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME); SdkTracerProviderBuilder builder = SdkTracerProvider.builder().setSampler(sampler) .setResource(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName))); - for (SpanProcessor spanProcessor : spanProcessors) { - builder.addSpanProcessor(spanProcessor); - } + spanProcessors.orderedStream().forEach(builder::addSpanProcessor); return builder.build(); } @Bean @ConditionalOnMissingBean - ContextPropagators otelContextPropagators(List textMapPropagators) { - return ContextPropagators.create(TextMapPropagator.composite(textMapPropagators)); + ContextPropagators otelContextPropagators(ObjectProvider textMapPropagators) { + return ContextPropagators + .create(TextMapPropagator.composite(textMapPropagators.orderedStream().collect(Collectors.toList()))); } @Bean @ConditionalOnMissingBean - Sampler otelSampler(TracingProperties properties) { - return Sampler.traceIdRatioBased(properties.getSampling().getProbability()); + Sampler otelSampler() { + return Sampler.traceIdRatioBased(this.tracingProperties.getSampling().getProbability()); } @Bean - SpanProcessor otelSpanProcessor(List spanExporter) { - return SpanProcessor.composite(spanExporter.stream() + SpanProcessor otelSpanProcessor(ObjectProvider spanExporters) { + return SpanProcessor.composite(spanExporters.orderedStream() .map((exporter) -> BatchSpanProcessor.builder(exporter).build()).collect(Collectors.toList())); } @@ -130,9 +135,9 @@ public class OpenTelemetryAutoConfiguration { @Bean @ConditionalOnMissingBean OtelTracer micrometerOtelTracer(Tracer tracer, EventPublisher eventPublisher, - OtelCurrentTraceContext otelCurrentTraceContext, TracingProperties properties) { - return new OtelTracer(tracer, otelCurrentTraceContext, eventPublisher, - new OtelBaggageManager(otelCurrentTraceContext, properties.getBaggage().getRemoteFields(), List.of())); + OtelCurrentTraceContext otelCurrentTraceContext) { + return new OtelTracer(tracer, otelCurrentTraceContext, eventPublisher, new OtelBaggageManager( + otelCurrentTraceContext, this.tracingProperties.getBaggage().getRemoteFields(), List.of())); } @Bean @@ -168,93 +173,65 @@ public class OpenTelemetryAutoConfiguration { new DefaultHttpServerAttributesExtractor()); } - @Configuration(proxyBeanMethods = false) - static class PropagationConfiguration { - - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(B3Propagator.class) - static class B3NoBaggagePropagatorConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(value = "management.tracing.propagation.type", havingValue = "B3") - B3Propagator b3TextMapPropagator() { - return B3Propagator.injectingSingleHeader(); - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnProperty(value = "management.tracing.baggage.enabled", havingValue = "false") - static class W3CNoBaggagePropagatorConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(value = "management.tracing.propagation.type", havingValue = "W3C", - matchIfMissing = true) - W3CTraceContextPropagator w3cTextMapPropagatorWithoutBaggage() { - return W3CTraceContextPropagator.getInstance(); - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnProperty(value = "management.tracing.baggage.enabled", matchIfMissing = true) - static class W3CBaggagePropagatorConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(value = "management.tracing.propagation.type", havingValue = "W3C", - matchIfMissing = true) - TextMapPropagator w3cTextMapPropagatorWithBaggage() { - return TextMapPropagator.composite(W3CTraceContextPropagator.getInstance(), - W3CBaggagePropagator.getInstance()); - } - - } - + @Bean + @ConditionalOnMissingBean + Slf4JEventListener otelSlf4JEventListener() { + return new Slf4JEventListener(); } @Configuration(proxyBeanMethods = false) - static class MicrometerTracingPropagationConfiguration { + @ConditionalOnProperty(prefix = "management.tracing.baggage", name = "enabled", matchIfMissing = true) + static class BaggageConfiguration { - @Configuration(proxyBeanMethods = false) - @ConditionalOnProperty(value = "management.tracing.baggage.enabled", matchIfMissing = true) - static class BaggagePropagatorConfiguration { - - @Bean - @ConditionalOnProperty(value = "management.tracing.propagation.type", havingValue = "B3") - BaggageTextMapPropagator b3BaggageTextMapPropagator(TracingProperties properties, - OtelCurrentTraceContext otelCurrentTraceContext) { - return new BaggageTextMapPropagator(properties.getBaggage().getRemoteFields(), new OtelBaggageManager( - otelCurrentTraceContext, properties.getBaggage().getRemoteFields(), List.of())); - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(MDC.class) - static class Slf4jConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(value = "management.tracing.baggage.correlation.enabled", matchIfMissing = true) - Slf4JBaggageEventListener otelSlf4JBaggageEventListener(TracingProperties tracingProperties) { - return new Slf4JBaggageEventListener(tracingProperties.getBaggage().getCorrelation().getFields()); - } - - } + private final TracingProperties tracingProperties; + BaggageConfiguration(TracingProperties tracingProperties) { + this.tracingProperties = tracingProperties; } - } + @Bean + @ConditionalOnProperty(prefix = "management.tracing.propagation", name = "type", havingValue = "W3C", + matchIfMissing = true) + TextMapPropagator w3cTextMapPropagatorWithBaggage() { + return TextMapPropagator.composite(W3CTraceContextPropagator.getInstance(), + W3CBaggagePropagator.getInstance()); + } - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(MDC.class) - static class Slf4jConfiguration { + @Bean + @ConditionalOnProperty(prefix = "management.tracing.propagation", name = "type", havingValue = "B3") + TextMapPropagator b3BaggageTextMapPropagator(OtelCurrentTraceContext otelCurrentTraceContext) { + List remoteFields = this.tracingProperties.getBaggage().getRemoteFields(); + return TextMapPropagator.composite(B3Propagator.injectingSingleHeader(), new BaggageTextMapPropagator( + remoteFields, new OtelBaggageManager(otelCurrentTraceContext, remoteFields, List.of()))); + } @Bean @ConditionalOnMissingBean - Slf4JEventListener otelSlf4JEventListener() { - return new Slf4JEventListener(); + @ConditionalOnProperty(prefix = "management.tracing.baggage.correlation", name = "enabled", + matchIfMissing = true) + Slf4JBaggageEventListener otelSlf4JBaggageEventListener() { + return new Slf4JBaggageEventListener(this.tracingProperties.getBaggage().getCorrelation().getFields()); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(prefix = "management.tracing.baggage", name = "enabled", havingValue = "false") + static class NoBaggageConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(prefix = "management.tracing.propagation", name = "type", havingValue = "B3") + B3Propagator b3TextMapPropagator() { + return B3Propagator.injectingSingleHeader(); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(prefix = "management.tracing.propagation", name = "type", havingValue = "W3C", + matchIfMissing = true) + W3CTraceContextPropagator w3cTextMapPropagatorWithoutBaggage() { + return W3CTraceContextPropagator.getInstance(); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java index 51a801045e..1aca4a5339 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java @@ -79,7 +79,7 @@ public class TracingProperties { /** * Whether to enable Micrometer Tracing baggage propagation. */ - private boolean enabled; + private boolean enabled = true; /** * Correlation configuration. diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BaggageAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BaggagePropagationIntegrationTests.java similarity index 97% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BaggageAutoConfigurationTests.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BaggagePropagationIntegrationTests.java index f104fad7d5..2d4fb67902 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BaggageAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BaggagePropagationIntegrationTests.java @@ -36,11 +36,12 @@ import org.springframework.context.ApplicationContext; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for Baggage configuration. + * Tests for Baggage propagation with Brave and OpenTelemetry using W3C and B3 propagation + * formats. * * @author Marcin Grzejszczak */ -class BaggageAutoConfigurationTests { +class BaggagePropagationIntegrationTests { static final String COUNTRY_CODE = "country-code"; static final String BUSINESS_PROCESS = "bp"; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java index baecb0bb3b..1bac894b1b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java @@ -19,6 +19,7 @@ package org.springframework.boot.actuate.autoconfigure.tracing; import brave.Tracer; import brave.Tracing; import brave.baggage.BaggagePropagation; +import brave.baggage.CorrelationScopeConfig.SingleCorrelationField; import brave.http.HttpClientHandler; import brave.http.HttpClientRequest; import brave.http.HttpClientResponse; @@ -27,6 +28,7 @@ import brave.http.HttpServerRequest; import brave.http.HttpServerResponse; import brave.http.HttpTracing; import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.ScopeDecorator; import brave.propagation.Propagation; import brave.propagation.Propagation.Factory; import brave.sampler.Sampler; @@ -34,6 +36,8 @@ import io.micrometer.tracing.brave.bridge.BraveBaggageManager; import io.micrometer.tracing.brave.bridge.BraveHttpClientHandler; import io.micrometer.tracing.brave.bridge.BraveHttpServerHandler; import io.micrometer.tracing.brave.bridge.BraveTracer; +import io.micrometer.tracing.brave.bridge.W3CPropagation; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.mockito.Answers; @@ -59,6 +63,7 @@ class BraveAutoConfigurationTests { @Test void shouldSupplyDefaultBeans() { this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(BraveAutoConfiguration.class); assertThat(context).hasSingleBean(Tracing.class); assertThat(context).hasSingleBean(Tracer.class); assertThat(context).hasSingleBean(CurrentTraceContext.class); @@ -72,6 +77,9 @@ class BraveAutoConfigurationTests { assertThat(context).hasSingleBean(BraveHttpClientHandler.class); assertThat(context).hasSingleBean(Propagation.Factory.class); assertThat(context).hasSingleBean(BaggagePropagation.FactoryBuilder.class); + assertThat(context).hasSingleBean(BraveTracer.class); + assertThat(context).hasSingleBean(BraveHttpServerHandler.class); + assertThat(context).hasSingleBean(BraveHttpClientHandler.class); }); } @@ -118,6 +126,7 @@ class BraveAutoConfigurationTests { }); } + @Test void shouldNotSupplyBeansIfBraveIsMissing() { this.contextRunner.withClassLoader(new FilteredClassLoader("brave")) .run((context) -> assertThat(context).doesNotHaveBean(BraveAutoConfiguration.class)); @@ -125,14 +134,15 @@ class BraveAutoConfigurationTests { @Test void shouldNotSupplyBeansIfMicrometerIsMissing() { - this.contextRunner.withClassLoader(new FilteredClassLoader("brave")) + this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer")) .run((context) -> assertThat(context).doesNotHaveBean(BraveAutoConfiguration.class)); } @Test void shouldSupplyW3CPropagationFactoryByDefault() { this.contextRunner.run((context) -> { - assertThat(context).hasBean("w3cPropagationFactory"); + assertThat(context).hasBean("propagationFactory"); + assertThat(context).hasSingleBean(W3CPropagation.class); assertThat(context).hasSingleBean(BaggagePropagation.FactoryBuilder.class); }); } @@ -140,7 +150,8 @@ class BraveAutoConfigurationTests { @Test void shouldSupplyB3PropagationFactoryViaProperty() { this.contextRunner.withPropertyValues("management.tracing.propagation.type=B3").run((context) -> { - assertThat(context).hasBean("b3PropagationFactory"); + assertThat(context).hasBean("propagationFactory"); + assertThat(context.getBean(Factory.class).toString()).isEqualTo("B3Propagation"); assertThat(context).hasSingleBean(BaggagePropagation.FactoryBuilder.class); }); } @@ -151,14 +162,6 @@ class BraveAutoConfigurationTests { .run((context) -> assertThat(context).doesNotHaveBean(BraveAutoConfiguration.class)); } - @Test - void shouldNotSupplyMdcCorrelationScopeWhenMdcNotOnClasspath() { - this.contextRunner.withClassLoader(new FilteredClassLoader("org.slf4j")).run((context) -> { - assertThat(context).doesNotHaveBean("mdcCorrelationScopeDecoratorBuilder"); - assertThat(context).doesNotHaveBean("correlationScopeDecorator"); - }); - } - @Test void shouldNotSupplyCorrelationScopeDecoratorIfBaggageDisabled() { this.contextRunner.withPropertyValues("management.tracing.baggage.enabled=false") @@ -168,21 +171,38 @@ class BraveAutoConfigurationTests { @Test void shouldSupplyW3CWithoutBaggageByDefaultIfBaggageDisabled() { this.contextRunner.withPropertyValues("management.tracing.baggage.enabled=false") - .run((context) -> assertThat(context).hasBean("w3cPropagationNoBaggageFactory")); + .run((context) -> assertThat(context).hasSingleBean(W3CPropagation.class)); } @Test void shouldSupplyB3WithoutBaggageIfBaggageDisabledAndB3Picked() { - this.contextRunner - .withPropertyValues("management.tracing.baggage.enabled=false", - "management.tracing.propagation.type=B3") - .run((context) -> assertThat(context).hasBean("b3PropagationNoBaggageFactory")); + this.contextRunner.withPropertyValues("management.tracing.baggage.enabled=false", + "management.tracing.propagation.type=B3").run((context) -> { + assertThat(context).hasBean("propagationFactory"); + assertThat(context.getBean(Factory.class).toString()).isEqualTo("B3Propagation"); + }); } @Test - void shouldNotSupplyCorrelationScopeDecoratorIfBaggageCorrelationDisabled() { - this.contextRunner.withPropertyValues("management.tracing.baggage.correlation.enabled=false") - .run((context) -> assertThat(context).doesNotHaveBean("correlationFieldsCorrelationScopeDecorator")); + void shouldNotApplyCorrelationFieldsIfBaggageCorrelationDisabled() { + this.contextRunner.withPropertyValues("management.tracing.baggage.correlation.enabled=false", + "management.tracing.baggage.correlation.fields=alpha,bravo").run((context) -> { + ScopeDecorator scopeDecorator = context.getBean(ScopeDecorator.class); + assertThat(scopeDecorator) + .extracting("fields", InstanceOfAssertFactories.array(SingleCorrelationField[].class)) + .hasSize(2); + }); + } + + @Test + void shouldNotApplyCorrelationFieldsIfBaggageCorrelationEnabled() { + this.contextRunner.withPropertyValues("management.tracing.baggage.correlation.enabled=true", + "management.tracing.baggage.correlation.fields=alpha,bravo").run((context) -> { + ScopeDecorator scopeDecorator = context.getBean(ScopeDecorator.class); + assertThat(scopeDecorator) + .extracting("fields", InstanceOfAssertFactories.array(SingleCorrelationField[].class)) + .hasSize(4); + }); } @Test diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java index 1c50fb33ca..21f3ed7e5c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java @@ -16,9 +16,12 @@ package org.springframework.boot.actuate.autoconfigure.tracing; +import java.util.List; + import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; import io.micrometer.tracing.otel.bridge.OtelHttpClientHandler; import io.micrometer.tracing.otel.bridge.OtelHttpServerHandler; +import io.micrometer.tracing.otel.bridge.OtelPropagator; import io.micrometer.tracing.otel.bridge.OtelTracer; import io.micrometer.tracing.otel.bridge.OtelTracer.EventPublisher; import io.micrometer.tracing.otel.bridge.Slf4JBaggageEventListener; @@ -70,6 +73,11 @@ class OpenTelemetryAutoConfigurationTests { assertThat(context).hasSingleBean(ContextPropagators.class); assertThat(context).hasSingleBean(Sampler.class); assertThat(context).hasSingleBean(Tracer.class); + assertThat(context).hasSingleBean(Slf4JEventListener.class); + assertThat(context).hasSingleBean(Slf4JBaggageEventListener.class); + assertThat(context).hasSingleBean(SpanProcessor.class); + assertThat(context).hasSingleBean(OtelPropagator.class); + assertThat(context).hasSingleBean(TextMapPropagator.class); }); } @@ -86,8 +94,12 @@ class OpenTelemetryAutoConfigurationTests { assertThat(context).doesNotHaveBean(SdkTracerProvider.class); assertThat(context).doesNotHaveBean(ContextPropagators.class); assertThat(context).doesNotHaveBean(Sampler.class); - assertThat(context).doesNotHaveBean(SpanProcessor.class); assertThat(context).doesNotHaveBean(Tracer.class); + assertThat(context).doesNotHaveBean(Slf4JEventListener.class); + assertThat(context).doesNotHaveBean(Slf4JBaggageEventListener.class); + assertThat(context).doesNotHaveBean(SpanProcessor.class); + assertThat(context).doesNotHaveBean(OtelPropagator.class); + assertThat(context).doesNotHaveBean(TextMapPropagator.class); }); } @@ -112,53 +124,63 @@ class OpenTelemetryAutoConfigurationTests { assertThat(context).hasSingleBean(ContextPropagators.class); assertThat(context).hasBean("customSampler"); assertThat(context).hasSingleBean(Sampler.class); - assertThat(context).hasBean("customSpanProcessor"); assertThat(context).hasBean("customTracer"); assertThat(context).hasSingleBean(Tracer.class); - }); - } - - @Test - void shouldSupplyBaggageAndSlf4jEventListenersWhenMdcOnClasspath() { - this.contextRunner.run((context) -> { + assertThat(context).hasBean("customSlf4jEventListener"); assertThat(context).hasSingleBean(Slf4JEventListener.class); + assertThat(context).hasBean("customSlf4jBaggageEventListener"); assertThat(context).hasSingleBean(Slf4JBaggageEventListener.class); + assertThat(context).hasBean("customOtelPropagator"); + assertThat(context).hasSingleBean(OtelPropagator.class); }); } @Test - void shouldSupplySlf4jEventListenersWhenMdcOnClasspathAndBaggageCorrelationDisabled() { - this.contextRunner.withPropertyValues("management.tracing.baggage.correlation.enabled=false").run((context) -> { - assertThat(context).hasSingleBean(Slf4JEventListener.class); - assertThat(context).doesNotHaveBean(Slf4JBaggageEventListener.class); + void shouldAllowMultipleSpanProcessors() { + this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { + assertThat(context.getBeansOfType(SpanProcessor.class)).hasSize(2); + assertThat(context).hasBean("customSpanProcessor"); }); } @Test - void shouldSupplySlf4jEventListenersWhenMdcOnClasspathAndBaggageDisabled() { - this.contextRunner.withPropertyValues("management.tracing.baggage.enabled=false").run((context) -> { - assertThat(context).hasSingleBean(Slf4JEventListener.class); - assertThat(context).doesNotHaveBean(Slf4JBaggageEventListener.class); + void shouldAllowMultipleTextMapPropagators() { + this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { + assertThat(context.getBeansOfType(TextMapPropagator.class)).hasSize(2); + assertThat(context).hasBean("customTextMapPropagator"); }); } @Test - void shouldNotSupplySlf4jEventListenersWhenMdcNotOnClasspath() { - this.contextRunner.withClassLoader(new FilteredClassLoader("org.slf4j")).run((context) -> { - assertThat(context).doesNotHaveBean(Slf4JEventListener.class); - assertThat(context).doesNotHaveBean(Slf4JBaggageEventListener.class); - }); + void shouldNotSupplySlf4jBaggageEventListenerBaggageCorrelationDisabled() { + this.contextRunner.withPropertyValues("management.tracing.baggage.correlation.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(Slf4JBaggageEventListener.class)); + } + + @Test + void shouldNotSupplySlf4JBaggageEventListenerWhenBaggageDisabled() { + this.contextRunner.withPropertyValues("management.tracing.baggage.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(Slf4JBaggageEventListener.class)); } @Test void shouldSupplyB3PropagationIfPropagationPropertySet() { this.contextRunner.withPropertyValues("management.tracing.propagation.type=B3").run((context) -> { - assertThat(context).hasSingleBean(B3Propagator.class); - assertThat(context).hasBean("b3TextMapPropagator"); + assertThat(context).hasBean("b3BaggageTextMapPropagator"); assertThat(context).doesNotHaveBean(W3CTraceContextPropagator.class); }); } + @Test + void shouldSupplyB3PropagationIfPropagationPropertySetAndBaggageDisabled() { + this.contextRunner.withPropertyValues("management.tracing.propagation.type=B3", + "management.tracing.baggage.enabled=false").run((context) -> { + assertThat(context).hasSingleBean(B3Propagator.class); + assertThat(context).hasBean("b3TextMapPropagator"); + assertThat(context).doesNotHaveBean(W3CTraceContextPropagator.class); + }); + } + @Test void shouldSupplyW3CPropagationWithBaggageByDefault() { this.contextRunner.run((context) -> assertThat(context).hasBean("w3cTextMapPropagatorWithBaggage")); @@ -238,6 +260,26 @@ class OpenTelemetryAutoConfigurationTests { return mock(Tracer.class); } + @Bean + Slf4JEventListener customSlf4jEventListener() { + return new Slf4JEventListener(); + } + + @Bean + Slf4JBaggageEventListener customSlf4jBaggageEventListener() { + return new Slf4JBaggageEventListener(List.of("alpha")); + } + + @Bean + OtelPropagator customOtelPropagator(ContextPropagators propagators, Tracer tracer) { + return new OtelPropagator(propagators, tracer); + } + + @Bean + TextMapPropagator customTextMapPropagator() { + return mock(TextMapPropagator.class); + } + } @Configuration(proxyBeanMethods = false)