Skip to content

Commit 1bdb7da

Browse files
committed
Auto-configure Otel's MeterProvider where appropriate
Closes gh-44833
1 parent bb6b477 commit 1bdb7da

File tree

4 files changed

+119
-13
lines changed

4 files changed

+119
-13
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingConfigurations.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818

1919
import java.util.Locale;
2020

21+
import io.opentelemetry.api.metrics.MeterProvider;
2122
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
2223
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporterBuilder;
2324
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;
2425
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporterBuilder;
2526

27+
import org.springframework.beans.factory.ObjectProvider;
2628
import org.springframework.boot.actuate.autoconfigure.logging.ConditionalOnEnabledLoggingExport;
2729
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2830
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -83,26 +85,28 @@ static class Exporters {
8385
@Bean
8486
@ConditionalOnProperty(name = "management.otlp.logging.transport", havingValue = "http", matchIfMissing = true)
8587
OtlpHttpLogRecordExporter otlpHttpLogRecordExporter(OtlpLoggingProperties properties,
86-
OtlpLoggingConnectionDetails connectionDetails) {
88+
OtlpLoggingConnectionDetails connectionDetails, ObjectProvider<MeterProvider> meterProvider) {
8789
OtlpHttpLogRecordExporterBuilder builder = OtlpHttpLogRecordExporter.builder()
8890
.setEndpoint(connectionDetails.getUrl(Transport.HTTP))
8991
.setTimeout(properties.getTimeout())
9092
.setConnectTimeout(properties.getConnectTimeout())
9193
.setCompression(properties.getCompression().name().toLowerCase(Locale.US));
9294
properties.getHeaders().forEach(builder::addHeader);
95+
meterProvider.ifAvailable(builder::setMeterProvider);
9396
return builder.build();
9497
}
9598

9699
@Bean
97100
@ConditionalOnProperty(name = "management.otlp.logging.transport", havingValue = "grpc")
98101
OtlpGrpcLogRecordExporter otlpGrpcLogRecordExporter(OtlpLoggingProperties properties,
99-
OtlpLoggingConnectionDetails connectionDetails) {
102+
OtlpLoggingConnectionDetails connectionDetails, ObjectProvider<MeterProvider> meterProvider) {
100103
OtlpGrpcLogRecordExporterBuilder builder = OtlpGrpcLogRecordExporter.builder()
101104
.setEndpoint(connectionDetails.getUrl(Transport.GRPC))
102105
.setTimeout(properties.getTimeout())
103106
.setConnectTimeout(properties.getConnectTimeout())
104107
.setCompression(properties.getCompression().name().toLowerCase(Locale.US));
105108
properties.getHeaders().forEach(builder::addHeader);
109+
meterProvider.ifAvailable(builder::setMeterProvider);
106110
return builder.build();
107111
}
108112

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818

1919
import java.util.Locale;
2020

21+
import io.opentelemetry.api.metrics.MeterProvider;
2122
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
2223
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder;
2324
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
2425
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder;
2526

27+
import org.springframework.beans.factory.ObjectProvider;
2628
import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing;
2729
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2830
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -81,26 +83,28 @@ static class Exporters {
8183
@Bean
8284
@ConditionalOnProperty(name = "management.otlp.tracing.transport", havingValue = "http", matchIfMissing = true)
8385
OtlpHttpSpanExporter otlpHttpSpanExporter(OtlpTracingProperties properties,
84-
OtlpTracingConnectionDetails connectionDetails) {
86+
OtlpTracingConnectionDetails connectionDetails, ObjectProvider<MeterProvider> meterProvider) {
8587
OtlpHttpSpanExporterBuilder builder = OtlpHttpSpanExporter.builder()
8688
.setEndpoint(connectionDetails.getUrl(Transport.HTTP))
8789
.setTimeout(properties.getTimeout())
8890
.setConnectTimeout(properties.getConnectTimeout())
8991
.setCompression(properties.getCompression().name().toLowerCase(Locale.ROOT));
9092
properties.getHeaders().forEach(builder::addHeader);
93+
meterProvider.ifAvailable(builder::setMeterProvider);
9194
return builder.build();
9295
}
9396

9497
@Bean
9598
@ConditionalOnProperty(name = "management.otlp.tracing.transport", havingValue = "grpc")
9699
OtlpGrpcSpanExporter otlpGrpcSpanExporter(OtlpTracingProperties properties,
97-
OtlpTracingConnectionDetails connectionDetails) {
100+
OtlpTracingConnectionDetails connectionDetails, ObjectProvider<MeterProvider> meterProvider) {
98101
OtlpGrpcSpanExporterBuilder builder = OtlpGrpcSpanExporter.builder()
99102
.setEndpoint(connectionDetails.getUrl(Transport.GRPC))
100103
.setTimeout(properties.getTimeout())
101104
.setConnectTimeout(properties.getConnectTimeout())
102105
.setCompression(properties.getCompression().name().toLowerCase(Locale.ROOT));
103106
properties.getHeaders().forEach(builder::addHeader);
107+
meterProvider.ifAvailable(builder::setMeterProvider);
104108
return builder.build();
105109
}
106110

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingAutoConfigurationTests.java

+57-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,8 +16,13 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.logging.otlp;
1818

19+
import java.util.function.Supplier;
20+
21+
import io.opentelemetry.api.metrics.MeterProvider;
1922
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
23+
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporterBuilder;
2024
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;
25+
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporterBuilder;
2126
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
2227
import okhttp3.HttpUrl;
2328
import org.junit.jupiter.api.Test;
@@ -30,13 +35,15 @@
3035
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
3136
import org.springframework.context.annotation.Bean;
3237
import org.springframework.context.annotation.Configuration;
38+
import org.springframework.test.util.ReflectionTestUtils;
3339

3440
import static org.assertj.core.api.Assertions.assertThat;
3541

3642
/**
3743
* Tests for {@link OtlpLoggingAutoConfiguration}.
3844
*
3945
* @author Toshiaki Maki
46+
* @author Moritz Halbritter
4047
*/
4148
class OtlpLoggingAutoConfigurationTests {
4249

@@ -118,7 +125,6 @@ void shouldBackOffWhenCustomOtlpLoggingConnectionDetailsIsDefined() {
118125
assertThat(otlpHttpLogRecordExporter).extracting("delegate.httpSender.url")
119126
.isEqualTo(HttpUrl.get("https://otel.example.com/v1/logs"));
120127
});
121-
122128
}
123129

124130
@Test
@@ -155,31 +161,74 @@ void shouldUseGrpcExporterIfTransportIsSetToGrpc() {
155161
});
156162
}
157163

164+
@Test
165+
@SuppressWarnings("unchecked")
166+
void httpShouldUseMeterProviderIfSet() {
167+
this.contextRunner.withUserConfiguration(MeterProviderConfiguration.class)
168+
.withPropertyValues("management.otlp.logging.endpoint=http://localhost:4318/v1/logs")
169+
.run((context) -> {
170+
OtlpHttpLogRecordExporter otlpHttpLogRecordExporter = context.getBean(OtlpHttpLogRecordExporter.class);
171+
OtlpHttpLogRecordExporterBuilder builder = otlpHttpLogRecordExporter.toBuilder();
172+
Supplier<MeterProvider> meterProviderSupplier = (Supplier<MeterProvider>) ReflectionTestUtils
173+
.getField(ReflectionTestUtils.getField(builder, "delegate"), "meterProviderSupplier");
174+
assertThat(meterProviderSupplier).isNotNull();
175+
assertThat(meterProviderSupplier.get()).isSameAs(MeterProviderConfiguration.meterProvider);
176+
});
177+
}
178+
179+
@Test
180+
@SuppressWarnings("unchecked")
181+
void grpcShouldUseMeterProviderIfSet() {
182+
this.contextRunner.withUserConfiguration(MeterProviderConfiguration.class)
183+
.withPropertyValues("management.otlp.logging.endpoint=http://localhost:4318/v1/logs",
184+
"management.otlp.logging.transport=grpc")
185+
.run((context) -> {
186+
OtlpGrpcLogRecordExporter otlpGrpcLogRecordExporter = context.getBean(OtlpGrpcLogRecordExporter.class);
187+
OtlpGrpcLogRecordExporterBuilder builder = otlpGrpcLogRecordExporter.toBuilder();
188+
Supplier<MeterProvider> meterProviderSupplier = (Supplier<MeterProvider>) ReflectionTestUtils
189+
.getField(ReflectionTestUtils.getField(builder, "delegate"), "meterProviderSupplier");
190+
assertThat(meterProviderSupplier).isNotNull();
191+
assertThat(meterProviderSupplier.get()).isSameAs(MeterProviderConfiguration.meterProvider);
192+
});
193+
}
194+
195+
@Configuration(proxyBeanMethods = false)
196+
private static final class MeterProviderConfiguration {
197+
198+
static final MeterProvider meterProvider = (instrumentationScopeName) -> null;
199+
200+
@Bean
201+
MeterProvider meterProvider() {
202+
return meterProvider;
203+
}
204+
205+
}
206+
158207
@Configuration(proxyBeanMethods = false)
159-
public static class CustomHttpExporterConfiguration {
208+
private static final class CustomHttpExporterConfiguration {
160209

161210
@Bean
162-
public OtlpHttpLogRecordExporter customOtlpHttpLogRecordExporter() {
211+
OtlpHttpLogRecordExporter customOtlpHttpLogRecordExporter() {
163212
return OtlpHttpLogRecordExporter.builder().build();
164213
}
165214

166215
}
167216

168217
@Configuration(proxyBeanMethods = false)
169-
public static class CustomGrpcExporterConfiguration {
218+
private static final class CustomGrpcExporterConfiguration {
170219

171220
@Bean
172-
public OtlpGrpcLogRecordExporter customOtlpGrpcLogRecordExporter() {
221+
OtlpGrpcLogRecordExporter customOtlpGrpcLogRecordExporter() {
173222
return OtlpGrpcLogRecordExporter.builder().build();
174223
}
175224

176225
}
177226

178227
@Configuration(proxyBeanMethods = false)
179-
public static class CustomOtlpLoggingConnectionDetails {
228+
private static final class CustomOtlpLoggingConnectionDetails {
180229

181230
@Bean
182-
public OtlpLoggingConnectionDetails customOtlpLoggingConnectionDetails() {
231+
OtlpLoggingConnectionDetails customOtlpLoggingConnectionDetails() {
183232
return (transport) -> "https://otel.example.com/v1/logs";
184233
}
185234

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

+50-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,8 +16,13 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.tracing.otlp;
1818

19+
import java.util.function.Supplier;
20+
21+
import io.opentelemetry.api.metrics.MeterProvider;
1922
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
23+
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder;
2024
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
25+
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder;
2126
import io.opentelemetry.sdk.trace.export.SpanExporter;
2227
import okhttp3.HttpUrl;
2328
import org.junit.jupiter.api.Test;
@@ -28,6 +33,7 @@
2833
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
2934
import org.springframework.context.annotation.Bean;
3035
import org.springframework.context.annotation.Configuration;
36+
import org.springframework.test.util.ReflectionTestUtils;
3137

3238
import static org.assertj.core.api.Assertions.assertThat;
3339

@@ -147,6 +153,49 @@ void testConnectionFactoryWithOverridesWhenUsingCustomConnectionDetails() {
147153
});
148154
}
149155

156+
@Test
157+
@SuppressWarnings("unchecked")
158+
void httpShouldUseMeterProviderIfSet() {
159+
this.contextRunner.withUserConfiguration(MeterProviderConfiguration.class)
160+
.withPropertyValues("management.otlp.tracing.endpoint=http://localhost:4318/v1/traces")
161+
.run((context) -> {
162+
OtlpHttpSpanExporter otlpHttpSpanExporter = context.getBean(OtlpHttpSpanExporter.class);
163+
OtlpHttpSpanExporterBuilder builder = otlpHttpSpanExporter.toBuilder();
164+
Supplier<MeterProvider> meterProviderSupplier = (Supplier<MeterProvider>) ReflectionTestUtils
165+
.getField(ReflectionTestUtils.getField(builder, "delegate"), "meterProviderSupplier");
166+
assertThat(meterProviderSupplier).isNotNull();
167+
assertThat(meterProviderSupplier.get()).isSameAs(MeterProviderConfiguration.meterProvider);
168+
});
169+
}
170+
171+
@Test
172+
@SuppressWarnings("unchecked")
173+
void grpcShouldUseMeterProviderIfSet() {
174+
this.contextRunner.withUserConfiguration(MeterProviderConfiguration.class)
175+
.withPropertyValues("management.otlp.tracing.endpoint=http://localhost:4318/v1/traces",
176+
"management.otlp.tracing.transport=grpc")
177+
.run((context) -> {
178+
OtlpGrpcSpanExporter otlpGrpcSpanExporter = context.getBean(OtlpGrpcSpanExporter.class);
179+
OtlpGrpcSpanExporterBuilder builder = otlpGrpcSpanExporter.toBuilder();
180+
Supplier<MeterProvider> meterProviderSupplier = (Supplier<MeterProvider>) ReflectionTestUtils
181+
.getField(ReflectionTestUtils.getField(builder, "delegate"), "meterProviderSupplier");
182+
assertThat(meterProviderSupplier).isNotNull();
183+
assertThat(meterProviderSupplier.get()).isSameAs(MeterProviderConfiguration.meterProvider);
184+
});
185+
}
186+
187+
@Configuration(proxyBeanMethods = false)
188+
private static final class MeterProviderConfiguration {
189+
190+
static final MeterProvider meterProvider = (instrumentationScopeName) -> null;
191+
192+
@Bean
193+
MeterProvider meterProvider() {
194+
return meterProvider;
195+
}
196+
197+
}
198+
150199
@Configuration(proxyBeanMethods = false)
151200
private static final class CustomHttpExporterConfiguration {
152201

0 commit comments

Comments
 (0)