Skip to content

Commit d515599

Browse files
committed
Support overriding default OTel SpanProcessor
Also makes it easier to set the MeterProvider used in the default SpanProcessor. Closes gh-35560
1 parent 0439b63 commit d515599

File tree

4 files changed

+190
-11
lines changed

4 files changed

+190
-11
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.Collections;
2020
import java.util.List;
21+
import java.util.stream.Collectors;
2122

2223
import io.micrometer.tracing.SpanCustomizer;
2324
import io.micrometer.tracing.exporter.SpanExportingPredicate;
@@ -37,6 +38,7 @@
3738
import io.micrometer.tracing.otel.propagation.BaggageTextMapPropagator;
3839
import io.opentelemetry.api.OpenTelemetry;
3940
import io.opentelemetry.api.common.Attributes;
41+
import io.opentelemetry.api.metrics.MeterProvider;
4042
import io.opentelemetry.api.trace.Tracer;
4143
import io.opentelemetry.context.ContextStorage;
4244
import io.opentelemetry.context.propagation.ContextPropagators;
@@ -47,6 +49,7 @@
4749
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
4850
import io.opentelemetry.sdk.trace.SpanProcessor;
4951
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
52+
import io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder;
5053
import io.opentelemetry.sdk.trace.export.SpanExporter;
5154
import io.opentelemetry.sdk.trace.samplers.Sampler;
5255
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
@@ -99,13 +102,13 @@ OpenTelemetry openTelemetry(SdkTracerProvider sdkTracerProvider, ContextPropagat
99102

100103
@Bean
101104
@ConditionalOnMissingBean
102-
SdkTracerProvider otelSdkTracerProvider(Environment environment, ObjectProvider<SpanProcessor> spanProcessors,
103-
Sampler sampler, ObjectProvider<SdkTracerProviderBuilderCustomizer> customizers) {
105+
SdkTracerProvider otelSdkTracerProvider(Environment environment, SpanProcessors spanProcessors, Sampler sampler,
106+
ObjectProvider<SdkTracerProviderBuilderCustomizer> customizers) {
104107
String applicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME);
105108
SdkTracerProviderBuilder builder = SdkTracerProvider.builder()
106109
.setSampler(sampler)
107110
.setResource(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName)));
108-
spanProcessors.orderedStream().forEach(builder::addSpanProcessor);
111+
spanProcessors.forEach(builder::addSpanProcessor);
109112
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
110113
return builder.build();
111114
}
@@ -124,14 +127,20 @@ Sampler otelSampler() {
124127
}
125128

126129
@Bean
127-
SpanProcessor otelSpanProcessor(ObjectProvider<SpanExporter> spanExporters,
130+
@ConditionalOnMissingBean
131+
SpanProcessors spanProcessors(ObjectProvider<SpanProcessor> spanProcessors) {
132+
return () -> spanProcessors.orderedStream().collect(Collectors.toList());
133+
}
134+
135+
@Bean
136+
BatchSpanProcessor otelSpanProcessor(ObjectProvider<SpanExporter> spanExporters,
128137
ObjectProvider<SpanExportingPredicate> spanExportingPredicates, ObjectProvider<SpanReporter> spanReporters,
129-
ObjectProvider<SpanFilter> spanFilters) {
130-
return BatchSpanProcessor
131-
.builder(new CompositeSpanExporter(spanExporters.orderedStream().toList(),
132-
spanExportingPredicates.orderedStream().toList(), spanReporters.orderedStream().toList(),
133-
spanFilters.orderedStream().toList()))
134-
.build();
138+
ObjectProvider<SpanFilter> spanFilters, ObjectProvider<MeterProvider> meterProvider) {
139+
BatchSpanProcessorBuilder builder = BatchSpanProcessor.builder(new CompositeSpanExporter(
140+
spanExporters.orderedStream().toList(), spanExportingPredicates.orderedStream().toList(),
141+
spanReporters.orderedStream().toList(), spanFilters.orderedStream().toList()));
142+
meterProvider.ifAvailable(builder::setMeterProvider);
143+
return builder.build();
135144
}
136145

137146
@Bean
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.tracing;
18+
19+
import java.util.Arrays;
20+
import java.util.Iterator;
21+
import java.util.List;
22+
import java.util.Spliterator;
23+
24+
import io.opentelemetry.sdk.trace.SpanProcessor;
25+
26+
/**
27+
* A collection of {@link SpanProcessor span processors}.
28+
*
29+
* @author Moritz Halbritter
30+
* @since 3.2.0
31+
*/
32+
public interface SpanProcessors extends Iterable<SpanProcessor> {
33+
34+
/**
35+
* Returns the list of {@link SpanProcessor span processors}.
36+
* @return the list of span processors
37+
*/
38+
List<SpanProcessor> getList();
39+
40+
@Override
41+
default Iterator<SpanProcessor> iterator() {
42+
return getList().iterator();
43+
}
44+
45+
@Override
46+
default Spliterator<SpanProcessor> spliterator() {
47+
return getList().spliterator();
48+
}
49+
50+
/**
51+
* Constructs a {@link SpanProcessors} instance with the given list of
52+
* {@link SpanProcessor span processors}.
53+
* @param spanProcessors the list of span processors
54+
* @return the constructed {@link SpanProcessors} instance
55+
*/
56+
static SpanProcessors of(List<SpanProcessor> spanProcessors) {
57+
return () -> spanProcessors;
58+
}
59+
60+
/**
61+
* Constructs a {@link SpanProcessors} instance with the given {@link SpanProcessor
62+
* span processors}.
63+
* @param spanProcessors the span processors
64+
* @return the constructed {@link SpanProcessors} instance
65+
*/
66+
static SpanProcessors of(SpanProcessor... spanProcessors) {
67+
return of(Arrays.asList(spanProcessors));
68+
}
69+
70+
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import io.micrometer.tracing.otel.bridge.Slf4JEventListener;
3030
import io.micrometer.tracing.otel.propagation.BaggageTextMapPropagator;
3131
import io.opentelemetry.api.OpenTelemetry;
32+
import io.opentelemetry.api.metrics.MeterProvider;
3233
import io.opentelemetry.api.trace.Tracer;
3334
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
3435
import io.opentelemetry.context.propagation.ContextPropagators;
@@ -41,6 +42,7 @@
4142
import org.junit.jupiter.api.Test;
4243
import org.junit.jupiter.params.ParameterizedTest;
4344
import org.junit.jupiter.params.provider.ValueSource;
45+
import org.mockito.Mockito;
4446

4547
import org.springframework.boot.autoconfigure.AutoConfigurations;
4648
import org.springframework.boot.test.context.FilteredClassLoader;
@@ -51,6 +53,9 @@
5153

5254
import static org.assertj.core.api.Assertions.assertThat;
5355
import static org.assertj.core.api.Assertions.fail;
56+
import static org.mockito.ArgumentMatchers.anyString;
57+
import static org.mockito.BDDMockito.given;
58+
import static org.mockito.BDDMockito.then;
5459
import static org.mockito.Mockito.mock;
5560

5661
/**
@@ -82,6 +87,7 @@ void shouldSupplyBeans() {
8287
assertThat(context).hasSingleBean(OtelPropagator.class);
8388
assertThat(context).hasSingleBean(TextMapPropagator.class);
8489
assertThat(context).hasSingleBean(OtelSpanCustomizer.class);
90+
assertThat(context).hasSingleBean(SpanProcessors.class);
8591
});
8692
}
8793

@@ -112,6 +118,7 @@ void shouldNotSupplyBeansIfDependencyIsMissing(String packageName) {
112118
assertThat(context).doesNotHaveBean(OtelPropagator.class);
113119
assertThat(context).doesNotHaveBean(TextMapPropagator.class);
114120
assertThat(context).doesNotHaveBean(OtelSpanCustomizer.class);
121+
assertThat(context).doesNotHaveBean(SpanProcessors.class);
115122
});
116123
}
117124

@@ -142,14 +149,18 @@ void shouldBackOffOnCustomBeans() {
142149
assertThat(context).hasSingleBean(OtelPropagator.class);
143150
assertThat(context).hasBean("customSpanCustomizer");
144151
assertThat(context).hasSingleBean(SpanCustomizer.class);
152+
assertThat(context).hasBean("customSpanProcessors");
153+
assertThat(context).hasSingleBean(SpanProcessors.class);
145154
});
146155
}
147156

148157
@Test
149158
void shouldAllowMultipleSpanProcessors() {
150-
this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> {
159+
this.contextRunner.withUserConfiguration(AdditionalSpanProcessorConfiguration.class).run((context) -> {
151160
assertThat(context.getBeansOfType(SpanProcessor.class)).hasSize(2);
152161
assertThat(context).hasBean("customSpanProcessor");
162+
SpanProcessors spanProcessors = context.getBean(SpanProcessors.class);
163+
assertThat(spanProcessors).hasSize(2);
153164
});
154165
}
155166

@@ -235,9 +246,46 @@ void shouldCustomizeSdkTracerProvider() {
235246
});
236247
}
237248

249+
@Test
250+
void defaultSpanProcessorShouldUseMeterProviderIfAvailable() {
251+
this.contextRunner.withUserConfiguration(MeterProviderConfiguration.class).run((context) -> {
252+
MeterProvider meterProvider = context.getBean(MeterProvider.class);
253+
assertThat(Mockito.mockingDetails(meterProvider).isMock()).isTrue();
254+
then(meterProvider).should().meterBuilder(anyString());
255+
});
256+
}
257+
258+
@Configuration(proxyBeanMethods = false)
259+
private static class MeterProviderConfiguration {
260+
261+
@Bean
262+
MeterProvider meterProvider() {
263+
MeterProvider mock = mock(MeterProvider.class);
264+
given(mock.meterBuilder(anyString()))
265+
.willAnswer((invocation) -> MeterProvider.noop().meterBuilder(invocation.getArgument(0, String.class)));
266+
return mock;
267+
}
268+
269+
}
270+
271+
@Configuration(proxyBeanMethods = false)
272+
private static class AdditionalSpanProcessorConfiguration {
273+
274+
@Bean
275+
SpanProcessor customSpanProcessor() {
276+
return mock(SpanProcessor.class);
277+
}
278+
279+
}
280+
238281
@Configuration(proxyBeanMethods = false)
239282
private static class CustomConfiguration {
240283

284+
@Bean
285+
SpanProcessors customSpanProcessors() {
286+
return SpanProcessors.of(mock(SpanProcessor.class));
287+
}
288+
241289
@Bean
242290
io.micrometer.tracing.Tracer customMicrometerTracer() {
243291
return mock(io.micrometer.tracing.Tracer.class);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.tracing;
18+
19+
import java.util.List;
20+
21+
import io.opentelemetry.sdk.trace.SpanProcessor;
22+
import org.junit.jupiter.api.Test;
23+
24+
import static org.assertj.core.api.Assertions.assertThat;
25+
import static org.mockito.Mockito.mock;
26+
27+
/**
28+
* Tests for {@link SpanProcessors}.
29+
*
30+
* @author Moritz Halbritter
31+
*/
32+
class SpanProcessorsTests {
33+
34+
@Test
35+
void ofList() {
36+
SpanProcessor spanProcessor1 = mock(SpanProcessor.class);
37+
SpanProcessor spanProcessor2 = mock(SpanProcessor.class);
38+
SpanProcessors spanProcessors = SpanProcessors.of(List.of(spanProcessor1, spanProcessor2));
39+
assertThat(spanProcessors).containsExactly(spanProcessor1, spanProcessor2);
40+
assertThat(spanProcessors.getList()).containsExactly(spanProcessor1, spanProcessor2);
41+
}
42+
43+
@Test
44+
void ofArray() {
45+
SpanProcessor spanProcessor1 = mock(SpanProcessor.class);
46+
SpanProcessor spanProcessor2 = mock(SpanProcessor.class);
47+
SpanProcessors spanProcessors = SpanProcessors.of(spanProcessor1, spanProcessor2);
48+
assertThat(spanProcessors).containsExactly(spanProcessor1, spanProcessor2);
49+
assertThat(spanProcessors.getList()).containsExactly(spanProcessor1, spanProcessor2);
50+
}
51+
52+
}

0 commit comments

Comments
 (0)