Support overriding default OTel SpanProcessor

Also makes it easier to set the MeterProvider used in the default
SpanProcessor.

Closes gh-35560
This commit is contained in:
Moritz Halbritter
2023-06-16 09:54:27 +02:00
parent 0439b630d1
commit d51559956f
4 changed files with 190 additions and 11 deletions

View File

@@ -18,6 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.tracing;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import io.micrometer.tracing.SpanCustomizer;
import io.micrometer.tracing.exporter.SpanExportingPredicate;
@@ -37,6 +38,7 @@ import io.micrometer.tracing.otel.bridge.Slf4JEventListener;
import io.micrometer.tracing.otel.propagation.BaggageTextMapPropagator;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.ContextStorage;
import io.opentelemetry.context.propagation.ContextPropagators;
@@ -47,6 +49,7 @@ import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
import io.opentelemetry.sdk.trace.SpanProcessor;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
@@ -99,13 +102,13 @@ public class OpenTelemetryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
SdkTracerProvider otelSdkTracerProvider(Environment environment, ObjectProvider<SpanProcessor> spanProcessors,
Sampler sampler, ObjectProvider<SdkTracerProviderBuilderCustomizer> customizers) {
SdkTracerProvider otelSdkTracerProvider(Environment environment, SpanProcessors spanProcessors, Sampler sampler,
ObjectProvider<SdkTracerProviderBuilderCustomizer> customizers) {
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)));
spanProcessors.orderedStream().forEach(builder::addSpanProcessor);
spanProcessors.forEach(builder::addSpanProcessor);
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
@@ -124,14 +127,20 @@ public class OpenTelemetryAutoConfiguration {
}
@Bean
SpanProcessor otelSpanProcessor(ObjectProvider<SpanExporter> spanExporters,
@ConditionalOnMissingBean
SpanProcessors spanProcessors(ObjectProvider<SpanProcessor> spanProcessors) {
return () -> spanProcessors.orderedStream().collect(Collectors.toList());
}
@Bean
BatchSpanProcessor otelSpanProcessor(ObjectProvider<SpanExporter> spanExporters,
ObjectProvider<SpanExportingPredicate> spanExportingPredicates, ObjectProvider<SpanReporter> spanReporters,
ObjectProvider<SpanFilter> spanFilters) {
return BatchSpanProcessor
.builder(new CompositeSpanExporter(spanExporters.orderedStream().toList(),
spanExportingPredicates.orderedStream().toList(), spanReporters.orderedStream().toList(),
spanFilters.orderedStream().toList()))
.build();
ObjectProvider<SpanFilter> spanFilters, ObjectProvider<MeterProvider> meterProvider) {
BatchSpanProcessorBuilder builder = BatchSpanProcessor.builder(new CompositeSpanExporter(
spanExporters.orderedStream().toList(), spanExportingPredicates.orderedStream().toList(),
spanReporters.orderedStream().toList(), spanFilters.orderedStream().toList()));
meterProvider.ifAvailable(builder::setMeterProvider);
return builder.build();
}
@Bean

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2012-2023 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.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
import io.opentelemetry.sdk.trace.SpanProcessor;
/**
* A collection of {@link SpanProcessor span processors}.
*
* @author Moritz Halbritter
* @since 3.2.0
*/
public interface SpanProcessors extends Iterable<SpanProcessor> {
/**
* Returns the list of {@link SpanProcessor span processors}.
* @return the list of span processors
*/
List<SpanProcessor> getList();
@Override
default Iterator<SpanProcessor> iterator() {
return getList().iterator();
}
@Override
default Spliterator<SpanProcessor> spliterator() {
return getList().spliterator();
}
/**
* Constructs a {@link SpanProcessors} instance with the given list of
* {@link SpanProcessor span processors}.
* @param spanProcessors the list of span processors
* @return the constructed {@link SpanProcessors} instance
*/
static SpanProcessors of(List<SpanProcessor> spanProcessors) {
return () -> spanProcessors;
}
/**
* Constructs a {@link SpanProcessors} instance with the given {@link SpanProcessor
* span processors}.
* @param spanProcessors the span processors
* @return the constructed {@link SpanProcessors} instance
*/
static SpanProcessors of(SpanProcessor... spanProcessors) {
return of(Arrays.asList(spanProcessors));
}
}

View File

@@ -29,6 +29,7 @@ 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.metrics.MeterProvider;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.ContextPropagators;
@@ -41,6 +42,7 @@ 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.Mockito;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
@@ -51,6 +53,9 @@ import org.springframework.core.annotation.Order;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
@@ -82,6 +87,7 @@ class OpenTelemetryAutoConfigurationTests {
assertThat(context).hasSingleBean(OtelPropagator.class);
assertThat(context).hasSingleBean(TextMapPropagator.class);
assertThat(context).hasSingleBean(OtelSpanCustomizer.class);
assertThat(context).hasSingleBean(SpanProcessors.class);
});
}
@@ -112,6 +118,7 @@ class OpenTelemetryAutoConfigurationTests {
assertThat(context).doesNotHaveBean(OtelPropagator.class);
assertThat(context).doesNotHaveBean(TextMapPropagator.class);
assertThat(context).doesNotHaveBean(OtelSpanCustomizer.class);
assertThat(context).doesNotHaveBean(SpanProcessors.class);
});
}
@@ -142,14 +149,18 @@ class OpenTelemetryAutoConfigurationTests {
assertThat(context).hasSingleBean(OtelPropagator.class);
assertThat(context).hasBean("customSpanCustomizer");
assertThat(context).hasSingleBean(SpanCustomizer.class);
assertThat(context).hasBean("customSpanProcessors");
assertThat(context).hasSingleBean(SpanProcessors.class);
});
}
@Test
void shouldAllowMultipleSpanProcessors() {
this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> {
this.contextRunner.withUserConfiguration(AdditionalSpanProcessorConfiguration.class).run((context) -> {
assertThat(context.getBeansOfType(SpanProcessor.class)).hasSize(2);
assertThat(context).hasBean("customSpanProcessor");
SpanProcessors spanProcessors = context.getBean(SpanProcessors.class);
assertThat(spanProcessors).hasSize(2);
});
}
@@ -235,9 +246,46 @@ class OpenTelemetryAutoConfigurationTests {
});
}
@Test
void defaultSpanProcessorShouldUseMeterProviderIfAvailable() {
this.contextRunner.withUserConfiguration(MeterProviderConfiguration.class).run((context) -> {
MeterProvider meterProvider = context.getBean(MeterProvider.class);
assertThat(Mockito.mockingDetails(meterProvider).isMock()).isTrue();
then(meterProvider).should().meterBuilder(anyString());
});
}
@Configuration(proxyBeanMethods = false)
private static class MeterProviderConfiguration {
@Bean
MeterProvider meterProvider() {
MeterProvider mock = mock(MeterProvider.class);
given(mock.meterBuilder(anyString()))
.willAnswer((invocation) -> MeterProvider.noop().meterBuilder(invocation.getArgument(0, String.class)));
return mock;
}
}
@Configuration(proxyBeanMethods = false)
private static class AdditionalSpanProcessorConfiguration {
@Bean
SpanProcessor customSpanProcessor() {
return mock(SpanProcessor.class);
}
}
@Configuration(proxyBeanMethods = false)
private static class CustomConfiguration {
@Bean
SpanProcessors customSpanProcessors() {
return SpanProcessors.of(mock(SpanProcessor.class));
}
@Bean
io.micrometer.tracing.Tracer customMicrometerTracer() {
return mock(io.micrometer.tracing.Tracer.class);

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2012-2023 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.List;
import io.opentelemetry.sdk.trace.SpanProcessor;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link SpanProcessors}.
*
* @author Moritz Halbritter
*/
class SpanProcessorsTests {
@Test
void ofList() {
SpanProcessor spanProcessor1 = mock(SpanProcessor.class);
SpanProcessor spanProcessor2 = mock(SpanProcessor.class);
SpanProcessors spanProcessors = SpanProcessors.of(List.of(spanProcessor1, spanProcessor2));
assertThat(spanProcessors).containsExactly(spanProcessor1, spanProcessor2);
assertThat(spanProcessors.getList()).containsExactly(spanProcessor1, spanProcessor2);
}
@Test
void ofArray() {
SpanProcessor spanProcessor1 = mock(SpanProcessor.class);
SpanProcessor spanProcessor2 = mock(SpanProcessor.class);
SpanProcessors spanProcessors = SpanProcessors.of(spanProcessor1, spanProcessor2);
assertThat(spanProcessors).containsExactly(spanProcessor1, spanProcessor2);
assertThat(spanProcessors.getList()).containsExactly(spanProcessor1, spanProcessor2);
}
}