Merge pull request #32480 from marcingrzejszczak

* gh-32480:
  Polish "Add support for MDC, Context Propagation (via B3 and W3C), and Baggage"
  Add support for MDC, Context Propagation (via B3 and W3C), and Baggage

Closes gh-32480
This commit is contained in:
Andy Wilkinson 2022-09-27 17:13:44 +01:00
commit 3acd9b80e8
7 changed files with 780 additions and 34 deletions

View File

@ -22,6 +22,15 @@ import brave.Tracer;
import brave.Tracing;
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;
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;
@ -41,20 +50,27 @@ 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.springframework.beans.factory.ObjectProvider;
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.annotation.Order;
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 +79,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 +123,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 +149,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 +173,92 @@ public class BraveAutoConfiguration {
return new BraveHttpClientHandler(httpClientHandler);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(value = "management.tracing.baggage.enabled", havingValue = "false")
static class BraveNoBaggageConfiguration {
@Bean
@ConditionalOnMissingBean
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());
};
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(value = "management.tracing.baggage.enabled", matchIfMissing = true)
static class BraveBaggageConfiguration {
private final TracingProperties tracingProperties;
BraveBaggageConfiguration(TracingProperties tracingProperties) {
this.tracingProperties = tracingProperties;
}
@Bean
@ConditionalOnMissingBean
BaggagePropagation.FactoryBuilder propagationFactoryBuilder(
ObjectProvider<BaggagePropagationCustomizer> 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<String> remoteFields = this.tracingProperties.getBaggage().getRemoteFields();
for (String fieldName : remoteFields) {
builder.add(BaggagePropagationConfig.SingleBaggageField.remote(BaggageField.create(fieldName)));
}
};
}
@Bean
@ConditionalOnMissingBean
Factory propagationFactory(BaggagePropagation.FactoryBuilder factoryBuilder) {
return factoryBuilder.build();
}
@Bean
@ConditionalOnMissingBean
CorrelationScopeDecorator.Builder mdcCorrelationScopeDecoratorBuilder(
ObjectProvider<CorrelationScopeCustomizer> 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<String> 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();
}
}
}

View File

@ -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;
@ -44,13 +54,16 @@ import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import org.springframework.beans.factory.ObjectProvider;
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;
/**
@ -70,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) {
@ -79,33 +98,31 @@ public class OpenTelemetryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
SdkTracerProvider otelSdkTracerProvider(Environment environment, List<SpanProcessor> spanProcessors,
SdkTracerProvider otelSdkTracerProvider(Environment environment, ObjectProvider<SpanProcessor> 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<TextMapPropagator> textMapPropagators) {
return ContextPropagators.create(TextMapPropagator.composite(textMapPropagators));
ContextPropagators otelContextPropagators(ObjectProvider<TextMapPropagator> 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
@ConditionalOnMissingBean
SpanProcessor otelSpanProcessor(List<SpanExporter> spanExporter) {
return SpanProcessor.composite(spanExporter.stream()
SpanProcessor otelSpanProcessor(ObjectProvider<SpanExporter> spanExporters) {
return SpanProcessor.composite(spanExporters.orderedStream()
.map((exporter) -> BatchSpanProcessor.builder(exporter).build()).collect(Collectors.toList()));
}
@ -119,20 +136,26 @@ public class OpenTelemetryAutoConfiguration {
@ConditionalOnMissingBean
OtelTracer micrometerOtelTracer(Tracer tracer, EventPublisher eventPublisher,
OtelCurrentTraceContext otelCurrentTraceContext) {
return new OtelTracer(tracer, otelCurrentTraceContext, eventPublisher,
new OtelBaggageManager(otelCurrentTraceContext, List.of(), List.of()));
return new OtelTracer(tracer, otelCurrentTraceContext, eventPublisher, new OtelBaggageManager(
otelCurrentTraceContext, this.tracingProperties.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<EventListener> eventListeners) {
return new OTelEventPublisher(eventListeners);
}
@Bean
@ConditionalOnMissingBean
OtelCurrentTraceContext otelCurrentTraceContext(EventPublisher publisher) {
ContextStorage.addWrapper(new EventPublishingContextWrapper(publisher));
return new OtelCurrentTraceContext();
}
@ -150,4 +173,84 @@ public class OpenTelemetryAutoConfiguration {
new DefaultHttpServerAttributesExtractor());
}
@Bean
@ConditionalOnMissingBean
Slf4JEventListener otelSlf4JEventListener() {
return new Slf4JEventListener();
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "management.tracing.baggage", name = "enabled", matchIfMissing = true)
static class BaggageConfiguration {
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());
}
@Bean
@ConditionalOnProperty(prefix = "management.tracing.propagation", name = "type", havingValue = "B3")
TextMapPropagator b3BaggageTextMapPropagator(OtelCurrentTraceContext otelCurrentTraceContext) {
List<String> remoteFields = this.tracingProperties.getBaggage().getRemoteFields();
return TextMapPropagator.composite(B3Propagator.injectingSingleHeader(), new BaggageTextMapPropagator(
remoteFields, new OtelBaggageManager(otelCurrentTraceContext, remoteFields, List.of())));
}
@Bean
@ConditionalOnMissingBean
@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();
}
}
static class OTelEventPublisher implements EventPublisher {
private final List<EventListener> listeners;
OTelEventPublisher(List<EventListener> listeners) {
this.listeners = listeners;
}
@Override
public void publishEvent(Object event) {
for (EventListener listener : this.listeners) {
listener.onEvent(event);
}
}
}
}

View File

@ -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 = true;
/**
* 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<String> 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<String> getRemoteFields() {
return this.remoteFields;
}
public void setRemoteFields(List<String> 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<String> fields = new ArrayList<>();
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public List<String> getFields() {
return this.fields;
}
public void setFields(List<String> 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
}
}
}

View File

@ -2086,6 +2086,10 @@
"replacement": "management.trace.http.include",
"level": "error"
}
},
{
"name": "management.tracing.propagation.type",
"defaultValue": "W3C"
}
],
"hints": [

View File

@ -0,0 +1,183 @@
/*
* 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 propagation with Brave and OpenTelemetry using W3C and B3 propagation
* formats.
*
* @author Marcin Grzejszczak
*/
class BaggagePropagationIntegrationTests {
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<ApplicationContextRunner> {
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");
}
}
}
}

View File

@ -18,6 +18,8 @@ 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;
@ -26,12 +28,16 @@ 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;
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;
@ -57,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);
@ -66,7 +73,11 @@ 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);
assertThat(context).hasSingleBean(BraveTracer.class);
assertThat(context).hasSingleBean(BraveHttpServerHandler.class);
assertThat(context).hasSingleBean(BraveHttpClientHandler.class);
});
@ -99,6 +110,19 @@ 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);
});
}
@ -110,16 +134,83 @@ 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("propagationFactory");
assertThat(context).hasSingleBean(W3CPropagation.class);
assertThat(context).hasSingleBean(BaggagePropagation.FactoryBuilder.class);
});
}
@Test
void shouldSupplyB3PropagationFactoryViaProperty() {
this.contextRunner.withPropertyValues("management.tracing.propagation.type=B3").run((context) -> {
assertThat(context).hasBean("propagationFactory");
assertThat(context.getBean(Factory.class).toString()).isEqualTo("B3Propagation");
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 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).hasSingleBean(W3CPropagation.class));
}
@Test
void shouldSupplyB3WithoutBaggageIfBaggageDisabledAndB3Picked() {
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 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
void shouldSupplyMdcCorrelationScopeDecoratorIfBaggageCorrelationDisabled() {
this.contextRunner.withPropertyValues("management.tracing.baggage.correlation.enabled=false")
.run((context) -> assertThat(context).hasBean("mdcCorrelationScopeDecoratorBuilder"));
}
@Configuration(proxyBeanMethods = false)
private static class CustomConfiguration {

View File

@ -16,20 +16,29 @@
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;
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,8 +72,12 @@ 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);
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);
});
}
@ -81,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);
});
}
@ -107,13 +124,84 @@ class OpenTelemetryAutoConfigurationTests {
assertThat(context).hasSingleBean(ContextPropagators.class);
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);
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 shouldAllowMultipleSpanProcessors() {
this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> {
assertThat(context.getBeansOfType(SpanProcessor.class)).hasSize(2);
assertThat(context).hasBean("customSpanProcessor");
});
}
@Test
void shouldAllowMultipleTextMapPropagators() {
this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> {
assertThat(context.getBeansOfType(TextMapPropagator.class)).hasSize(2);
assertThat(context).hasBean("customTextMapPropagator");
});
}
@Test
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).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"));
}
@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 {
@ -172,6 +260,56 @@ 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)
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);
}
}
}