diff --git a/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpGrpcLogRecordExporterBuilderCustomizer.java b/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpGrpcLogRecordExporterBuilderCustomizer.java new file mode 100644 index 00000000000..6d02983dc21 --- /dev/null +++ b/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpGrpcLogRecordExporterBuilderCustomizer.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2026 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.opentelemetry.autoconfigure.logging.otlp; + +import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporterBuilder; + +/** + * Callback interface that can be implemented by beans wishing to customize the + * {@link OtlpGrpcLogRecordExporterBuilder} whilst retaining default auto-configuration. + * + * @author Joaquin Santana + * @since 4.1.0 + */ +@FunctionalInterface +public interface OtlpGrpcLogRecordExporterBuilderCustomizer { + + /** + * Customize the {@link OtlpGrpcLogRecordExporterBuilder}. + * @param builder the builder to customize + */ + void customize(OtlpGrpcLogRecordExporterBuilder builder); + +} diff --git a/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpHttpLogRecordExporterBuilderCustomizer.java b/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpHttpLogRecordExporterBuilderCustomizer.java new file mode 100644 index 00000000000..e49a250cc2b --- /dev/null +++ b/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpHttpLogRecordExporterBuilderCustomizer.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2026 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.opentelemetry.autoconfigure.logging.otlp; + +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporterBuilder; + +/** + * Callback interface that can be implemented by beans wishing to customize the + * {@link OtlpHttpLogRecordExporterBuilder} whilst retaining default auto-configuration. + * + * @author Joaquin Santana + * @since 4.1.0 + */ +@FunctionalInterface +public interface OtlpHttpLogRecordExporterBuilderCustomizer { + + /** + * Customize the {@link OtlpHttpLogRecordExporterBuilder}. + * @param builder the builder to customize + */ + void customize(OtlpHttpLogRecordExporterBuilder builder); + +} diff --git a/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpLoggingConfigurations.java b/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpLoggingConfigurations.java index 33e3bb7742b..4763b7affc6 100644 --- a/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpLoggingConfigurations.java +++ b/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpLoggingConfigurations.java @@ -88,7 +88,8 @@ final class OtlpLoggingConfigurations { @ConditionalOnProperty(name = "management.opentelemetry.logging.export.otlp.transport", havingValue = "http", matchIfMissing = true) OtlpHttpLogRecordExporter otlpHttpLogRecordExporter(OtlpLoggingProperties properties, - OtlpLoggingConnectionDetails connectionDetails, ObjectProvider meterProvider) { + OtlpLoggingConnectionDetails connectionDetails, ObjectProvider meterProvider, + ObjectProvider customizers) { OtlpHttpLogRecordExporterBuilder builder = OtlpHttpLogRecordExporter.builder() .setEndpoint(connectionDetails.getUrl(Transport.HTTP)) .setTimeout(properties.getTimeout()) @@ -96,13 +97,15 @@ final class OtlpLoggingConfigurations { .setCompression(properties.getCompression().name().toLowerCase(Locale.US)); properties.getHeaders().forEach(builder::addHeader); meterProvider.ifAvailable(builder::setMeterProvider); + customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); return builder.build(); } @Bean @ConditionalOnProperty(name = "management.opentelemetry.logging.export.otlp.transport", havingValue = "grpc") OtlpGrpcLogRecordExporter otlpGrpcLogRecordExporter(OtlpLoggingProperties properties, - OtlpLoggingConnectionDetails connectionDetails, ObjectProvider meterProvider) { + OtlpLoggingConnectionDetails connectionDetails, ObjectProvider meterProvider, + ObjectProvider customizers) { OtlpGrpcLogRecordExporterBuilder builder = OtlpGrpcLogRecordExporter.builder() .setEndpoint(connectionDetails.getUrl(Transport.GRPC)) .setTimeout(properties.getTimeout()) @@ -110,6 +113,7 @@ final class OtlpLoggingConfigurations { .setCompression(properties.getCompression().name().toLowerCase(Locale.US)); properties.getHeaders().forEach(builder::addHeader); meterProvider.ifAvailable(builder::setMeterProvider); + customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); return builder.build(); } diff --git a/module/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpLoggingAutoConfigurationTests.java b/module/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpLoggingAutoConfigurationTests.java index 20709e64371..41313a95de1 100644 --- a/module/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpLoggingAutoConfigurationTests.java +++ b/module/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpLoggingAutoConfigurationTests.java @@ -16,6 +16,7 @@ package org.springframework.boot.opentelemetry.autoconfigure.logging.otlp; +import java.time.Duration; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; @@ -224,6 +225,47 @@ class OtlpLoggingAutoConfigurationTests { }); } + @Test + void shouldCustomizeHttpTransportWithOtlpHttpLogRecordExporterBuilderCustomizer() { + Duration connectTimeout = Duration.ofMinutes(20); + Duration timeout = Duration.ofMinutes(10); + this.contextRunner + .withBean("httpCustomizer1", OtlpHttpLogRecordExporterBuilderCustomizer.class, + () -> (builder) -> builder.setConnectTimeout(connectTimeout)) + .withBean("httpCustomizer2", OtlpHttpLogRecordExporterBuilderCustomizer.class, + () -> (builder) -> builder.setTimeout(timeout)) + .withPropertyValues("management.opentelemetry.logging.export.otlp.endpoint=http://localhost:4318/v1/logs") + .run((context) -> { + assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class) + .hasSingleBean(LogRecordExporter.class); + OtlpHttpLogRecordExporter exporter = context.getBean(OtlpHttpLogRecordExporter.class); + assertThat(exporter).extracting("delegate.httpSender.client") + .hasFieldOrPropertyWithValue("connectTimeoutMillis", (int) connectTimeout.toMillis()) + .hasFieldOrPropertyWithValue("callTimeoutMillis", (int) timeout.toMillis()); + }); + } + + @Test + void shouldCustomizeGrpcTransportWhenEnabledWithOtlpGrpcLogRecordExporterBuilderCustomizer() { + Duration timeout = Duration.ofMinutes(10); + Duration connectTimeout = Duration.ofMinutes(20); + this.contextRunner + .withBean("grpcCustomizer1", OtlpGrpcLogRecordExporterBuilderCustomizer.class, + () -> (builder) -> builder.setConnectTimeout(connectTimeout)) + .withBean("grpcCustomizer2", OtlpGrpcLogRecordExporterBuilderCustomizer.class, + () -> (builder) -> builder.setTimeout(timeout)) + .withPropertyValues("management.opentelemetry.logging.export.otlp.endpoint=http://localhost:4318/v1/logs", + "management.opentelemetry.logging.export.otlp.transport=grpc") + .run((context) -> { + assertThat(context).hasSingleBean(OtlpGrpcLogRecordExporter.class) + .hasSingleBean(LogRecordExporter.class); + OtlpGrpcLogRecordExporter exporter = context.getBean(OtlpGrpcLogRecordExporter.class); + assertThat(exporter).extracting("delegate.grpcSender.client") + .hasFieldOrPropertyWithValue("connectTimeoutMillis", (int) connectTimeout.toMillis()) + .hasFieldOrPropertyWithValue("callTimeoutMillis", (int) timeout.toMillis()); + }); + } + @Configuration(proxyBeanMethods = false) public static class MultipleSdkLoggerProviderBuilderCustomizersConfig {