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:
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user