Skip to content

Commit bd663c1

Browse files
committed
Add property to disable the OpenTelemetry SDK
This uses no-op implementations for the SdkTracerProvider, SdkLoggerProvider and SdkMeterProvider, but still configures the propagators. Additionally, it doesn't create the tracing and logging beans which would be superfluous for a disabled SDK. Closes gh-49564
1 parent afb201d commit bd663c1

File tree

9 files changed

+187
-0
lines changed

9 files changed

+187
-0
lines changed

documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/observability.adoc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,17 @@ Exporting OpenTelemetry traces is only auto-configured when used together with x
125125

126126

127127

128+
[[actuator.observability.opentelemetry.disabling]]
129+
=== Disabling OpenTelemetry
130+
131+
The OpenTelemetry support can be disabled by setting the configprop:management.opentelemetry.enabled[] property to `false`.
132+
This behaves similarly to the https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#general-sdk-configuration[`OTEL_SDK_DISABLED`] environment variable (but negated): when the SDK is disabled, metrics, traces, and logging will use no-op implementations.
133+
Context propagators are not affected and continue to function normally.
134+
135+
NOTE: Keep in mind that Spring Boot doesn't use OpenTelemetry's metrics functionality, so metrics might still be enabled even when disabling OpenTelemetry.
136+
137+
138+
128139
[[actuator.observability.opentelemetry.environment-variables]]
129140
=== Environment variables
130141

module/spring-boot-micrometer-tracing-opentelemetry/src/main/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryTracingAutoConfiguration.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure.OpenTelemetryPropagationConfigurations.PropagationWithoutBaggage;
6565
import org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure.OpenTelemetryTracingProperties.Export;
6666
import org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure.OpenTelemetryTracingProperties.Limits;
67+
import org.springframework.boot.opentelemetry.autoconfigure.ConditionalOnEnabledOpenTelemetry;
6768
import org.springframework.context.annotation.Bean;
6869
import org.springframework.context.annotation.Import;
6970
import org.springframework.util.CollectionUtils;
@@ -99,6 +100,7 @@ public final class OpenTelemetryTracingAutoConfiguration {
99100

100101
@Bean
101102
@ConditionalOnMissingBean
103+
@ConditionalOnEnabledOpenTelemetry
102104
SdkTracerProvider otelSdkTracerProvider(Resource resource, SpanProcessors spanProcessors, Sampler sampler,
103105
ObjectProvider<SdkTracerProviderBuilderCustomizer> customizers, SpanLimits spanLimits) {
104106
SdkTracerProviderBuilder builder = SdkTracerProvider.builder()
@@ -112,6 +114,7 @@ SdkTracerProvider otelSdkTracerProvider(Resource resource, SpanProcessors spanPr
112114

113115
@Bean
114116
@ConditionalOnMissingBean
117+
@ConditionalOnEnabledOpenTelemetry
115118
SpanLimits otelSpanLimits() {
116119
Limits limits = this.openTelemetryTracingProperties.getLimits();
117120
return SpanLimits.builder()
@@ -132,6 +135,7 @@ ContextPropagators otelContextPropagators(ObjectProvider<TextMapPropagator> text
132135

133136
@Bean
134137
@ConditionalOnMissingBean
138+
@ConditionalOnEnabledOpenTelemetry
135139
Sampler otelSampler() {
136140
return switch (this.openTelemetryTracingProperties.getSampler()) {
137141
case ALWAYS_ON -> Sampler.alwaysOn();
@@ -146,12 +150,14 @@ Sampler otelSampler() {
146150

147151
@Bean
148152
@ConditionalOnMissingBean
153+
@ConditionalOnEnabledOpenTelemetry
149154
SpanProcessors spanProcessors(ObjectProvider<SpanProcessor> spanProcessors) {
150155
return SpanProcessors.of(spanProcessors.orderedStream().toList());
151156
}
152157

153158
@Bean
154159
@ConditionalOnMissingBean
160+
@ConditionalOnEnabledOpenTelemetry
155161
BatchSpanProcessor otelSpanProcessor(SpanExporters spanExporters,
156162
ObjectProvider<SpanExportingPredicate> spanExportingPredicates, ObjectProvider<SpanReporter> spanReporters,
157163
ObjectProvider<SpanFilter> spanFilters, ObjectProvider<MeterProvider> meterProvider) {
@@ -171,6 +177,7 @@ BatchSpanProcessor otelSpanProcessor(SpanExporters spanExporters,
171177

172178
@Bean
173179
@ConditionalOnMissingBean
180+
@ConditionalOnEnabledOpenTelemetry
174181
SpanExporters spanExporters(ObjectProvider<SpanExporter> spanExporters) {
175182
return SpanExporters.of(spanExporters.orderedStream().toList());
176183
}

module/spring-boot-micrometer-tracing-opentelemetry/src/test/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryTracingAutoConfigurationTests.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,31 @@ void batchSpanProcessorShouldBeConfiguredWithDefaultProperties() {
485485
});
486486
}
487487

488+
@Test
489+
void whenOpenTelemetryIsDisabledDoesNotProvideTracingSignalBeans() {
490+
this.contextRunner.withPropertyValues("management.opentelemetry.enabled=false").run((context) -> {
491+
assertThat(context).doesNotHaveBean(SdkTracerProvider.class);
492+
assertThat(context).doesNotHaveBean(SpanLimits.class);
493+
assertThat(context).doesNotHaveBean(Sampler.class);
494+
assertThat(context).doesNotHaveBean(SpanProcessors.class);
495+
assertThat(context).doesNotHaveBean(SpanExporters.class);
496+
assertThat(context).doesNotHaveBean(BatchSpanProcessor.class);
497+
});
498+
}
499+
500+
@Test
501+
void whenOpenTelemetryIsDisabledStillProvidesBridgeAndPropagationBeans() {
502+
this.contextRunner.withPropertyValues("management.opentelemetry.enabled=false").run((context) -> {
503+
assertThat(context).hasSingleBean(OtelTracer.class);
504+
assertThat(context).hasSingleBean(OtelPropagator.class);
505+
assertThat(context).hasSingleBean(OtelCurrentTraceContext.class);
506+
assertThat(context).hasSingleBean(Slf4JEventListener.class);
507+
assertThat(context).hasSingleBean(OtelSpanCustomizer.class);
508+
assertThat(context).hasSingleBean(ContextPropagators.class);
509+
assertThat(context).hasSingleBean(TextMapPropagator.class);
510+
});
511+
}
512+
488513
@Test // gh-41439
489514
@ForkedClassPath
490515
void shouldPublishEventsWhenContextStorageIsInitializedEarly() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2012-present 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.opentelemetry.autoconfigure;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
26+
import org.springframework.context.annotation.Conditional;
27+
28+
/**
29+
* {@link Conditional @Conditional} that checks whether OpenTelemetry is enabled. It
30+
* matches if the value of the {@code management.opentelemetry.enabled} property is
31+
* {@code true} or if it is not configured.
32+
*
33+
* @author Moritz Halbritter
34+
* @since 4.1.0
35+
*/
36+
@Retention(RetentionPolicy.RUNTIME)
37+
@Target({ ElementType.TYPE, ElementType.METHOD })
38+
@Documented
39+
@ConditionalOnBooleanProperty(name = "management.opentelemetry.enabled", matchIfMissing = true)
40+
public @interface ConditionalOnEnabledOpenTelemetry {
41+
42+
}

module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetryProperties.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,26 @@
3030
@ConfigurationProperties("management.opentelemetry")
3131
public class OpenTelemetryProperties {
3232

33+
/**
34+
* Whether OpenTelemetry should be enabled. If OpenTelemetry is disabled, only
35+
* propagators are configured. Metrics, traces, and logging will use no-op
36+
* implementations.
37+
*/
38+
private boolean enabled = true;
39+
3340
/**
3441
* Resource attributes.
3542
*/
3643
private Map<String, String> resourceAttributes = new HashMap<>();
3744

45+
public boolean isEnabled() {
46+
return this.enabled;
47+
}
48+
49+
public void setEnabled(boolean enabled) {
50+
this.enabled = enabled;
51+
}
52+
3853
public Map<String, String> getResourceAttributes() {
3954
return this.resourceAttributes;
4055
}

module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetrySdkAutoConfiguration.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@
3131
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
3232
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3333
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
34+
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
3435
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3536
import org.springframework.context.annotation.Bean;
37+
import org.springframework.context.annotation.Conditional;
3638
import org.springframework.core.env.Environment;
3739

3840
/**
@@ -51,6 +53,7 @@ public final class OpenTelemetrySdkAutoConfiguration {
5153

5254
@Bean
5355
@ConditionalOnMissingBean(OpenTelemetry.class)
56+
@ConditionalOnEnabledOpenTelemetry
5457
OpenTelemetrySdk openTelemetrySdk(ObjectProvider<SdkTracerProvider> openTelemetrySdkTracerProvider,
5558
ObjectProvider<ContextPropagators> openTelemetryContextPropagators,
5659
ObjectProvider<SdkLoggerProvider> openTelemetrySdkLoggerProvider,
@@ -63,6 +66,15 @@ OpenTelemetrySdk openTelemetrySdk(ObjectProvider<SdkTracerProvider> openTelemetr
6366
return builder.build();
6467
}
6568

69+
@Bean
70+
@ConditionalOnMissingBean(OpenTelemetry.class)
71+
@Conditional(OnDisabledOpenTelemetryCondition.class)
72+
OpenTelemetrySdk disabledOpenTelemetrySdk(ObjectProvider<ContextPropagators> openTelemetryContextPropagators) {
73+
OpenTelemetrySdkBuilder builder = OpenTelemetrySdk.builder();
74+
openTelemetryContextPropagators.ifAvailable(builder::setPropagators);
75+
return builder.build();
76+
}
77+
6678
@Bean
6779
@ConditionalOnMissingBean
6880
Resource openTelemetryResource(Environment environment, OpenTelemetryProperties properties) {
@@ -75,4 +87,17 @@ private Resource toResource(Environment environment, OpenTelemetryProperties pro
7587
return builder.build();
7688
}
7789

90+
static class OnDisabledOpenTelemetryCondition extends NoneNestedConditions {
91+
92+
OnDisabledOpenTelemetryCondition() {
93+
super(ConfigurationPhase.PARSE_CONFIGURATION);
94+
}
95+
96+
@ConditionalOnEnabledOpenTelemetry
97+
static class EnabledCondition {
98+
99+
}
100+
101+
}
102+
78103
}

module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/OpenTelemetryLoggingAutoConfiguration.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3232
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3333
import org.springframework.boot.context.properties.EnableConfigurationProperties;
34+
import org.springframework.boot.opentelemetry.autoconfigure.ConditionalOnEnabledOpenTelemetry;
3435
import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetrySdkAutoConfiguration;
3536
import org.springframework.boot.opentelemetry.autoconfigure.logging.OpenTelemetryLoggingProperties.Export;
3637
import org.springframework.boot.opentelemetry.autoconfigure.logging.OpenTelemetryLoggingProperties.Limits;
@@ -44,6 +45,7 @@
4445
*/
4546
@AutoConfiguration(after = OpenTelemetrySdkAutoConfiguration.class)
4647
@ConditionalOnClass(SdkLoggerProvider.class)
48+
@ConditionalOnEnabledOpenTelemetry
4749
@EnableConfigurationProperties(OpenTelemetryLoggingProperties.class)
4850
public final class OpenTelemetryLoggingAutoConfiguration {
4951

module/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetrySdkAutoConfigurationTests.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,57 @@ void whenHasSdkMeterProviderProvidesMeterProvider() {
180180
});
181181
}
182182

183+
@Test
184+
void whenOpenTelemetryIsDisabledStillProvidesOpenTelemetrySdk() {
185+
this.contextRunner.withPropertyValues("management.opentelemetry.enabled=false").run((context) -> {
186+
assertThat(context).hasSingleBean(OpenTelemetrySdk.class);
187+
assertThat(context).hasBean("disabledOpenTelemetrySdk");
188+
});
189+
}
190+
191+
@Test
192+
void whenOpenTelemetryIsDisabledStillConfiguresPropagators() {
193+
this.contextRunner.withPropertyValues("management.opentelemetry.enabled=false")
194+
.withBean(ContextPropagators.class, ContextPropagators::noop)
195+
.run((context) -> {
196+
OpenTelemetry openTelemetry = context.getBean(OpenTelemetry.class);
197+
assertThat(openTelemetry.getPropagators()).isNotNull();
198+
});
199+
}
200+
201+
@Test
202+
void whenOpenTelemetryIsDisabledDoesNotUseTracerProvider() {
203+
SdkTracerProvider tracerProvider = SdkTracerProvider.builder().build();
204+
this.contextRunner.withPropertyValues("management.opentelemetry.enabled=false")
205+
.withBean(SdkTracerProvider.class, () -> tracerProvider)
206+
.run((context) -> {
207+
OpenTelemetrySdk openTelemetry = context.getBean(OpenTelemetrySdk.class);
208+
assertThat(openTelemetry.getSdkTracerProvider()).isNotSameAs(tracerProvider);
209+
});
210+
}
211+
212+
@Test
213+
void whenOpenTelemetryIsDisabledDoesNotUseLoggerProvider() {
214+
SdkLoggerProvider loggerProvider = SdkLoggerProvider.builder().build();
215+
this.contextRunner.withPropertyValues("management.opentelemetry.enabled=false")
216+
.withBean(SdkLoggerProvider.class, () -> loggerProvider)
217+
.run((context) -> {
218+
OpenTelemetrySdk openTelemetry = context.getBean(OpenTelemetrySdk.class);
219+
assertThat(openTelemetry.getSdkLoggerProvider()).isNotSameAs(loggerProvider);
220+
});
221+
}
222+
223+
@Test
224+
void whenOpenTelemetryIsDisabledDoesNotUseMeterProvider() {
225+
SdkMeterProvider meterProvider = SdkMeterProvider.builder().build();
226+
this.contextRunner.withPropertyValues("management.opentelemetry.enabled=false")
227+
.withBean(SdkMeterProvider.class, () -> meterProvider)
228+
.run((context) -> {
229+
OpenTelemetrySdk openTelemetry = context.getBean(OpenTelemetrySdk.class);
230+
assertThat(openTelemetry.getSdkMeterProvider()).isNotSameAs(meterProvider);
231+
});
232+
}
233+
183234
@Configuration(proxyBeanMethods = false)
184235
static class UserConfiguration {
185236

module/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/logging/OpenTelemetryLoggingAutoConfigurationTests.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ void providesBeans() {
7070
});
7171
}
7272

73+
@Test
74+
void whenOpenTelemetryIsDisabledDoesNotProvideBeans() {
75+
this.contextRunner.withPropertyValues("management.opentelemetry.enabled=false").run((context) -> {
76+
assertThat(context).doesNotHaveBean(BatchLogRecordProcessor.class);
77+
assertThat(context).doesNotHaveBean(SdkLoggerProvider.class);
78+
assertThat(context).doesNotHaveBean(LogLimits.class);
79+
});
80+
}
81+
7382
@Test
7483
void whenOpenTelemetryLogsIsNotOnClasspathDoesNotProvideBeans() {
7584
this.contextRunner.withClassLoader(new FilteredClassLoader("io.opentelemetry.sdk.logs")).run((context) -> {

0 commit comments

Comments
 (0)