Add OTLP span exporter builder customizers

This commit adds customizers for both OtlpHttpSpanExporterBuilder
and OtlpGrpcSpanExporterBuilder.

See gh-44900

Signed-off-by: Dmytro Nosan <dimanosan@gmail.com>
This commit is contained in:
Dmytro Nosan 2025-03-13 17:34:10 +02:00 committed by Moritz Halbritter
parent 49db6dd276
commit 19004e028b
5 changed files with 122 additions and 2 deletions

View File

@ -0,0 +1,37 @@
/*
* Copyright 2012-2025 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.otlp;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder;
/**
* Callback interface that can be implemented by beans wishing to customize the
* {@link OtlpGrpcSpanExporterBuilder} whilst retaining default auto-configuration.
*
* @author Dmytro Nosan
* @since 3.5.0
*/
@FunctionalInterface
public interface OtlpGrpcSpanExporterBuilderCustomizer {
/**
* Customize the {@link OtlpGrpcSpanExporterBuilder}.
* @param builder the builder to customize
*/
void customize(OtlpGrpcSpanExporterBuilder builder);
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2012-2025 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.otlp;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder;
/**
* Callback interface that can be implemented by beans wishing to customize the
* {@link OtlpHttpSpanExporterBuilder} whilst retaining default auto-configuration.
*
* @author Dmytro Nosan
* @since 3.5.0
*/
@FunctionalInterface
public interface OtlpHttpSpanExporterBuilderCustomizer {
/**
* Customize the {@link OtlpHttpSpanExporterBuilder}.
* @param builder the builder to customize
*/
void customize(OtlpHttpSpanExporterBuilder builder);
}

View File

@ -83,7 +83,8 @@ class OtlpTracingConfigurations {
@Bean
@ConditionalOnProperty(name = "management.otlp.tracing.transport", havingValue = "http", matchIfMissing = true)
OtlpHttpSpanExporter otlpHttpSpanExporter(OtlpTracingProperties properties,
OtlpTracingConnectionDetails connectionDetails, ObjectProvider<MeterProvider> meterProvider) {
OtlpTracingConnectionDetails connectionDetails, ObjectProvider<MeterProvider> meterProvider,
ObjectProvider<OtlpHttpSpanExporterBuilderCustomizer> customizers) {
OtlpHttpSpanExporterBuilder builder = OtlpHttpSpanExporter.builder()
.setEndpoint(connectionDetails.getUrl(Transport.HTTP))
.setTimeout(properties.getTimeout())
@ -91,13 +92,15 @@ class OtlpTracingConfigurations {
.setCompression(properties.getCompression().name().toLowerCase(Locale.ROOT));
properties.getHeaders().forEach(builder::addHeader);
meterProvider.ifAvailable(builder::setMeterProvider);
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
@Bean
@ConditionalOnProperty(name = "management.otlp.tracing.transport", havingValue = "grpc")
OtlpGrpcSpanExporter otlpGrpcSpanExporter(OtlpTracingProperties properties,
OtlpTracingConnectionDetails connectionDetails, ObjectProvider<MeterProvider> meterProvider) {
OtlpTracingConnectionDetails connectionDetails, ObjectProvider<MeterProvider> meterProvider,
ObjectProvider<OtlpGrpcSpanExporterBuilderCustomizer> customizers) {
OtlpGrpcSpanExporterBuilder builder = OtlpGrpcSpanExporter.builder()
.setEndpoint(connectionDetails.getUrl(Transport.GRPC))
.setTimeout(properties.getTimeout())
@ -105,6 +108,7 @@ class OtlpTracingConfigurations {
.setCompression(properties.getCompression().name().toLowerCase(Locale.ROOT));
properties.getHeaders().forEach(builder::addHeader);
meterProvider.ifAvailable(builder::setMeterProvider);
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}

View File

@ -16,6 +16,7 @@
package org.springframework.boot.actuate.autoconfigure.tracing.otlp;
import java.time.Duration;
import java.util.List;
import java.util.function.Supplier;
@ -230,6 +231,45 @@ class OtlpTracingAutoConfigurationTests {
});
}
@Test
void shouldCustomizeHttpTransportWithOtlpHttpSpanExporterBuilderCustomizer() {
Duration connectTimeout = Duration.ofMinutes(20);
Duration timeout = Duration.ofMinutes(10);
this.contextRunner
.withBean("httpCustomizer1", OtlpHttpSpanExporterBuilderCustomizer.class,
() -> (builder) -> builder.setConnectTimeout(connectTimeout))
.withBean("httpCustomizer2", OtlpHttpSpanExporterBuilderCustomizer.class,
() -> (builder) -> builder.setTimeout(timeout))
.withPropertyValues("management.otlp.tracing.endpoint=http://localhost:4317/v1/traces")
.run((context) -> {
assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class).hasSingleBean(SpanExporter.class);
OtlpHttpSpanExporter exporter = context.getBean(OtlpHttpSpanExporter.class);
assertThat(exporter).extracting("delegate.httpSender.client")
.hasFieldOrPropertyWithValue("connectTimeoutMillis", (int) connectTimeout.toMillis())
.hasFieldOrPropertyWithValue("callTimeoutMillis", (int) timeout.toMillis());
});
}
@Test
void shouldCustomizeGrpcTransportWhenEnabledWithOtlpGrpcSpanExporterBuilderCustomizer() {
Duration timeout = Duration.ofMinutes(10);
Duration connectTimeout = Duration.ofMinutes(20);
this.contextRunner
.withBean("grpcCustomizer1", OtlpGrpcSpanExporterBuilderCustomizer.class,
() -> (builder) -> builder.setConnectTimeout(connectTimeout))
.withBean("grpcCustomizer2", OtlpGrpcSpanExporterBuilderCustomizer.class,
() -> (builder) -> builder.setTimeout(timeout))
.withPropertyValues("management.otlp.tracing.endpoint=http://localhost:4317/v1/traces",
"management.otlp.tracing.transport=grpc")
.run((context) -> {
assertThat(context).hasSingleBean(OtlpGrpcSpanExporter.class).hasSingleBean(SpanExporter.class);
OtlpGrpcSpanExporter exporter = context.getBean(OtlpGrpcSpanExporter.class);
assertThat(exporter).extracting("delegate.grpcSender.client")
.hasFieldOrPropertyWithValue("connectTimeoutMillis", (int) connectTimeout.toMillis())
.hasFieldOrPropertyWithValue("callTimeoutMillis", (int) timeout.toMillis());
});
}
@Configuration(proxyBeanMethods = false)
private static final class MeterProviderConfiguration {

View File

@ -150,6 +150,8 @@ Tracing with OpenTelemetry and reporting using OTLP requires the following depen
Use the `management.otlp.tracing.*` configuration properties to configure reporting using OTLP.
NOTE: If you need to apply advanced customizations to OTLP span exporters, consider registering javadoc:org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpHttpSpanExporterBuilderCustomizer[] or javadoc:org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpGrpcSpanExporterBuilderCustomizer[] beans. These will be invoked **before** the creation of the `OtlpHttpSpanExporter` or `OtlpGrpcSpanExporter`. The customizers take precedence over anything applied by the auto-configuration.
[[actuator.micrometer-tracing.tracer-implementations.brave-zipkin]]