From f3339978553987a1831dbdaf1c82f02b7fdac5be Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 27 May 2025 15:27:27 -0700 Subject: [PATCH] Create spring-boot-opentelemetry module Renames `management.otlp.logging` properties to `management.opentelemetry.logging`. --- settings.gradle | 1 + .../build.gradle | 1 + ...OpenTelemetryLoggingAutoConfiguration.java | 61 --- .../otlp/OtlpLoggingConfigurations.java | 112 ------ .../OtlpMetricsExportAutoConfiguration.java | 2 +- .../OtlpMetricsPropertiesConfigAdapter.java | 4 +- .../OpenTelemetryAutoConfiguration.java | 75 ---- ...ot.autoconfigure.AutoConfiguration.imports | 5 - ...elemetryLoggingAutoConfigurationTests.java | 226 ----------- ...lpMetricsPropertiesConfigAdapterTests.java | 2 +- .../OpenTelemetryAutoConfigurationTests.java | 190 --------- .../BaggagePropagationIntegrationTests.java | 10 +- ...elemetryTracingAutoConfigurationTests.java | 2 +- ...cingAutoConfigurationIntegrationTests.java | 4 +- .../ConditionalOnEnabledLoggingExport.java | 0 .../LogFileWebEndpointAutoConfiguration.java | 0 .../logging/LogFileWebEndpointProperties.java | 0 .../LoggersEndpointAutoConfiguration.java | 0 .../OnEnabledLoggingExportCondition.java | 0 .../autoconfigure/logging/package-info.java | 0 ...ot.autoconfigure.AutoConfiguration.imports | 2 + ...FileWebEndpointAutoConfigurationTests.java | 0 ...LoggersEndpointAutoConfigurationTests.java | 0 .../OnEnabledLoggingExportConditionTests.java | 0 .../build.gradle | 3 + ...nectionDetailsFactoryIntegrationTests.java | 6 +- ...nectionDetailsFactoryIntegrationTests.java | 6 +- ...DockerComposeConnectionDetailsFactory.java | 15 +- .../spring-boot-docs/build.gradle | 2 + .../reference/pages/actuator/loggers.adoc | 5 +- .../pages/features/dev-services.adoc | 2 +- .../pages/testing/testcontainers.adoc | 2 +- .../spring-boot-opentelemetry/build.gradle | 27 ++ ...penTelemetryLoggingConnectionDetails.java} | 9 +- ...LoggingConnectionDetailsConfiguration.java | 63 +++ ...emetryLoggingExportAutoConfiguration.java} | 17 +- ...OpenTelemetryLoggingExportProperties.java} | 10 +- ...elemetryLoggingTransportConfiguration.java | 75 ++++ .../autoconfigure/logging}/Transport.java | 6 +- .../autoconfigure/logging}/package-info.java | 4 +- .../OpenTelemetryProperties.java | 2 +- .../OpenTelemetryResourceAttributes.java | 24 +- .../OpenTelemetrySdkAutoConfiguration.java | 109 ++++++ .../SdkLoggerProviderBuilderCustomizer.java | 4 +- .../autoconfigure}/package-info.java | 2 +- ...itional-spring-configuration-metadata.json | 98 +++++ ...ot.autoconfigure.AutoConfiguration.imports | 2 + ...ortAutoConfigurationIntegrationTests.java} | 21 +- ...yLoggingExportAutoConfigurationTests.java} | 187 +++++---- .../OpenTelemetryPropertiesTests.java | 2 +- .../OpenTelemetryResourceAttributesTests.java | 4 +- ...penTelemetrySdkAutoConfigurationTests.java | 364 ++++++++++++++++++ .../build.gradle | 1 + ...nectionDetailsFactoryIntegrationTests.java | 10 +- ...nectionDetailsFactoryIntegrationTests.java | 6 +- ...gingContainerConnectionDetailsFactory.java | 14 +- ...gingContainerConnectionDetailsFactory.java | 14 +- 57 files changed, 973 insertions(+), 840 deletions(-) delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/OpenTelemetryLoggingAutoConfiguration.java delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingConfigurations.java delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfiguration.java delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/OpenTelemetryLoggingAutoConfigurationTests.java delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfigurationTests.java rename spring-boot-project/{spring-boot-actuator-autoconfigure-all => spring-boot-actuator-autoconfigure}/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/ConditionalOnEnabledLoggingExport.java (100%) rename spring-boot-project/{spring-boot-actuator-autoconfigure-all => spring-boot-actuator-autoconfigure}/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointAutoConfiguration.java (100%) rename spring-boot-project/{spring-boot-actuator-autoconfigure-all => spring-boot-actuator-autoconfigure}/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointProperties.java (100%) rename spring-boot-project/{spring-boot-actuator-autoconfigure-all => spring-boot-actuator-autoconfigure}/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LoggersEndpointAutoConfiguration.java (100%) rename spring-boot-project/{spring-boot-actuator-autoconfigure-all => spring-boot-actuator-autoconfigure}/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/OnEnabledLoggingExportCondition.java (100%) rename spring-boot-project/{spring-boot-actuator-autoconfigure-all => spring-boot-actuator-autoconfigure}/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/package-info.java (100%) rename spring-boot-project/{spring-boot-actuator-autoconfigure-all => spring-boot-actuator-autoconfigure}/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointAutoConfigurationTests.java (100%) rename spring-boot-project/{spring-boot-actuator-autoconfigure-all => spring-boot-actuator-autoconfigure}/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/LoggersEndpointAutoConfigurationTests.java (100%) rename spring-boot-project/{spring-boot-actuator-autoconfigure-all => spring-boot-actuator-autoconfigure}/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/OnEnabledLoggingExportConditionTests.java (100%) create mode 100644 spring-boot-project/spring-boot-opentelemetry/build.gradle rename spring-boot-project/{spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingConnectionDetails.java => spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingConnectionDetails.java} (77%) create mode 100644 spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingConnectionDetailsConfiguration.java rename spring-boot-project/{spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingAutoConfiguration.java => spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingExportAutoConfiguration.java} (61%) rename spring-boot-project/{spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingProperties.java => spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingExportProperties.java} (90%) create mode 100644 spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingTransportConfiguration.java rename spring-boot-project/{spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp => spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging}/Transport.java (85%) rename spring-boot-project/{spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp => spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging}/package-info.java (81%) rename spring-boot-project/{spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry => spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure}/OpenTelemetryProperties.java (94%) rename spring-boot-project/{spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry => spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure}/OpenTelemetryResourceAttributes.java (91%) create mode 100644 spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetrySdkAutoConfiguration.java rename spring-boot-project/{spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging => spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure}/SdkLoggerProviderBuilderCustomizer.java (93%) rename spring-boot-project/{spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry => spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure}/package-info.java (90%) create mode 100644 spring-boot-project/spring-boot-opentelemetry/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 spring-boot-project/spring-boot-opentelemetry/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports rename spring-boot-project/{spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingAutoConfigurationIntegrationTests.java => spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingExportAutoConfigurationIntegrationTests.java} (81%) rename spring-boot-project/{spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingAutoConfigurationTests.java => spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingExportAutoConfigurationTests.java} (50%) rename spring-boot-project/{spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry => spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure}/OpenTelemetryPropertiesTests.java (96%) rename spring-boot-project/{spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry => spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure}/OpenTelemetryResourceAttributesTests.java (99%) create mode 100644 spring-boot-project/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetrySdkAutoConfigurationTests.java diff --git a/settings.gradle b/settings.gradle index 7848b7dc5d..9334004c0d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -103,6 +103,7 @@ include "spring-boot-project:spring-boot-metrics" include "spring-boot-project:spring-boot-mongodb" include "spring-boot-project:spring-boot-mustache" include "spring-boot-project:spring-boot-netty" +include "spring-boot-project:spring-boot-opentelemetry" include "spring-boot-project:spring-boot-parent" include "spring-boot-project:spring-boot-pulsar" include "spring-boot-project:spring-boot-quartz" diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/build.gradle b/spring-boot-project/spring-boot-actuator-autoconfigure-all/build.gradle index b1ea60e154..0cb05cc1b3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/build.gradle +++ b/spring-boot-project/spring-boot-actuator-autoconfigure-all/build.gradle @@ -39,6 +39,7 @@ dependencies { optional(project(":spring-boot-project:spring-boot-jsonb")) optional(project(":spring-boot-project:spring-boot-kafka")) optional(project(":spring-boot-project:spring-boot-metrics")) + optional(project(":spring-boot-project:spring-boot-opentelemetry")) optional(project(":spring-boot-project:spring-boot-r2dbc")) optional(project(":spring-boot-project:spring-boot-restclient")) optional(project(":spring-boot-project:spring-boot-security-oauth2-client")) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/OpenTelemetryLoggingAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/OpenTelemetryLoggingAutoConfiguration.java deleted file mode 100644 index 374e135c5d..0000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/OpenTelemetryLoggingAutoConfiguration.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.logging; - -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.sdk.logs.LogRecordProcessor; -import io.opentelemetry.sdk.logs.SdkLoggerProvider; -import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; -import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor; -import io.opentelemetry.sdk.logs.export.LogRecordExporter; -import io.opentelemetry.sdk.resources.Resource; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; - -/** - * {@link EnableAutoConfiguration Auto-configuration} for OpenTelemetry logging. - * - * @author Toshiaki Maki - * @since 3.4.0 - */ -@AutoConfiguration -@ConditionalOnClass({ SdkLoggerProvider.class, OpenTelemetry.class }) -public class OpenTelemetryLoggingAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - BatchLogRecordProcessor batchLogRecordProcessor(ObjectProvider logRecordExporters) { - return BatchLogRecordProcessor.builder(LogRecordExporter.composite(logRecordExporters.orderedStream().toList())) - .build(); - } - - @Bean - @ConditionalOnMissingBean - SdkLoggerProvider otelSdkLoggerProvider(Resource resource, ObjectProvider logRecordProcessors, - ObjectProvider customizers) { - SdkLoggerProviderBuilder builder = SdkLoggerProvider.builder().setResource(resource); - logRecordProcessors.orderedStream().forEach(builder::addLogRecordProcessor); - customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); - return builder.build(); - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingConfigurations.java b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingConfigurations.java deleted file mode 100644 index 313fb4f806..0000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingConfigurations.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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.logging.otlp; - -import java.util.Locale; - -import io.opentelemetry.api.metrics.MeterProvider; -import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; -import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporterBuilder; -import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; -import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporterBuilder; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.actuate.autoconfigure.logging.ConditionalOnEnabledLoggingExport; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.util.Assert; - -/** - * Configurations imported by {@link OtlpLoggingAutoConfiguration}. - * - * @author Toshiaki Maki - */ -final class OtlpLoggingConfigurations { - - @Configuration(proxyBeanMethods = false) - static class ConnectionDetails { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty("management.otlp.logging.endpoint") - OtlpLoggingConnectionDetails otlpLoggingConnectionDetails(OtlpLoggingProperties properties) { - return new PropertiesOtlpLoggingConnectionDetails(properties); - } - - /** - * Adapts {@link OtlpLoggingProperties} to {@link OtlpLoggingConnectionDetails}. - */ - static class PropertiesOtlpLoggingConnectionDetails implements OtlpLoggingConnectionDetails { - - private final OtlpLoggingProperties properties; - - PropertiesOtlpLoggingConnectionDetails(OtlpLoggingProperties properties) { - this.properties = properties; - } - - @Override - public String getUrl(Transport transport) { - Assert.state(transport == this.properties.getTransport(), - "Requested transport %s doesn't match configured transport %s".formatted(transport, - this.properties.getTransport())); - return this.properties.getEndpoint(); - } - - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnMissingBean({ OtlpGrpcLogRecordExporter.class, OtlpHttpLogRecordExporter.class }) - @ConditionalOnBean(OtlpLoggingConnectionDetails.class) - @ConditionalOnEnabledLoggingExport("otlp") - static class Exporters { - - @Bean - @ConditionalOnProperty(name = "management.otlp.logging.transport", havingValue = "http", matchIfMissing = true) - OtlpHttpLogRecordExporter otlpHttpLogRecordExporter(OtlpLoggingProperties properties, - OtlpLoggingConnectionDetails connectionDetails, ObjectProvider meterProvider) { - OtlpHttpLogRecordExporterBuilder builder = OtlpHttpLogRecordExporter.builder() - .setEndpoint(connectionDetails.getUrl(Transport.HTTP)) - .setTimeout(properties.getTimeout()) - .setConnectTimeout(properties.getConnectTimeout()) - .setCompression(properties.getCompression().name().toLowerCase(Locale.US)); - properties.getHeaders().forEach(builder::addHeader); - meterProvider.ifAvailable(builder::setMeterProvider); - return builder.build(); - } - - @Bean - @ConditionalOnProperty(name = "management.otlp.logging.transport", havingValue = "grpc") - OtlpGrpcLogRecordExporter otlpGrpcLogRecordExporter(OtlpLoggingProperties properties, - OtlpLoggingConnectionDetails connectionDetails, ObjectProvider meterProvider) { - OtlpGrpcLogRecordExporterBuilder builder = OtlpGrpcLogRecordExporter.builder() - .setEndpoint(connectionDetails.getUrl(Transport.GRPC)) - .setTimeout(properties.getTimeout()) - .setConnectTimeout(properties.getConnectTimeout()) - .setCompression(properties.getCompression().name().toLowerCase(Locale.US)); - properties.getHeaders().forEach(builder::addHeader); - meterProvider.ifAvailable(builder::setMeterProvider); - return builder.build(); - } - - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java index 055c59a48e..0c566c2ad8 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java @@ -24,7 +24,6 @@ import io.micrometer.registry.otlp.OtlpMetricsSender; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.metrics.export.ConditionalOnEnabledMetricsExport; import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryProperties; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -35,6 +34,7 @@ import org.springframework.boot.autoconfigure.thread.Threading; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.metrics.autoconfigure.CompositeMeterRegistryAutoConfiguration; import org.springframework.boot.metrics.autoconfigure.MetricsAutoConfiguration; +import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetryProperties; import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment; import org.springframework.core.task.VirtualThreadTaskExecutor; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesConfigAdapter.java index d3f9c4838b..b4641b5898 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesConfigAdapter.java @@ -28,8 +28,8 @@ import io.micrometer.registry.otlp.OtlpConfig; import org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsProperties.Meter; import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryPropertiesConfigAdapter; -import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryProperties; -import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryResourceAttributes; +import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetryProperties; +import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetryResourceAttributes; import org.springframework.core.env.Environment; import org.springframework.util.CollectionUtils; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfiguration.java deleted file mode 100644 index c19a7c8a95..0000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfiguration.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.opentelemetry; - -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.context.propagation.ContextPropagators; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.OpenTelemetrySdkBuilder; -import io.opentelemetry.sdk.logs.SdkLoggerProvider; -import io.opentelemetry.sdk.metrics.SdkMeterProvider; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.resources.ResourceBuilder; -import io.opentelemetry.sdk.trace.SdkTracerProvider; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.core.env.Environment; - -/** - * {@link EnableAutoConfiguration Auto-configuration} for OpenTelemetry. - * - * @author Moritz Halbritter - * @since 3.2.0 - */ -@AutoConfiguration -@ConditionalOnClass(OpenTelemetrySdk.class) -@EnableConfigurationProperties(OpenTelemetryProperties.class) -public class OpenTelemetryAutoConfiguration { - - @Bean - @ConditionalOnMissingBean(OpenTelemetry.class) - OpenTelemetrySdk openTelemetry(ObjectProvider tracerProvider, - ObjectProvider propagators, ObjectProvider loggerProvider, - ObjectProvider meterProvider) { - OpenTelemetrySdkBuilder builder = OpenTelemetrySdk.builder(); - tracerProvider.ifAvailable(builder::setTracerProvider); - propagators.ifAvailable(builder::setPropagators); - loggerProvider.ifAvailable(builder::setLoggerProvider); - meterProvider.ifAvailable(builder::setMeterProvider); - return builder.build(); - } - - @Bean - @ConditionalOnMissingBean - Resource openTelemetryResource(Environment environment, OpenTelemetryProperties properties) { - Resource resource = Resource.getDefault(); - return resource.merge(toResource(environment, properties)); - } - - private Resource toResource(Environment environment, OpenTelemetryProperties properties) { - ResourceBuilder builder = Resource.builder(); - new OpenTelemetryResourceAttributes(environment, properties.getResourceAttributes()).applyTo(builder::put); - return builder.build(); - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 70d975d5d2..405cb6497a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -11,10 +11,6 @@ org.springframework.boot.actuate.autoconfigure.context.ShutdownEndpointAutoConfi org.springframework.boot.actuate.autoconfigure.endpoint.jackson.JacksonEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.env.EnvironmentEndpointAutoConfiguration -org.springframework.boot.actuate.autoconfigure.logging.LogFileWebEndpointAutoConfiguration -org.springframework.boot.actuate.autoconfigure.logging.LoggersEndpointAutoConfiguration -org.springframework.boot.actuate.autoconfigure.logging.OpenTelemetryLoggingAutoConfiguration -org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingAutoConfiguration org.springframework.boot.actuate.autoconfigure.management.HeapDumpWebEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.management.ThreadDumpEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.data.RepositoryMetricsAutoConfiguration @@ -44,7 +40,6 @@ org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfig org.springframework.boot.actuate.autoconfigure.observation.web.client.HttpClientObservationsAutoConfiguration org.springframework.boot.actuate.autoconfigure.observation.web.reactive.WebFluxObservationAutoConfiguration org.springframework.boot.actuate.autoconfigure.observation.web.servlet.WebMvcObservationAutoConfiguration -org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration org.springframework.boot.actuate.autoconfigure.r2dbc.ConnectionFactoryHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.r2dbc.R2dbcObservationAutoConfiguration org.springframework.boot.actuate.autoconfigure.sbom.SbomEndpointAutoConfiguration diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/OpenTelemetryLoggingAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/OpenTelemetryLoggingAutoConfigurationTests.java deleted file mode 100644 index 4a3265585a..0000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/OpenTelemetryLoggingAutoConfigurationTests.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * 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.logging; - -import java.util.Collection; -import java.util.concurrent.atomic.AtomicInteger; - -import io.opentelemetry.context.Context; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.logs.LogRecordProcessor; -import io.opentelemetry.sdk.logs.ReadWriteLogRecord; -import io.opentelemetry.sdk.logs.SdkLoggerProvider; -import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; -import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor; -import io.opentelemetry.sdk.logs.export.LogRecordExporter; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.FilteredClassLoader; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link OpenTelemetryLoggingAutoConfiguration}. - * - * @author Toshiaki Maki - */ -class OpenTelemetryLoggingAutoConfigurationTests { - - private final ApplicationContextRunner contextRunner; - - OpenTelemetryLoggingAutoConfigurationTests() { - this.contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations - .of(OpenTelemetryAutoConfiguration.class, OpenTelemetryLoggingAutoConfiguration.class)); - } - - @Test - void shouldSupplyBeans() { - this.contextRunner.run((context) -> { - assertThat(context).hasSingleBean(BatchLogRecordProcessor.class); - assertThat(context).hasSingleBean(SdkLoggerProvider.class); - }); - } - - @ParameterizedTest - @ValueSource(strings = { "io.opentelemetry.sdk.logs", "io.opentelemetry.api" }) - void shouldNotSupplyBeansIfDependencyIsMissing(String packageName) { - this.contextRunner.withClassLoader(new FilteredClassLoader(packageName)).run((context) -> { - assertThat(context).doesNotHaveBean(BatchLogRecordProcessor.class); - assertThat(context).doesNotHaveBean(SdkLoggerProvider.class); - }); - } - - @Test - void shouldBackOffOnCustomBeans() { - this.contextRunner.withUserConfiguration(CustomConfig.class).run((context) -> { - assertThat(context).hasBean("customBatchLogRecordProcessor").hasSingleBean(BatchLogRecordProcessor.class); - assertThat(context.getBeansOfType(LogRecordProcessor.class)).hasSize(1); - assertThat(context).hasBean("customSdkLoggerProvider").hasSingleBean(SdkLoggerProvider.class); - }); - } - - @Test - void shouldAllowMultipleLogRecordExporters() { - this.contextRunner.withUserConfiguration(MultipleLogRecordExportersConfig.class).run((context) -> { - assertThat(context).hasSingleBean(BatchLogRecordProcessor.class); - assertThat(context.getBeansOfType(LogRecordExporter.class)).hasSize(2); - assertThat(context).hasBean("customLogRecordExporter1"); - assertThat(context).hasBean("customLogRecordExporter2"); - }); - } - - @Test - void shouldAllowMultipleLogRecordProcessorsInAdditionToBatchLogRecordProcessor() { - this.contextRunner.withUserConfiguration(MultipleLogRecordProcessorsConfig.class).run((context) -> { - assertThat(context).hasSingleBean(BatchLogRecordProcessor.class); - assertThat(context).hasSingleBean(SdkLoggerProvider.class); - assertThat(context.getBeansOfType(LogRecordProcessor.class)).hasSize(3); - assertThat(context).hasBean("batchLogRecordProcessor"); - assertThat(context).hasBean("customLogRecordProcessor1"); - assertThat(context).hasBean("customLogRecordProcessor2"); - }); - } - - @Test - void shouldAllowMultipleSdkLoggerProviderBuilderCustomizers() { - this.contextRunner.withUserConfiguration(MultipleSdkLoggerProviderBuilderCustomizersConfig.class) - .run((context) -> { - assertThat(context).hasSingleBean(SdkLoggerProvider.class); - assertThat(context.getBeansOfType(SdkLoggerProviderBuilderCustomizer.class)).hasSize(2); - assertThat(context).hasBean("customSdkLoggerProviderBuilderCustomizer1"); - assertThat(context).hasBean("customSdkLoggerProviderBuilderCustomizer2"); - assertThat(context - .getBean("customSdkLoggerProviderBuilderCustomizer1", NoopSdkLoggerProviderBuilderCustomizer.class) - .called()).isEqualTo(1); - assertThat(context - .getBean("customSdkLoggerProviderBuilderCustomizer2", NoopSdkLoggerProviderBuilderCustomizer.class) - .called()).isEqualTo(1); - }); - } - - @Configuration(proxyBeanMethods = false) - public static class CustomConfig { - - @Bean - public BatchLogRecordProcessor customBatchLogRecordProcessor() { - return BatchLogRecordProcessor.builder(new NoopLogRecordExporter()).build(); - } - - @Bean - public SdkLoggerProvider customSdkLoggerProvider() { - return SdkLoggerProvider.builder().build(); - } - - } - - @Configuration(proxyBeanMethods = false) - public static class MultipleLogRecordExportersConfig { - - @Bean - public LogRecordExporter customLogRecordExporter1() { - return new NoopLogRecordExporter(); - } - - @Bean - public LogRecordExporter customLogRecordExporter2() { - return new NoopLogRecordExporter(); - } - - } - - @Configuration(proxyBeanMethods = false) - public static class MultipleLogRecordProcessorsConfig { - - @Bean - public LogRecordProcessor customLogRecordProcessor1() { - return new NoopLogRecordProcessor(); - } - - @Bean - public LogRecordProcessor customLogRecordProcessor2() { - return new NoopLogRecordProcessor(); - } - - } - - @Configuration(proxyBeanMethods = false) - public static class MultipleSdkLoggerProviderBuilderCustomizersConfig { - - @Bean - public SdkLoggerProviderBuilderCustomizer customSdkLoggerProviderBuilderCustomizer1() { - return new NoopSdkLoggerProviderBuilderCustomizer(); - } - - @Bean - public SdkLoggerProviderBuilderCustomizer customSdkLoggerProviderBuilderCustomizer2() { - return new NoopSdkLoggerProviderBuilderCustomizer(); - } - - } - - static class NoopLogRecordExporter implements LogRecordExporter { - - @Override - public CompletableResultCode export(Collection logs) { - return CompletableResultCode.ofSuccess(); - } - - @Override - public CompletableResultCode flush() { - return CompletableResultCode.ofSuccess(); - } - - @Override - public CompletableResultCode shutdown() { - return CompletableResultCode.ofSuccess(); - } - - } - - static class NoopLogRecordProcessor implements LogRecordProcessor { - - @Override - public void onEmit(Context context, ReadWriteLogRecord logRecord) { - - } - - } - - static class NoopSdkLoggerProviderBuilderCustomizer implements SdkLoggerProviderBuilderCustomizer { - - final AtomicInteger called = new AtomicInteger(0); - - @Override - public void customize(SdkLoggerProviderBuilder builder) { - this.called.incrementAndGet(); - } - - int called() { - return this.called.get(); - } - - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesConfigAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesConfigAdapterTests.java index 7f050f1650..6b9ea3a461 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesConfigAdapterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesConfigAdapterTests.java @@ -26,7 +26,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsExportAutoConfiguration.PropertiesOtlpMetricsConnectionDetails; import org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsProperties.Meter; -import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryProperties; +import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetryProperties; import org.springframework.mock.env.MockEnvironment; import static org.assertj.core.api.Assertions.assertThat; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfigurationTests.java deleted file mode 100644 index 161d3f37f0..0000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfigurationTests.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * 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.opentelemetry; - -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.context.propagation.ContextPropagators; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.logs.SdkLoggerProvider; -import io.opentelemetry.sdk.metrics.SdkMeterProvider; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.context.annotation.ImportCandidates; -import org.springframework.boot.test.context.FilteredClassLoader; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link OpenTelemetryAutoConfiguration}. - * - * @author Moritz Halbritter - */ -class OpenTelemetryAutoConfigurationTests { - - private final ApplicationContextRunner runner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)); - - @Test - void isRegisteredInAutoConfigurationImports() { - assertThat(ImportCandidates.load(AutoConfiguration.class, null).getCandidates()) - .contains(OpenTelemetryAutoConfiguration.class.getName()); - } - - @Test - void shouldProvideBeans() { - this.runner.run((context) -> { - assertThat(context).hasSingleBean(OpenTelemetrySdk.class); - assertThat(context).hasSingleBean(Resource.class); - }); - } - - @Test - void shouldBackOffIfOpenTelemetryIsNotOnClasspath() { - this.runner.withClassLoader(new FilteredClassLoader("io.opentelemetry")).run((context) -> { - assertThat(context).doesNotHaveBean(OpenTelemetrySdk.class); - assertThat(context).doesNotHaveBean(Resource.class); - }); - } - - @Test - void backsOffOnUserSuppliedBeans() { - this.runner.withUserConfiguration(UserConfiguration.class).run((context) -> { - assertThat(context).hasSingleBean(OpenTelemetry.class); - assertThat(context).hasBean("customOpenTelemetry"); - assertThat(context).hasSingleBean(Resource.class); - assertThat(context).hasBean("customResource"); - }); - } - - @Test - void shouldApplySpringApplicationNameToResource() { - this.runner.withPropertyValues("spring.application.name=my-application").run((context) -> { - Resource resource = context.getBean(Resource.class); - assertThat(resource.getAttributes().asMap()) - .contains(entry(AttributeKey.stringKey("service.name"), "my-application")); - }); - } - - @Test - void shouldApplySpringApplicationGroupToResource() { - this.runner.withPropertyValues("spring.application.group=my-group").run((context) -> { - Resource resource = context.getBean(Resource.class); - assertThat(resource.getAttributes().asMap()) - .contains(entry(AttributeKey.stringKey("service.group"), "my-group")); - }); - } - - @Test - void shouldNotApplySpringApplicationGroupIfNotSet() { - this.runner.run((context) -> { - Resource resource = context.getBean(Resource.class); - assertThat(resource.getAttributes().asMap()).doesNotContainKey(AttributeKey.stringKey("service.group")); - }); - } - - @Test - void shouldApplyServiceNamespaceIfApplicationGroupIsSet() { - this.runner.withPropertyValues("spring.application.group=my-group").run((context) -> { - Resource resource = context.getBean(Resource.class); - assertThat(resource.getAttributes().asMap()).containsEntry(AttributeKey.stringKey("service.namespace"), - "my-group"); - }); - } - - @Test - void shouldNotApplyServiceNamespaceIfApplicationGroupIsNotSet() { - this.runner.run(((context) -> { - Resource resource = context.getBean(Resource.class); - assertThat(resource.getAttributes().asMap()).doesNotContainKey(AttributeKey.stringKey("service.namespace")); - })); - } - - @Test - void shouldFallbackToDefaultApplicationNameIfSpringApplicationNameIsNotSet() { - this.runner.run((context) -> { - Resource resource = context.getBean(Resource.class); - assertThat(resource.getAttributes().asMap()) - .contains(entry(AttributeKey.stringKey("service.name"), "unknown_service")); - }); - } - - @Test - void shouldApplyResourceAttributesFromProperties() { - this.runner.withPropertyValues("management.opentelemetry.resource-attributes.region=us-west").run((context) -> { - Resource resource = context.getBean(Resource.class); - assertThat(resource.getAttributes().asMap()).contains(entry(AttributeKey.stringKey("region"), "us-west")); - }); - } - - @Test - void shouldRegisterSdkTracerProviderIfAvailable() { - this.runner.withBean(SdkTracerProvider.class, () -> SdkTracerProvider.builder().build()).run((context) -> { - OpenTelemetry openTelemetry = context.getBean(OpenTelemetry.class); - assertThat(openTelemetry.getTracerProvider()).isNotNull(); - }); - } - - @Test - void shouldRegisterContextPropagatorsIfAvailable() { - this.runner.withBean(ContextPropagators.class, ContextPropagators::noop).run((context) -> { - OpenTelemetry openTelemetry = context.getBean(OpenTelemetry.class); - assertThat(openTelemetry.getPropagators()).isNotNull(); - }); - } - - @Test - void shouldRegisterSdkLoggerProviderIfAvailable() { - this.runner.withBean(SdkLoggerProvider.class, () -> SdkLoggerProvider.builder().build()).run((context) -> { - OpenTelemetry openTelemetry = context.getBean(OpenTelemetry.class); - assertThat(openTelemetry.getLogsBridge()).isNotNull(); - }); - } - - @Test - void shouldRegisterSdkMeterProviderIfAvailable() { - this.runner.withBean(SdkMeterProvider.class, () -> SdkMeterProvider.builder().build()).run((context) -> { - OpenTelemetry openTelemetry = context.getBean(OpenTelemetry.class); - assertThat(openTelemetry.getMeterProvider()).isNotNull(); - }); - } - - @Configuration(proxyBeanMethods = false) - static class UserConfiguration { - - @Bean - OpenTelemetry customOpenTelemetry() { - return mock(OpenTelemetry.class); - } - - @Bean - Resource customResource() { - return Resource.getDefault(); - } - - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BaggagePropagationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BaggagePropagationIntegrationTests.java index 0175feb9a2..d9c0222b73 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BaggagePropagationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BaggagePropagationIntegrationTests.java @@ -30,8 +30,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.slf4j.MDC; -import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetrySdkAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.testsupport.classpath.ForkedClassPath; import org.springframework.context.ApplicationContext; @@ -173,7 +173,7 @@ class BaggagePropagationIntegrationTests { @Override public ApplicationContextRunner get() { return new ApplicationContextRunner().withInitializer(new OtelApplicationContextInitializer()) - .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class, + .withConfiguration(AutoConfigurations.of(OpenTelemetrySdkAutoConfiguration.class, org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.class)) .withPropertyValues("management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", "management.tracing.baggage.correlation.fields=country-code,bp"); @@ -199,7 +199,7 @@ class BaggagePropagationIntegrationTests { @Override public ApplicationContextRunner get() { return new ApplicationContextRunner().withInitializer(new OtelApplicationContextInitializer()) - .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class, + .withConfiguration(AutoConfigurations.of(OpenTelemetrySdkAutoConfiguration.class, org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.class)) .withPropertyValues("management.tracing.propagation.type=W3C", "management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", @@ -239,7 +239,7 @@ class BaggagePropagationIntegrationTests { @Override public ApplicationContextRunner get() { return new ApplicationContextRunner().withInitializer(new OtelApplicationContextInitializer()) - .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class, + .withConfiguration(AutoConfigurations.of(OpenTelemetrySdkAutoConfiguration.class, org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.class)) .withPropertyValues("management.tracing.propagation.type=B3", "management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", @@ -253,7 +253,7 @@ class BaggagePropagationIntegrationTests { @Override public ApplicationContextRunner get() { return new ApplicationContextRunner().withInitializer(new OtelApplicationContextInitializer()) - .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class, + .withConfiguration(AutoConfigurations.of(OpenTelemetrySdkAutoConfiguration.class, org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.class)) .withPropertyValues("management.tracing.propagation.type=B3_MULTI", "management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryTracingAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryTracingAutoConfigurationTests.java index 38dd0c0091..981fb96358 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryTracingAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryTracingAutoConfigurationTests.java @@ -90,7 +90,7 @@ class OpenTelemetryTracingAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of( - org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration.class, + org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetrySdkAutoConfiguration.class, OpenTelemetryTracingAutoConfiguration.class)); @Test diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingAutoConfigurationIntegrationTests.java index afbec50900..e6392d0e2f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingAutoConfigurationIntegrationTests.java @@ -47,10 +47,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingAutoConfigurationIntegrationTests.MockGrpcServer.RecordedGrpcRequest; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetrySdkAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -65,7 +65,7 @@ class OtlpTracingAutoConfigurationIntegrationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("management.tracing.sampling.probability=1.0") .withConfiguration(AutoConfigurations.of(ObservationAutoConfiguration.class, - MicrometerTracingAutoConfiguration.class, OpenTelemetryAutoConfiguration.class, + MicrometerTracingAutoConfiguration.class, OpenTelemetrySdkAutoConfiguration.class, org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.class, OtlpTracingAutoConfiguration.class)); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/ConditionalOnEnabledLoggingExport.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/ConditionalOnEnabledLoggingExport.java similarity index 100% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/ConditionalOnEnabledLoggingExport.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/ConditionalOnEnabledLoggingExport.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointAutoConfiguration.java similarity index 100% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointAutoConfiguration.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointAutoConfiguration.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointProperties.java similarity index 100% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointProperties.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointProperties.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LoggersEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LoggersEndpointAutoConfiguration.java similarity index 100% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LoggersEndpointAutoConfiguration.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LoggersEndpointAutoConfiguration.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/OnEnabledLoggingExportCondition.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/OnEnabledLoggingExportCondition.java similarity index 100% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/OnEnabledLoggingExportCondition.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/OnEnabledLoggingExportCondition.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/package-info.java similarity index 100% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/package-info.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/package-info.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index b05f3c8936..3195288df7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -4,4 +4,6 @@ org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfi org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.info.InfoContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration +org.springframework.boot.actuate.autoconfigure.logging.LogFileWebEndpointAutoConfiguration +org.springframework.boot.actuate.autoconfigure.logging.LoggersEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointAutoConfigurationTests.java similarity index 100% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointAutoConfigurationTests.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointAutoConfigurationTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/LoggersEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/LoggersEndpointAutoConfigurationTests.java similarity index 100% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/LoggersEndpointAutoConfigurationTests.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/LoggersEndpointAutoConfigurationTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/OnEnabledLoggingExportConditionTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/OnEnabledLoggingExportConditionTests.java similarity index 100% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/OnEnabledLoggingExportConditionTests.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/OnEnabledLoggingExportConditionTests.java diff --git a/spring-boot-project/spring-boot-docker-compose-all/build.gradle b/spring-boot-project/spring-boot-docker-compose-all/build.gradle index b7e8527dce..d13ef56367 100644 --- a/spring-boot-project/spring-boot-docker-compose-all/build.gradle +++ b/spring-boot-project/spring-boot-docker-compose-all/build.gradle @@ -15,6 +15,7 @@ dependencies { dockerTestImplementation(project(":spring-boot-project:spring-boot-jdbc")) dockerTestImplementation(project(":spring-boot-project:spring-boot-flyway")) dockerTestImplementation(project(":spring-boot-project:spring-boot-liquibase")) + dockerTestImplementation(project(":spring-boot-project:spring-boot-opentelemetry")) dockerTestImplementation(project(":spring-boot-project:spring-boot-pulsar")) dockerTestImplementation(project(":spring-boot-project:spring-boot-r2dbc")) dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker")) @@ -28,6 +29,7 @@ dependencies { dockerTestRuntimeOnly("com.clickhouse:clickhouse-r2dbc") dockerTestRuntimeOnly("com.microsoft.sqlserver:mssql-jdbc") dockerTestRuntimeOnly("com.oracle.database.r2dbc:oracle-r2dbc") + dockerTestRuntimeOnly("io.opentelemetry:opentelemetry-exporter-otlp") dockerTestRuntimeOnly("io.r2dbc:r2dbc-mssql") dockerTestRuntimeOnly("org.postgresql:postgresql") dockerTestRuntimeOnly("org.postgresql:r2dbc-postgresql") @@ -50,6 +52,7 @@ dependencies { optional(project(":spring-boot-project:spring-boot-liquibase")) optional(project(":spring-boot-project:spring-boot-mongodb")) optional(project(":spring-boot-project:spring-boot-neo4j")) + optional(project(":spring-boot-project:spring-boot-opentelemetry")) optional(project(":spring-boot-project:spring-boot-pulsar")) optional(project(":spring-boot-project:spring-boot-r2dbc")) optional(project(":spring-boot-project:spring-boot-zipkin")) diff --git a/spring-boot-project/spring-boot-docker-compose-all/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/GrafanaOpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose-all/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/GrafanaOpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests.java index 022d1d1555..4aa5372a7b 100644 --- a/spring-boot-project/spring-boot-docker-compose-all/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/GrafanaOpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ b/spring-boot-project/spring-boot-docker-compose-all/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/GrafanaOpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -16,9 +16,9 @@ package org.springframework.boot.docker.compose.service.connection.otlp; -import org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingConnectionDetails; -import org.springframework.boot.actuate.autoconfigure.logging.otlp.Transport; import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingConnectionDetails; +import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.Transport; import org.springframework.boot.testsupport.container.TestImage; import static org.assertj.core.api.Assertions.assertThat; @@ -32,7 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat; class GrafanaOpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests { @DockerComposeTest(composeFile = "otlp-compose.yaml", image = TestImage.GRAFANA_OTEL_LGTM) - void runCreatesConnectionDetails(OtlpLoggingConnectionDetails connectionDetails) { + void runCreatesConnectionDetails(OpenTelemetryLoggingConnectionDetails connectionDetails) { assertThat(connectionDetails.getUrl(Transport.HTTP)).startsWith("http://").endsWith("/v1/logs"); assertThat(connectionDetails.getUrl(Transport.GRPC)).startsWith("http://").endsWith("/v1/logs"); } diff --git a/spring-boot-project/spring-boot-docker-compose-all/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose-all/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests.java index 6f14c0a6d0..f6ed006014 100644 --- a/spring-boot-project/spring-boot-docker-compose-all/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ b/spring-boot-project/spring-boot-docker-compose-all/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -16,9 +16,9 @@ package org.springframework.boot.docker.compose.service.connection.otlp; -import org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingConnectionDetails; -import org.springframework.boot.actuate.autoconfigure.logging.otlp.Transport; import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingConnectionDetails; +import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.Transport; import org.springframework.boot.testsupport.container.TestImage; import static org.assertj.core.api.Assertions.assertThat; @@ -32,7 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat; class OpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests { @DockerComposeTest(composeFile = "otlp-compose.yaml", image = TestImage.OPENTELEMETRY) - void runCreatesConnectionDetails(OtlpLoggingConnectionDetails connectionDetails) { + void runCreatesConnectionDetails(OpenTelemetryLoggingConnectionDetails connectionDetails) { assertThat(connectionDetails.getUrl(Transport.HTTP)).startsWith("http://").endsWith("/v1/logs"); assertThat(connectionDetails.getUrl(Transport.GRPC)).startsWith("http://").endsWith("/v1/logs"); } diff --git a/spring-boot-project/spring-boot-docker-compose-all/src/main/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryLoggingDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose-all/src/main/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryLoggingDockerComposeConnectionDetailsFactory.java index a4a6da3abc..63205d9c9a 100644 --- a/spring-boot-project/spring-boot-docker-compose-all/src/main/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryLoggingDockerComposeConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-docker-compose-all/src/main/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryLoggingDockerComposeConnectionDetailsFactory.java @@ -16,20 +16,20 @@ package org.springframework.boot.docker.compose.service.connection.otlp; -import org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingConnectionDetails; -import org.springframework.boot.actuate.autoconfigure.logging.otlp.Transport; import org.springframework.boot.docker.compose.core.RunningService; import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; +import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingConnectionDetails; +import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.Transport; /** * {@link DockerComposeConnectionDetailsFactory} to create - * {@link OtlpLoggingConnectionDetails} for an OTLP service. + * {@link OpenTelemetryLoggingConnectionDetails} for an OTLP service. * * @author EddĂș MelĂ©ndez */ class OpenTelemetryLoggingDockerComposeConnectionDetailsFactory - extends DockerComposeConnectionDetailsFactory { + extends DockerComposeConnectionDetailsFactory { private static final String[] OPENTELEMETRY_IMAGE_NAMES = { "otel/opentelemetry-collector-contrib", "grafana/otel-lgtm" }; @@ -40,16 +40,17 @@ class OpenTelemetryLoggingDockerComposeConnectionDetailsFactory OpenTelemetryLoggingDockerComposeConnectionDetailsFactory() { super(OPENTELEMETRY_IMAGE_NAMES, - "org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingAutoConfiguration"); + "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportAutoConfiguration"); } @Override - protected OtlpLoggingConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + protected OpenTelemetryLoggingConnectionDetails getDockerComposeConnectionDetails( + DockerComposeConnectionSource source) { return new OpenTelemetryLoggingDockerComposeConnectionDetails(source.getRunningService()); } private static final class OpenTelemetryLoggingDockerComposeConnectionDetails extends DockerComposeConnectionDetails - implements OtlpLoggingConnectionDetails { + implements OpenTelemetryLoggingConnectionDetails { private final String host; diff --git a/spring-boot-project/spring-boot-docs/build.gradle b/spring-boot-project/spring-boot-docs/build.gradle index 337f864330..b81e785df9 100644 --- a/spring-boot-project/spring-boot-docs/build.gradle +++ b/spring-boot-project/spring-boot-docs/build.gradle @@ -112,6 +112,7 @@ dependencies { autoConfiguration(project(path: ":spring-boot-project:spring-boot-mustache", configuration: "autoConfigurationMetadata")) autoConfiguration(project(path: ":spring-boot-project:spring-boot-neo4j", configuration: "autoConfigurationMetadata")) autoConfiguration(project(path: ":spring-boot-project:spring-boot-netty", configuration: "autoConfigurationMetadata")) + autoConfiguration(project(path: ":spring-boot-project:spring-boot-opentelemetry", configuration: "autoConfigurationMetadata")) autoConfiguration(project(path: ":spring-boot-project:spring-boot-pulsar", configuration: "autoConfigurationMetadata")) autoConfiguration(project(path: ":spring-boot-project:spring-boot-quartz", configuration: "autoConfigurationMetadata")) autoConfiguration(project(path: ":spring-boot-project:spring-boot-r2dbc", configuration: "autoConfigurationMetadata")) @@ -196,6 +197,7 @@ dependencies { configurationProperties(project(path: ":spring-boot-project:spring-boot-mustache", configuration: "configurationPropertiesMetadata")) configurationProperties(project(path: ":spring-boot-project:spring-boot-neo4j", configuration: "configurationPropertiesMetadata")) configurationProperties(project(path: ":spring-boot-project:spring-boot-netty", configuration: "configurationPropertiesMetadata")) + configurationProperties(project(path: ":spring-boot-project:spring-boot-opentelemetry", configuration: "configurationPropertiesMetadata")) configurationProperties(project(path: ":spring-boot-project:spring-boot-pulsar", configuration: "configurationPropertiesMetadata")) configurationProperties(project(path: ":spring-boot-project:spring-boot-quartz", configuration: "configurationPropertiesMetadata")) configurationProperties(project(path: ":spring-boot-project:spring-boot-r2dbc", configuration: "configurationPropertiesMetadata")) diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/loggers.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/loggers.adoc index 5eedc916eb..b061a87c5b 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/loggers.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/loggers.adoc @@ -42,9 +42,10 @@ You have to provide the location of the OpenTelemetry logs endpoint to configure [configprops,yaml] ---- management: - otlp: + opentelemetry: logging: - endpoint: "https://otlp.example.com:4318/v1/logs" + export: + endpoint: "https://otlp.example.com:4318/v1/logs" ---- NOTE: The OpenTelemetry Logback appender and Log4j appender are not part of Spring Boot. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc index 300e8584c6..4360174223 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc @@ -110,7 +110,7 @@ The following service connections are currently supported: | javadoc:org.springframework.boot.neo4j.autoconfigure.Neo4jConnectionDetails[] | Containers named "neo4j" or "bitnami/neo4j" -| javadoc:org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingConnectionDetails[] +| javadoc:org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingConnectionDetails[] | Containers named "otel/opentelemetry-collector-contrib", "grafana/otel-lgtm" | javadoc:org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsConnectionDetails[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc index 4af914a57a..f3c7fd90a4 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc @@ -155,7 +155,7 @@ The following service connection factories are provided in the `spring-boot-test | javadoc:org.springframework.boot.neo4j.autoconfigure.Neo4jConnectionDetails[] | Containers of type javadoc:{url-testcontainers-neo4j-javadoc}/org.testcontainers.containers.Neo4jContainer[] -| javadoc:org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingConnectionDetails[] +| javadoc:org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingConnectionDetails[] | Containers named "otel/opentelemetry-collector-contrib" or of type javadoc:org.testcontainers.grafana.LgtmStackContainer[] | javadoc:org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsConnectionDetails[] diff --git a/spring-boot-project/spring-boot-opentelemetry/build.gradle b/spring-boot-project/spring-boot-opentelemetry/build.gradle new file mode 100644 index 0000000000..1dab0e6258 --- /dev/null +++ b/spring-boot-project/spring-boot-opentelemetry/build.gradle @@ -0,0 +1,27 @@ +plugins { + id "java-library" + id "org.springframework.boot.auto-configuration" + id "org.springframework.boot.configuration-properties" + id "org.springframework.boot.deployed" + id "org.springframework.boot.docker-test" + id "org.springframework.boot.optional-dependencies" +} + +description = "Spring Boot Open Telemetry" + +dependencies { + api(project(":spring-boot-project:spring-boot")) + api("io.opentelemetry:opentelemetry-api") + api("io.opentelemetry:opentelemetry-sdk") + + optional(project(":spring-boot-project:spring-boot-autoconfigure")) + optional(project(":spring-boot-project:spring-boot-actuator-autoconfigure")) + optional("io.opentelemetry:opentelemetry-exporter-otlp") + + testImplementation(project(":spring-boot-project:spring-boot-test")) + testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) + testImplementation("com.squareup.okhttp3:mockwebserver") + + testRuntimeOnly("ch.qos.logback:logback-classic") + testRuntimeOnly("io.grpc:grpc-api:1.72.0") +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingConnectionDetails.java b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingConnectionDetails.java similarity index 77% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingConnectionDetails.java rename to spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingConnectionDetails.java index d767ac8c0b..573cd8c44c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingConnectionDetails.java +++ b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingConnectionDetails.java @@ -14,17 +14,18 @@ * limitations under the License. */ -package org.springframework.boot.actuate.autoconfigure.logging.otlp; +package org.springframework.boot.opentelemetry.actuate.autoconfigure.logging; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; /** - * Details required to establish a connection to an OpenTelemetry logging service. + * Details required for actuator to establish a connection to an OpenTelemetry logging + * service. * * @author Toshiaki Maki - * @since 3.4.0 + * @since 4.0.0 */ -public interface OtlpLoggingConnectionDetails extends ConnectionDetails { +public interface OpenTelemetryLoggingConnectionDetails extends ConnectionDetails { /** * Address to where logs will be published. diff --git a/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingConnectionDetailsConfiguration.java b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingConnectionDetailsConfiguration.java new file mode 100644 index 0000000000..c08c8c6bc8 --- /dev/null +++ b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingConnectionDetailsConfiguration.java @@ -0,0 +1,63 @@ +/* + * 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.opentelemetry.actuate.autoconfigure.logging; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.Assert; + +/** + * {@link Configuration @Configuration} for {@link OpenTelemetryLoggingConnectionDetails}. + * + * @author Toshiaki Maki + */ +@Configuration(proxyBeanMethods = false) +class OpenTelemetryLoggingConnectionDetailsConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty("management.opentelemetry.logging.export.endpoint") + PropertiesOpenTelemetryLoggingConnectionDetails openTelemetryLoggingConnectionDetails( + OpenTelemetryLoggingExportProperties properties) { + return new PropertiesOpenTelemetryLoggingConnectionDetails(properties); + } + + /** + * Adapts {@link OpenTelemetryLoggingExportProperties} to + * {@link OpenTelemetryLoggingConnectionDetails}. + */ + static class PropertiesOpenTelemetryLoggingConnectionDetails implements OpenTelemetryLoggingConnectionDetails { + + private final OpenTelemetryLoggingExportProperties properties; + + PropertiesOpenTelemetryLoggingConnectionDetails(OpenTelemetryLoggingExportProperties properties) { + this.properties = properties; + } + + @Override + public String getUrl(Transport transport) { + Assert.state(transport == this.properties.getTransport(), + "Requested transport %s doesn't match configured transport %s".formatted(transport, + this.properties.getTransport())); + return this.properties.getEndpoint(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingAutoConfiguration.java b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingExportAutoConfiguration.java similarity index 61% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingAutoConfiguration.java rename to spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingExportAutoConfiguration.java index ce7d314179..8b0d2d2b88 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingAutoConfiguration.java +++ b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingExportAutoConfiguration.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package org.springframework.boot.actuate.autoconfigure.logging.otlp; +package org.springframework.boot.opentelemetry.actuate.autoconfigure.logging; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import org.springframework.boot.actuate.autoconfigure.logging.ConditionalOnEnabledLoggingExport; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -27,15 +27,16 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Import; /** - * {@link EnableAutoConfiguration Auto-configuration} for OTLP logging. + * {@link EnableAutoConfiguration Auto-configuration} for OpenTelemetry logging exports. * * @author Toshiaki Maki - * @since 3.4.0 + * @since 4.0.0 */ @AutoConfiguration -@ConditionalOnClass({ SdkLoggerProvider.class, OpenTelemetry.class, OtlpHttpLogRecordExporter.class }) -@EnableConfigurationProperties(OtlpLoggingProperties.class) -@Import({ OtlpLoggingConfigurations.ConnectionDetails.class, OtlpLoggingConfigurations.Exporters.class }) -public class OtlpLoggingAutoConfiguration { +@ConditionalOnClass({ ConditionalOnEnabledLoggingExport.class, OpenTelemetry.class, SdkLoggerProvider.class }) +@ConditionalOnEnabledLoggingExport("opentelemetry") +@EnableConfigurationProperties(OpenTelemetryLoggingExportProperties.class) +@Import({ OpenTelemetryLoggingConnectionDetailsConfiguration.class, OpenTelemetryLoggingTransportConfiguration.class }) +public class OpenTelemetryLoggingExportAutoConfiguration { } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingProperties.java b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingExportProperties.java similarity index 90% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingProperties.java rename to spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingExportProperties.java index 15b2b6196c..c898a13314 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingProperties.java +++ b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingExportProperties.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.actuate.autoconfigure.logging.otlp; +package org.springframework.boot.opentelemetry.actuate.autoconfigure.logging; import java.time.Duration; import java.util.HashMap; @@ -23,13 +23,13 @@ import java.util.Map; import org.springframework.boot.context.properties.ConfigurationProperties; /** - * Configuration properties for exporting logs using OTLP. + * Configuration properties for exporting logs using OpenTelemetry. * * @author Jonatan Ivanov - * @since 3.4.0 + * @since 4.0.0 */ -@ConfigurationProperties("management.otlp.logging") -public class OtlpLoggingProperties { +@ConfigurationProperties("management.opentelemetry.logging.export") +public class OpenTelemetryLoggingExportProperties { /** * URL to the OTel collector's HTTP API. diff --git a/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingTransportConfiguration.java b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingTransportConfiguration.java new file mode 100644 index 0000000000..90bc443bf7 --- /dev/null +++ b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingTransportConfiguration.java @@ -0,0 +1,75 @@ +/* + * 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.opentelemetry.actuate.autoconfigure.logging; + +import java.util.Locale; + +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporterBuilder; +import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; +import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporterBuilder; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link Configuration @Configuration} for OpenTelemetry log record exporters. + * + * @author Toshiaki Maki + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(OtlpHttpLogRecordExporter.class) +@ConditionalOnMissingBean({ OtlpGrpcLogRecordExporter.class, OtlpHttpLogRecordExporter.class }) +@ConditionalOnBean(OpenTelemetryLoggingConnectionDetails.class) +class OpenTelemetryLoggingTransportConfiguration { + + @Bean + @ConditionalOnProperty(name = "management.opentelemetry.logging.export.transport", havingValue = "http", + matchIfMissing = true) + OtlpHttpLogRecordExporter otlpHttpLogRecordExporter(OpenTelemetryLoggingExportProperties properties, + OpenTelemetryLoggingConnectionDetails connectionDetails, ObjectProvider meterProvider) { + OtlpHttpLogRecordExporterBuilder builder = OtlpHttpLogRecordExporter.builder() + .setEndpoint(connectionDetails.getUrl(Transport.HTTP)) + .setTimeout(properties.getTimeout()) + .setConnectTimeout(properties.getConnectTimeout()) + .setCompression(properties.getCompression().name().toLowerCase(Locale.US)); + properties.getHeaders().forEach(builder::addHeader); + meterProvider.ifAvailable(builder::setMeterProvider); + return builder.build(); + } + + @Bean + @ConditionalOnProperty(name = "management.opentelemetry.logging.export.transport", havingValue = "grpc") + OtlpGrpcLogRecordExporter otlpGrpcLogRecordExporter(OpenTelemetryLoggingExportProperties properties, + OpenTelemetryLoggingConnectionDetails connectionDetails, ObjectProvider meterProvider) { + OtlpGrpcLogRecordExporterBuilder builder = OtlpGrpcLogRecordExporter.builder() + .setEndpoint(connectionDetails.getUrl(Transport.GRPC)) + .setTimeout(properties.getTimeout()) + .setConnectTimeout(properties.getConnectTimeout()) + .setCompression(properties.getCompression().name().toLowerCase(Locale.US)); + properties.getHeaders().forEach(builder::addHeader); + meterProvider.ifAvailable(builder::setMeterProvider); + return builder.build(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/Transport.java b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/Transport.java similarity index 85% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/Transport.java rename to spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/Transport.java index 598ee0692e..a3104fc270 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/Transport.java +++ b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/Transport.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package org.springframework.boot.actuate.autoconfigure.logging.otlp; +package org.springframework.boot.opentelemetry.actuate.autoconfigure.logging; /** - * Transport used to send OTLP data. + * Transport used to send OTLP log data. * * @author Moritz Halbritter - * @since 3.4.0 + * @since 4.0.0 */ public enum Transport { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/package-info.java b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/package-info.java similarity index 81% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/package-info.java rename to spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/package-info.java index db69afc7a2..2a055a4b9e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/package-info.java +++ b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/package-info.java @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for exporting logs with OTLP. + * Auto-configuration for exporting logs with OpenTelemetry. */ -package org.springframework.boot.actuate.autoconfigure.logging.otlp; +package org.springframework.boot.opentelemetry.actuate.autoconfigure.logging; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryProperties.java b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetryProperties.java similarity index 94% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryProperties.java rename to spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetryProperties.java index bc3589af47..b7bfc651e1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryProperties.java +++ b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetryProperties.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.actuate.autoconfigure.opentelemetry; +package org.springframework.boot.opentelemetry.autoconfigure; import java.util.HashMap; import java.util.Map; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryResourceAttributes.java b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetryResourceAttributes.java similarity index 91% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryResourceAttributes.java rename to spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetryResourceAttributes.java index 0acdf0fa0c..f7e900c11e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryResourceAttributes.java +++ b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetryResourceAttributes.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.actuate.autoconfigure.opentelemetry; +package org.springframework.boot.opentelemetry.autoconfigure; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; @@ -39,9 +39,9 @@ import org.springframework.util.StringUtils; * Resource Specification * * @author Dmytro Nosan - * @since 3.5.0 + * @since 4.0.0 */ -public final class OpenTelemetryResourceAttributes { +public class OpenTelemetryResourceAttributes { /** * Default value for service name if {@code service.name} is not set. @@ -52,7 +52,7 @@ public final class OpenTelemetryResourceAttributes { private final Map resourceAttributes; - private final Function getEnv; + private final Function systemEnvironment; /** * Creates a new instance of {@link OpenTelemetryResourceAttributes}. @@ -67,14 +67,14 @@ public final class OpenTelemetryResourceAttributes { * Creates a new {@link OpenTelemetryResourceAttributes} instance. * @param environment the environment * @param resourceAttributes user-provided resource attributes to be used - * @param getEnv a function to retrieve environment variables by name + * @param systemEnvironment a function to retrieve environment variables by name */ OpenTelemetryResourceAttributes(Environment environment, Map resourceAttributes, - Function getEnv) { + Function systemEnvironment) { Assert.notNull(environment, "'environment' must not be null"); this.environment = environment; this.resourceAttributes = (resourceAttributes != null) ? resourceAttributes : Collections.emptyMap(); - this.getEnv = (getEnv != null) ? getEnv : System::getenv; + this.systemEnvironment = (systemEnvironment != null) ? systemEnvironment : System::getenv; } /** @@ -150,7 +150,7 @@ public final class OpenTelemetryResourceAttributes { } private String getEnv(String name) { - return this.getEnv.apply(name); + return this.systemEnvironment.apply(name); } /** @@ -166,17 +166,17 @@ public final class OpenTelemetryResourceAttributes { return value; } byte[] bytes = value.getBytes(StandardCharsets.UTF_8); - ByteArrayOutputStream bos = new ByteArrayOutputStream(bytes.length); + ByteArrayOutputStream out = new ByteArrayOutputStream(bytes.length); for (int i = 0; i < bytes.length; i++) { byte b = bytes[i]; if (b != '%') { - bos.write(b); + out.write(b); continue; } int u = decodeHex(bytes, i + 1); int l = decodeHex(bytes, i + 2); if (u >= 0 && l >= 0) { - bos.write((u << 4) + l); + out.write((u << 4) + l); } else { throw new IllegalArgumentException( @@ -185,7 +185,7 @@ public final class OpenTelemetryResourceAttributes { } i += 2; } - return bos.toString(StandardCharsets.UTF_8); + return out.toString(StandardCharsets.UTF_8); } private static int decodeHex(byte[] bytes, int index) { diff --git a/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetrySdkAutoConfiguration.java b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetrySdkAutoConfiguration.java new file mode 100644 index 0000000000..3c9470fed3 --- /dev/null +++ b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetrySdkAutoConfiguration.java @@ -0,0 +1,109 @@ +/* + * 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.opentelemetry.autoconfigure; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.OpenTelemetrySdkBuilder; +import io.opentelemetry.sdk.logs.LogRecordProcessor; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; +import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.resources.ResourceBuilder; +import io.opentelemetry.sdk.trace.SdkTracerProvider; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for the OpenTelemetry SDK. + * + * @author Moritz Halbritter + * @since 4.0.0 + */ +@AutoConfiguration +@ConditionalOnClass({ OpenTelemetry.class, OpenTelemetrySdk.class }) +@EnableConfigurationProperties(OpenTelemetryProperties.class) +public class OpenTelemetrySdkAutoConfiguration { + + OpenTelemetrySdkAutoConfiguration() { + } + + @Bean + @ConditionalOnMissingBean(OpenTelemetry.class) + OpenTelemetrySdk openTelemetrySdk(ObjectProvider openTelemetrySdkTracerProvider, + ObjectProvider openTelemetryContextPropagators, + ObjectProvider openTelemetrySdkLoggerProvider, + ObjectProvider openTelemetrySdkMeterProvider) { + OpenTelemetrySdkBuilder builder = OpenTelemetrySdk.builder(); + openTelemetrySdkTracerProvider.ifAvailable(builder::setTracerProvider); + openTelemetryContextPropagators.ifAvailable(builder::setPropagators); + openTelemetrySdkLoggerProvider.ifAvailable(builder::setLoggerProvider); + openTelemetrySdkMeterProvider.ifAvailable(builder::setMeterProvider); + return builder.build(); + } + + @Bean + @ConditionalOnMissingBean + Resource openTelemetryResource(Environment environment, OpenTelemetryProperties properties) { + return Resource.getDefault().merge(toResource(environment, properties)); + } + + private Resource toResource(Environment environment, OpenTelemetryProperties properties) { + ResourceBuilder builder = Resource.builder(); + new OpenTelemetryResourceAttributes(environment, properties.getResourceAttributes()).applyTo(builder::put); + return builder.build(); + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(SdkLoggerProvider.class) + static class LoggerConfiguration { + + @Bean + @ConditionalOnMissingBean + BatchLogRecordProcessor openTelemetryBatchLogRecordProcessor( + ObjectProvider logRecordExporters) { + LogRecordExporter exporter = LogRecordExporter.composite(logRecordExporters.orderedStream().toList()); + return BatchLogRecordProcessor.builder(exporter).build(); + } + + @Bean + @ConditionalOnMissingBean + SdkLoggerProvider openTelemetrySdkLoggerProvider(Resource openTelemetryResource, + ObjectProvider logRecordProcessors, + ObjectProvider customizers) { + SdkLoggerProviderBuilder builder = SdkLoggerProvider.builder(); + builder.setResource(openTelemetryResource); + logRecordProcessors.orderedStream().forEach(builder::addLogRecordProcessor); + customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); + return builder.build(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/SdkLoggerProviderBuilderCustomizer.java b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/SdkLoggerProviderBuilderCustomizer.java similarity index 93% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/SdkLoggerProviderBuilderCustomizer.java rename to spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/SdkLoggerProviderBuilderCustomizer.java index 32af0e0bc5..08aec3462c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/SdkLoggerProviderBuilderCustomizer.java +++ b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/SdkLoggerProviderBuilderCustomizer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.actuate.autoconfigure.logging; +package org.springframework.boot.opentelemetry.autoconfigure; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; @@ -24,7 +24,7 @@ import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; * that is used to create the auto-configured {@link SdkLoggerProvider}. * * @author Toshiaki Maki - * @since 3.4.0 + * @since 4.0.0 */ @FunctionalInterface public interface SdkLoggerProviderBuilderCustomizer { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/package-info.java b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/package-info.java similarity index 90% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/package-info.java rename to spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/package-info.java index 1cda50bdbc..14ee7af1bf 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/package-info.java +++ b/spring-boot-project/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/package-info.java @@ -17,4 +17,4 @@ /** * Auto-configuration for OpenTelemetry. */ -package org.springframework.boot.actuate.autoconfigure.opentelemetry; +package org.springframework.boot.opentelemetry.autoconfigure; diff --git a/spring-boot-project/spring-boot-opentelemetry/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-opentelemetry/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000000..637c7010f7 --- /dev/null +++ b/spring-boot-project/spring-boot-opentelemetry/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,98 @@ +{ + "groups": [], + "properties": [ + { + "name": "management.logging.export.enabled", + "type": "java.lang.Boolean", + "description": "Whether auto-configuration of logging is enabled to export logs.", + "defaultValue": true, + "deprecation": { + "replacement": "management.opentelemetry.logging.export.enabled", + "level": "error" + } + }, + { + "name": "management.otlp.logging", + "type": "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportProperties", + "sourceType": "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportProperties", + "deprecation": { + "replacement": "management.opentelemetry.logging.export", + "level": "error" + } + }, + { + "name": "management.otlp.logging.compression", + "type": "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportProperties$Compression", + "description": "Method used to compress the payload.", + "sourceType": "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportProperties", + "defaultValue": "none", + "deprecation": { + "replacement": "management.opentelemetry.logging.export.compression", + "level": "error" + } + }, + { + "name": "management.otlp.logging.connect-timeout", + "type": "java.time.Duration", + "description": "Connect timeout for the OTel collector connection.", + "sourceType": "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportProperties", + "defaultValue": "10s", + "deprecation": { + "replacement": "management.opentelemetry.logging.export.connect-timeout", + "level": "error" + } + }, + { + "name": "management.otlp.logging.endpoint", + "type": "java.lang.String", + "description": "URL to the OTel collector's HTTP API.", + "sourceType": "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportProperties", + "deprecation": { + "replacement": "management.opentelemetry.logging.export.endpoint", + "level": "error" + } + }, + { + "name": "management.otlp.logging.export.enabled", + "type": "java.lang.Boolean", + "description": "Whether auto-configuration of logging is enabled to export OTLP logs.", + "deprecation": { + "replacement": "management.opentelemetry.logging.export.enabled", + "level": "error" + } + }, + { + "name": "management.otlp.logging.headers", + "type": "java.util.Map", + "description": "Custom HTTP headers you want to pass to the collector, for example auth headers.", + "sourceType": "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportProperties", + "deprecation": { + "replacement": "management.opentelemetry.logging.export.headers", + "level": "error" + } + }, + { + "name": "management.otlp.logging.timeout", + "type": "java.time.Duration", + "description": "Call timeout for the OTel Collector to process an exported batch of data. This timeout spans the entire call: resolving DNS, connecting, writing the request body, server processing, and reading the response body. If the call requires redirects or retries all must complete within one timeout period.", + "sourceType": "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportProperties", + "defaultValue": "10s", + "deprecation": { + "replacement": "management.opentelemetry.logging.export.timeout", + "level": "error" + } + }, + { + "name": "management.otlp.logging.transport", + "type": "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.Transport", + "description": "Transport used to send the logs.", + "sourceType": "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportProperties", + "defaultValue": "http", + "deprecation": { + "replacement": "management.opentelemetry.logging.export.transport", + "level": "error" + } + } + ], + "hints": [] +} diff --git a/spring-boot-project/spring-boot-opentelemetry/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-opentelemetry/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..ce19b82311 --- /dev/null +++ b/spring-boot-project/spring-boot-opentelemetry/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportAutoConfiguration +org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetrySdkAutoConfiguration diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingExportAutoConfigurationIntegrationTests.java similarity index 81% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingAutoConfigurationIntegrationTests.java rename to spring-boot-project/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingExportAutoConfigurationIntegrationTests.java index c3df8ab4ba..4b87d1c72f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingExportAutoConfigurationIntegrationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.actuate.autoconfigure.logging.otlp; +package org.springframework.boot.opentelemetry.actuate.autoconfigure.logging; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -32,26 +32,25 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.boot.actuate.autoconfigure.logging.OpenTelemetryLoggingAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetrySdkAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContext; import static org.assertj.core.api.Assertions.assertThat; /** - * Integration tests for {@link OtlpLoggingAutoConfiguration}. + * Integration tests for {@link OpenTelemetryLoggingExportAutoConfiguration}. * * @author Toshiaki Maki */ -class OtlpLoggingAutoConfigurationIntegrationTests { +class OpenTelemetryLoggingExportAutoConfigurationIntegrationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.application.name=otlp-logs-test", - "management.otlp.logging.headers.Authorization=Bearer my-token") - .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class, - OpenTelemetryLoggingAutoConfiguration.class, OtlpLoggingAutoConfiguration.class)); + "management.opentelemetry.logging.export.headers.Authorization=Bearer my-token") + .withConfiguration(AutoConfigurations.of(OpenTelemetrySdkAutoConfiguration.class, + OpenTelemetryLoggingExportAutoConfiguration.class)); private final MockWebServer mockWebServer = new MockWebServer(); @@ -69,7 +68,7 @@ class OtlpLoggingAutoConfigurationIntegrationTests { void httpLogRecordExporterShouldUseProtobufAndNoCompressionByDefault() { this.mockWebServer.enqueue(new MockResponse()); this.contextRunner - .withPropertyValues("management.otlp.logging.endpoint=http://localhost:%d/v1/logs" + .withPropertyValues("management.opentelemetry.logging.export.endpoint=http://localhost:%d/v1/logs" .formatted(this.mockWebServer.getPort())) .run((context) -> { logMessage(context); @@ -89,8 +88,8 @@ class OtlpLoggingAutoConfigurationIntegrationTests { void httpLogRecordExporterCanBeConfiguredToUseGzipCompression() { this.mockWebServer.enqueue(new MockResponse()); this.contextRunner - .withPropertyValues("management.otlp.logging.endpoint=http://localhost:%d/v1/logs" - .formatted(this.mockWebServer.getPort()), "management.otlp.logging.compression=gzip") + .withPropertyValues("management.opentelemetry.logging.export.endpoint=http://localhost:%d/v1/logs" + .formatted(this.mockWebServer.getPort()), "management.opentelemetry.logging.export.compression=gzip") .run((context) -> { logMessage(context); RecordedRequest request = this.mockWebServer.takeRequest(10, TimeUnit.SECONDS); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingAutoConfigurationTests.java b/spring-boot-project/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingExportAutoConfigurationTests.java similarity index 50% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingAutoConfigurationTests.java rename to spring-boot-project/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingExportAutoConfigurationTests.java index cb1c467856..740a435f20 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/actuate/autoconfigure/logging/OpenTelemetryLoggingExportAutoConfigurationTests.java @@ -14,13 +14,15 @@ * limitations under the License. */ -package org.springframework.boot.actuate.autoconfigure.logging.otlp; +package org.springframework.boot.opentelemetry.actuate.autoconfigure.logging; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; +import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; import io.opentelemetry.sdk.logs.export.LogRecordExporter; import okhttp3.HttpUrl; import org.assertj.core.api.InstanceOfAssertFactories; @@ -28,8 +30,12 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingConfigurations.ConnectionDetails.PropertiesOtlpLoggingConnectionDetails; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.context.annotation.ImportCandidates; +import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingConnectionDetailsConfiguration.PropertiesOpenTelemetryLoggingConnectionDetails; +import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetrySdkAutoConfiguration; +import org.springframework.boot.opentelemetry.autoconfigure.SdkLoggerProviderBuilderCustomizer; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -38,131 +44,146 @@ import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link OtlpLoggingAutoConfiguration}. + * Tests for {@link OpenTelemetryLoggingExportAutoConfiguration}. * * @author Toshiaki Maki * @author Moritz Halbritter */ -class OtlpLoggingAutoConfigurationTests { +class OpenTelemetryLoggingExportAutoConfigurationTests { - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(OtlpLoggingAutoConfiguration.class)); + private final ApplicationContextRunner contextRunner; - @Test - void shouldNotSupplyBeansIfPropertyIsNotSet() { - this.contextRunner.run((context) -> { - assertThat(context).doesNotHaveBean(OtlpLoggingConnectionDetails.class); - assertThat(context).doesNotHaveBean(OtlpHttpLogRecordExporter.class); - }); + OpenTelemetryLoggingExportAutoConfigurationTests() { + this.contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations + .of(OpenTelemetrySdkAutoConfiguration.class, OpenTelemetryLoggingExportAutoConfiguration.class)); } @Test - void shouldSupplyBeans() { - this.contextRunner.withPropertyValues("management.otlp.logging.endpoint=http://localhost:4318/v1/logs") - .run((context) -> { - assertThat(context).hasSingleBean(OtlpLoggingConnectionDetails.class); - OtlpLoggingConnectionDetails connectionDetails = context.getBean(OtlpLoggingConnectionDetails.class); - assertThat(connectionDetails.getUrl(Transport.HTTP)).isEqualTo("http://localhost:4318/v1/logs"); - assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class) - .hasSingleBean(LogRecordExporter.class); - }); + void registeredInAutoConfigurationImports() { + assertThat(ImportCandidates.load(AutoConfiguration.class, null).getCandidates()) + .contains(OpenTelemetryLoggingExportAutoConfiguration.class.getName()); } @ParameterizedTest @ValueSource(strings = { "io.opentelemetry.sdk.logs", "io.opentelemetry.api", "io.opentelemetry.exporter.otlp.http.logs" }) - void shouldNotSupplyBeansIfDependencyIsMissing(String packageName) { + void whenOpenTelemetryIsNotOnClasspathDoesNotProvideBeans(String packageName) { this.contextRunner.withClassLoader(new FilteredClassLoader(packageName)).run((context) -> { - assertThat(context).doesNotHaveBean(OtlpLoggingConnectionDetails.class); + assertThat(context).doesNotHaveBean(OpenTelemetryLoggingConnectionDetails.class); assertThat(context).doesNotHaveBean(OtlpHttpLogRecordExporter.class); }); } @Test - void shouldBackOffWhenLoggingExportPropertyIsNotEnabled() { + void whenHasEndpointPropertyProvidesBeans() { + this.contextRunner + .withPropertyValues("management.opentelemetry.logging.export.endpoint=http://localhost:4318/v1/logs") + .run((context) -> { + assertThat(context).hasSingleBean(OpenTelemetryLoggingConnectionDetails.class); + OpenTelemetryLoggingConnectionDetails connectionDetails = context + .getBean(OpenTelemetryLoggingConnectionDetails.class); + assertThat(connectionDetails.getUrl(Transport.HTTP)).isEqualTo("http://localhost:4318/v1/logs"); + assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class); + assertThat(context).hasSingleBean(LogRecordExporter.class); + }); + } + + @Test + void whenHasNoEndpointPropertyDoesNotProvideBeans() { + this.contextRunner.run((context) -> { + assertThat(context).doesNotHaveBean(OpenTelemetryLoggingConnectionDetails.class); + assertThat(context).doesNotHaveBean(OtlpHttpLogRecordExporter.class); + }); + } + + @Test + void whenOpenTelemetryLoggingExportEnabledPropertyIsFalseProvidesExpectedBeans() { + this.contextRunner + .withPropertyValues("management.opentelemetry.logging.export.enabled=false", + "management.opentelemetry.logging.export.endpoint=http://localhost:4318/v1/logs") + .run((context) -> { + assertThat(context).doesNotHaveBean(OpenTelemetryLoggingConnectionDetails.class); + assertThat(context).doesNotHaveBean(LogRecordExporter.class); + }); + } + + @Test + void whenLoggingExportEnabledPropertyIsFalseNoProvideExpectedBeans() { this.contextRunner .withPropertyValues("management.logging.export.enabled=false", - "management.otlp.logging.endpoint=http://localhost:4318/v1/logs") + "management.opentelemetry.logging.export.endpoint=http://localhost:4318/v1/logs") .run((context) -> { - assertThat(context).hasSingleBean(OtlpLoggingConnectionDetails.class); + assertThat(context).doesNotHaveBean(OpenTelemetryLoggingConnectionDetails.class); assertThat(context).doesNotHaveBean(LogRecordExporter.class); }); } @Test - void shouldBackOffWhenOtlpLoggingExportPropertyIsNotEnabled() { - this.contextRunner - .withPropertyValues("management.otlp.logging.export.enabled=false", - "management.otlp.logging.endpoint=http://localhost:4318/v1/logs") - .run((context) -> { - assertThat(context).hasSingleBean(OtlpLoggingConnectionDetails.class); - assertThat(context).doesNotHaveBean(LogRecordExporter.class); - }); - } - - @Test - void shouldBackOffWhenCustomHttpExporterIsDefined() { + void whenHasCustomHttpExporterDoesNotProvideExporterBean() { this.contextRunner.withUserConfiguration(CustomHttpExporterConfiguration.class) .run((context) -> assertThat(context).hasBean("customOtlpHttpLogRecordExporter") .hasSingleBean(LogRecordExporter.class)); } @Test - void shouldBackOffWhenCustomGrpcExporterIsDefined() { + void whenHasCustomGrpcExporterDoesNotProvideExporterBean() { this.contextRunner.withUserConfiguration(CustomGrpcExporterConfiguration.class) .run((context) -> assertThat(context).hasBean("customOtlpGrpcLogRecordExporter") .hasSingleBean(LogRecordExporter.class)); } + // FIXME @Test - void shouldBackOffWhenCustomOtlpLoggingConnectionDetailsIsDefined() { - this.contextRunner.withUserConfiguration(CustomOtlpLoggingConnectionDetails.class).run((context) -> { - assertThat(context).hasSingleBean(OtlpLoggingConnectionDetails.class) - .doesNotHaveBean(PropertiesOtlpLoggingConnectionDetails.class); - OtlpHttpLogRecordExporter otlpHttpLogRecordExporter = context.getBean(OtlpHttpLogRecordExporter.class); - assertThat(otlpHttpLogRecordExporter).extracting("delegate.httpSender.url") - .isEqualTo(HttpUrl.get("https://otel.example.com/v1/logs")); - }); + void whenHasCustomLoggingConnectionDetailsDoesNotProvideExporterBean() { + this.contextRunner.withUserConfiguration(CustomOtlpLoggingConnectionDetailsConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(OpenTelemetryLoggingConnectionDetails.class) + .doesNotHaveBean(PropertiesOpenTelemetryLoggingConnectionDetails.class); + OtlpHttpLogRecordExporter otlpHttpLogRecordExporter = context.getBean(OtlpHttpLogRecordExporter.class); + assertThat(otlpHttpLogRecordExporter).extracting("delegate.httpSender.url") + .isEqualTo(HttpUrl.get("https://otel.example.com/v1/logs")); + }); } @Test - void shouldUseHttpExporterIfTransportIsNotSet() { - this.contextRunner.withPropertyValues("management.otlp.logging.endpoint=http://localhost:4318/v1/logs") + void whenHasNoTransportPropertySetUsesHttpExporter() { + this.contextRunner + .withPropertyValues("management.opentelemetry.logging.export.endpoint=http://localhost:4318/v1/logs") .run((context) -> { - assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class) - .hasSingleBean(LogRecordExporter.class); + assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class); + assertThat(context).hasSingleBean(LogRecordExporter.class); assertThat(context).doesNotHaveBean(OtlpGrpcLogRecordExporter.class); }); } @Test - void shouldUseHttpExporterIfTransportIsSetToHttp() { + void whenHasTransportPropertySetToHttpUsesHttpExporter() { this.contextRunner - .withPropertyValues("management.otlp.logging.endpoint=http://localhost:4318/v1/logs", - "management.otlp.logging.transport=http") + .withPropertyValues("management.opentelemetry.logging.export.endpoint=http://localhost:4318/v1/logs", + "management.opentelemetry.logging.export.transport=http") .run((context) -> { - assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class) - .hasSingleBean(LogRecordExporter.class); + assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class); + assertThat(context).hasSingleBean(LogRecordExporter.class); assertThat(context).doesNotHaveBean(OtlpGrpcLogRecordExporter.class); }); } @Test - void shouldUseGrpcExporterIfTransportIsSetToGrpc() { + void whenHasTransportPropertySetToGrpcUsesGrpcExporter() { this.contextRunner - .withPropertyValues("management.otlp.logging.endpoint=http://localhost:4318/v1/logs", - "management.otlp.logging.transport=grpc") + .withPropertyValues("management.opentelemetry.logging.export.endpoint=http://localhost:4318/v1/logs", + "management.opentelemetry.logging.export.transport=grpc") .run((context) -> { - assertThat(context).hasSingleBean(OtlpGrpcLogRecordExporter.class) - .hasSingleBean(LogRecordExporter.class); + assertThat(context).hasSingleBean(OtlpGrpcLogRecordExporter.class); + assertThat(context).hasSingleBean(LogRecordExporter.class); assertThat(context).doesNotHaveBean(OtlpHttpLogRecordExporter.class); }); } @Test - void httpShouldUseMeterProviderIfSet() { + void whenHasMeterProviderBeanAddsItToHttpExporter() { this.contextRunner.withUserConfiguration(MeterProviderConfiguration.class) - .withPropertyValues("management.otlp.logging.endpoint=http://localhost:4318/v1/logs") + .withPropertyValues("management.opentelemetry.logging.export.endpoint=http://localhost:4318/v1/logs") .run((context) -> { OtlpHttpLogRecordExporter otlpHttpLogRecordExporter = context.getBean(OtlpHttpLogRecordExporter.class); assertThat(otlpHttpLogRecordExporter.toBuilder()) @@ -173,10 +194,10 @@ class OtlpLoggingAutoConfigurationTests { } @Test - void grpcShouldUseMeterProviderIfSet() { + void whenHasMeterProviderBeanAddsItToGrpcExporter() { this.contextRunner.withUserConfiguration(MeterProviderConfiguration.class) - .withPropertyValues("management.otlp.logging.endpoint=http://localhost:4318/v1/logs", - "management.otlp.logging.transport=grpc") + .withPropertyValues("management.opentelemetry.logging.export.endpoint=http://localhost:4318/v1/logs", + "management.opentelemetry.logging.export.transport=grpc") .run((context) -> { OtlpGrpcLogRecordExporter otlpGrpcLogRecordExporter = context.getBean(OtlpGrpcLogRecordExporter.class); assertThat(otlpGrpcLogRecordExporter.toBuilder()) @@ -186,6 +207,36 @@ class OtlpLoggingAutoConfigurationTests { }); } + @Configuration(proxyBeanMethods = false) + public static class MultipleSdkLoggerProviderBuilderCustomizersConfig { + + @Bean + public SdkLoggerProviderBuilderCustomizer customSdkLoggerProviderBuilderCustomizer1() { + return new NoopSdkLoggerProviderBuilderCustomizer(); + } + + @Bean + public SdkLoggerProviderBuilderCustomizer customSdkLoggerProviderBuilderCustomizer2() { + return new NoopSdkLoggerProviderBuilderCustomizer(); + } + + } + + static class NoopSdkLoggerProviderBuilderCustomizer implements SdkLoggerProviderBuilderCustomizer { + + final AtomicInteger called = new AtomicInteger(0); + + @Override + public void customize(SdkLoggerProviderBuilder builder) { + this.called.incrementAndGet(); + } + + int called() { + return this.called.get(); + } + + } + @Configuration(proxyBeanMethods = false) private static final class MeterProviderConfiguration { @@ -219,10 +270,10 @@ class OtlpLoggingAutoConfigurationTests { } @Configuration(proxyBeanMethods = false) - private static final class CustomOtlpLoggingConnectionDetails { + private static final class CustomOtlpLoggingConnectionDetailsConfiguration { @Bean - OtlpLoggingConnectionDetails customOtlpLoggingConnectionDetails() { + OpenTelemetryLoggingConnectionDetails customOtlpLoggingConnectionDetails() { return (transport) -> "https://otel.example.com/v1/logs"; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryPropertiesTests.java b/spring-boot-project/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetryPropertiesTests.java similarity index 96% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryPropertiesTests.java rename to spring-boot-project/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetryPropertiesTests.java index 63f6c5578d..681e7aaf0e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryPropertiesTests.java +++ b/spring-boot-project/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetryPropertiesTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.actuate.autoconfigure.opentelemetry; +package org.springframework.boot.opentelemetry.autoconfigure; import org.junit.jupiter.api.Test; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryResourceAttributesTests.java b/spring-boot-project/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetryResourceAttributesTests.java similarity index 99% rename from spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryResourceAttributesTests.java rename to spring-boot-project/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetryResourceAttributesTests.java index 9481f94e7a..a6484c969f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryResourceAttributesTests.java +++ b/spring-boot-project/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetryResourceAttributesTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.actuate.autoconfigure.opentelemetry; +package org.springframework.boot.opentelemetry.autoconfigure; import java.util.LinkedHashMap; import java.util.Map; @@ -99,7 +99,7 @@ class OpenTelemetryResourceAttributesTests { @SuppressWarnings("unchecked") void systemGetEnvShouldBeUsedAsDefaultEnvFunction() { OpenTelemetryResourceAttributes attributes = new OpenTelemetryResourceAttributes(this.environment, null); - Function getEnv = assertThat(attributes).extracting("getEnv") + Function getEnv = assertThat(attributes).extracting("systemEnvironment") .asInstanceOf(InstanceOfAssertFactories.type(Function.class)) .actual(); System.getenv().forEach((key, value) -> assertThat(getEnv.apply(key)).isEqualTo(value)); diff --git a/spring-boot-project/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetrySdkAutoConfigurationTests.java b/spring-boot-project/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetrySdkAutoConfigurationTests.java new file mode 100644 index 0000000000..4ac62f6a7e --- /dev/null +++ b/spring-boot-project/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/OpenTelemetrySdkAutoConfigurationTests.java @@ -0,0 +1,364 @@ +/* + * 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.opentelemetry.autoconfigure; + +import java.util.Collection; +import java.util.concurrent.atomic.AtomicInteger; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.logs.LogRecordProcessor; +import io.opentelemetry.sdk.logs.ReadWriteLogRecord; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.context.annotation.ImportCandidates; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link OpenTelemetrySdkAutoConfiguration}. + * + * @author Moritz Halbritter + * @author Toshiaki Maki + * @author Phillip Webb + */ +class OpenTelemetrySdkAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(OpenTelemetrySdkAutoConfiguration.class)); + + @Test + void registeredInAutoConfigurationImports() { + assertThat(ImportCandidates.load(AutoConfiguration.class, null).getCandidates()) + .contains(OpenTelemetrySdkAutoConfiguration.class.getName()); + } + + @Test + void providesBeans() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(OpenTelemetrySdk.class); + assertThat(context).hasSingleBean(Resource.class); + assertThat(context).hasSingleBean(BatchLogRecordProcessor.class); + assertThat(context).hasSingleBean(SdkLoggerProvider.class); + }); + } + + @ParameterizedTest + @ValueSource(strings = { "io.opentelemetry", "io.opentelemetry.api" }) + void whenOpenTelemetryIsNotOnClasspathDoesNotProvideBeans(String packageName) { + this.contextRunner.withClassLoader(new FilteredClassLoader(packageName)).run((context) -> { + assertThat(context).doesNotHaveBean(OpenTelemetrySdk.class); + assertThat(context).doesNotHaveBean(Resource.class); + assertThat(context).doesNotHaveBean(BatchLogRecordProcessor.class); + assertThat(context).doesNotHaveBean(SdkLoggerProvider.class); + }); + } + + @Test + void whenOpenTelemetryLogsIsNotOnClasspathDoesNotProvideBeans() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.opentelemetry.sdk.logs")).run((context) -> { + assertThat(context).hasSingleBean(OpenTelemetrySdk.class); + assertThat(context).hasSingleBean(Resource.class); + assertThat(context).doesNotHaveBean(BatchLogRecordProcessor.class); + assertThat(context).doesNotHaveBean(SdkLoggerProvider.class); + }); + } + + @Test + void whenHasUserSuppliedBeansDoesNotProvideBeans() { + this.contextRunner.withUserConfiguration(UserConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(OpenTelemetry.class); + assertThat(context).hasBean("customOpenTelemetry"); + assertThat(context).hasSingleBean(Resource.class); + assertThat(context).hasBean("customResource"); + assertThat(context).hasSingleBean(BatchLogRecordProcessor.class); + assertThat(context).hasBean("customBatchLogRecordProcessor").hasSingleBean(BatchLogRecordProcessor.class); + assertThat(context).hasSingleBean(LogRecordProcessor.class); + assertThat(context).hasBean("customSdkLoggerProvider").hasSingleBean(SdkLoggerProvider.class); + }); + } + + @Test + void whenHasApplicationNamePropertyProvidesServiceNameResourceAttribute() { + this.contextRunner.withPropertyValues("spring.application.name=my-application").run((context) -> { + Resource resource = context.getBean(Resource.class); + assertThat(resource.getAttributes().asMap()) + .contains(entry(AttributeKey.stringKey("service.name"), "my-application")); + }); + } + + @Test + void whenHasApplicationGroupPropertyProvidesServiceGroupResourceAttribute() { + this.contextRunner.withPropertyValues("spring.application.group=my-group").run((context) -> { + Resource resource = context.getBean(Resource.class); + assertThat(resource.getAttributes().asMap()) + .contains(entry(AttributeKey.stringKey("service.group"), "my-group")); + }); + } + + @Test + void whenHasApplicationGroupPropertyProvidesServiceNamespaceResourceAttribute() { + this.contextRunner.withPropertyValues("spring.application.group=my-group").run((context) -> { + Resource resource = context.getBean(Resource.class); + assertThat(resource.getAttributes().asMap()).containsEntry(AttributeKey.stringKey("service.namespace"), + "my-group"); + }); + } + + @Test + void whenHasNoApplicationGroupPropertyProvidesNoServiceGroupResourceAttribute() { + this.contextRunner.run((context) -> { + Resource resource = context.getBean(Resource.class); + assertThat(resource.getAttributes().asMap()).doesNotContainKey(AttributeKey.stringKey("service.group")); + }); + } + + @Test + void whenHasNoApplicationGroupPropertyProvidesNoServiceNamespaceResourceAttribute() { + this.contextRunner.run(((context) -> { + Resource resource = context.getBean(Resource.class); + assertThat(resource.getAttributes().asMap()).doesNotContainKey(AttributeKey.stringKey("service.namespace")); + })); + } + + @Test + void whenHasNoApplicationNamePropertyProvidesDefaultApplicationName() { + this.contextRunner.run((context) -> { + Resource resource = context.getBean(Resource.class); + assertThat(resource.getAttributes().asMap()) + .contains(entry(AttributeKey.stringKey("service.name"), "unknown_service")); + }); + } + + @Test + void whenHasResourceAttributesPropertyProvidesResourceAttributes() { + this.contextRunner.withPropertyValues("management.opentelemetry.resource-attributes.region=us-west") + .run((context) -> { + Resource resource = context.getBean(Resource.class); + assertThat(resource.getAttributes().asMap()) + .contains(entry(AttributeKey.stringKey("region"), "us-west")); + }); + } + + @Test + void whenHasSdkTracerProviderBeanProvidesTracerProvider() { + this.contextRunner.withBean(SdkTracerProvider.class, () -> SdkTracerProvider.builder().build()) + .run((context) -> { + OpenTelemetry openTelemetry = context.getBean(OpenTelemetry.class); + assertThat(openTelemetry.getTracerProvider()).isNotNull(); + }); + } + + @Test + void whenHasContextPropagatorsBeanProvidesPropagators() { + this.contextRunner.withBean(ContextPropagators.class, ContextPropagators::noop).run((context) -> { + OpenTelemetry openTelemetry = context.getBean(OpenTelemetry.class); + assertThat(openTelemetry.getPropagators()).isNotNull(); + }); + } + + @Test + void whenHasSdkLoggerProviderBeanProvidesLogsBridge() { + this.contextRunner.withBean(SdkLoggerProvider.class, () -> SdkLoggerProvider.builder().build()) + .run((context) -> { + OpenTelemetry openTelemetry = context.getBean(OpenTelemetry.class); + assertThat(openTelemetry.getLogsBridge()).isNotNull(); + }); + } + + @Test + void whenHasSdkMeterProviderProvidesMeterProvider() { + this.contextRunner.withBean(SdkMeterProvider.class, () -> SdkMeterProvider.builder().build()).run((context) -> { + OpenTelemetry openTelemetry = context.getBean(OpenTelemetry.class); + assertThat(openTelemetry.getMeterProvider()).isNotNull(); + }); + } + + @Test + void whenHasMultipleLogRecordExportersProvidesBatchLogRecordProcessor() { + this.contextRunner.withUserConfiguration(MultipleLogRecordExportersConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(BatchLogRecordProcessor.class); + assertThat(context.getBeansOfType(LogRecordExporter.class)).hasSize(2); + assertThat(context).hasBean("customLogRecordExporter1"); + assertThat(context).hasBean("customLogRecordExporter2"); + }); + } + + @Test + void whenHasMultipleLogRecordProcessorsStillProvidesBatchLogRecordProcessor() { + this.contextRunner.withUserConfiguration(MultipleLogRecordProcessorsConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(BatchLogRecordProcessor.class); + assertThat(context).hasSingleBean(SdkLoggerProvider.class); + assertThat(context.getBeansOfType(LogRecordProcessor.class)).hasSize(3); + assertThat(context).hasBean("openTelemetryBatchLogRecordProcessor"); + assertThat(context).hasBean("customLogRecordProcessor1"); + assertThat(context).hasBean("customLogRecordProcessor2"); + }); + } + + @Test + void whenHasMultipleSdkLoggerProviderBuilderCustomizersCallsCustomizeMethod() { + this.contextRunner.withUserConfiguration(MultipleSdkLoggerProviderBuilderCustomizersConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(SdkLoggerProvider.class); + assertThat(context.getBeansOfType(SdkLoggerProviderBuilderCustomizer.class)).hasSize(2); + assertThat(context).hasBean("customSdkLoggerProviderBuilderCustomizer1"); + assertThat(context).hasBean("customSdkLoggerProviderBuilderCustomizer2"); + assertThat(context + .getBean("customSdkLoggerProviderBuilderCustomizer1", NoopSdkLoggerProviderBuilderCustomizer.class) + .called()).isEqualTo(1); + assertThat(context + .getBean("customSdkLoggerProviderBuilderCustomizer2", NoopSdkLoggerProviderBuilderCustomizer.class) + .called()).isEqualTo(1); + }); + } + + @Configuration(proxyBeanMethods = false) + static class UserConfiguration { + + @Bean + OpenTelemetry customOpenTelemetry() { + return mock(OpenTelemetry.class); + } + + @Bean + Resource customResource() { + return Resource.getDefault(); + } + + @Bean + BatchLogRecordProcessor customBatchLogRecordProcessor() { + return BatchLogRecordProcessor.builder(new NoopLogRecordExporter()).build(); + } + + @Bean + SdkLoggerProvider customSdkLoggerProvider() { + return SdkLoggerProvider.builder().build(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class MultipleLogRecordExportersConfiguration { + + @Bean + LogRecordExporter customLogRecordExporter1() { + return new NoopLogRecordExporter(); + } + + @Bean + LogRecordExporter customLogRecordExporter2() { + return new NoopLogRecordExporter(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class MultipleLogRecordProcessorsConfiguration { + + @Bean + LogRecordProcessor customLogRecordProcessor1() { + return new NoopLogRecordProcessor(); + } + + @Bean + LogRecordProcessor customLogRecordProcessor2() { + return new NoopLogRecordProcessor(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class MultipleSdkLoggerProviderBuilderCustomizersConfiguration { + + @Bean + SdkLoggerProviderBuilderCustomizer customSdkLoggerProviderBuilderCustomizer1() { + return new NoopSdkLoggerProviderBuilderCustomizer(); + } + + @Bean + SdkLoggerProviderBuilderCustomizer customSdkLoggerProviderBuilderCustomizer2() { + return new NoopSdkLoggerProviderBuilderCustomizer(); + } + + } + + static class NoopLogRecordExporter implements LogRecordExporter { + + @Override + public CompletableResultCode export(Collection logs) { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + return CompletableResultCode.ofSuccess(); + } + + } + + static class NoopLogRecordProcessor implements LogRecordProcessor { + + @Override + public void onEmit(Context context, ReadWriteLogRecord logRecord) { + } + + } + + static class NoopSdkLoggerProviderBuilderCustomizer implements SdkLoggerProviderBuilderCustomizer { + + final AtomicInteger called = new AtomicInteger(0); + + @Override + public void customize(SdkLoggerProviderBuilder builder) { + this.called.incrementAndGet(); + } + + int called() { + return this.called.get(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers-all/build.gradle b/spring-boot-project/spring-boot-testcontainers-all/build.gradle index e1ba1b77ca..ec74297dab 100644 --- a/spring-boot-project/spring-boot-testcontainers-all/build.gradle +++ b/spring-boot-project/spring-boot-testcontainers-all/build.gradle @@ -27,6 +27,7 @@ dependencies { dockerTestImplementation("org.testcontainers:junit-jupiter") optional(project(":spring-boot-project:spring-boot-actuator-autoconfigure-all")) + optional(project(":spring-boot-project:spring-boot-opentelemetry")) optional(project(":spring-boot-project:spring-boot-tx")) optional(project(":spring-boot-project:spring-boot-zipkin")) optional("org.springframework:spring-test") diff --git a/spring-boot-project/spring-boot-testcontainers-all/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers-all/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTests.java index 758232f5cb..fd05715711 100644 --- a/spring-boot-project/spring-boot-testcontainers-all/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTests.java +++ b/spring-boot-project/spring-boot-testcontainers-all/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTests.java @@ -22,10 +22,10 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingConnectionDetails; -import org.springframework.boot.actuate.autoconfigure.logging.otlp.Transport; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingConnectionDetails; +import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportAutoConfiguration; +import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.Transport; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Configuration; @@ -47,7 +47,7 @@ class GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTes static final LgtmStackContainer container = TestImage.container(LgtmStackContainer.class); @Autowired - private OtlpLoggingConnectionDetails connectionDetails; + private OpenTelemetryLoggingConnectionDetails connectionDetails; @Test void connectionCanBeMadeToOpenTelemetryContainer() { @@ -58,7 +58,7 @@ class GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTes } @Configuration(proxyBeanMethods = false) - @ImportAutoConfiguration(OtlpLoggingAutoConfiguration.class) + @ImportAutoConfiguration(OpenTelemetryLoggingExportAutoConfiguration.class) static class TestConfiguration { } diff --git a/spring-boot-project/spring-boot-testcontainers-all/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers-all/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTests.java index 2cbdbc92bc..b76b809fc8 100644 --- a/spring-boot-project/spring-boot-testcontainers-all/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTests.java +++ b/spring-boot-project/spring-boot-testcontainers-all/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTests.java @@ -22,10 +22,10 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingConnectionDetails; -import org.springframework.boot.actuate.autoconfigure.logging.otlp.Transport; import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingConnectionDetails; +import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.Transport; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Configuration; @@ -48,7 +48,7 @@ class OpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTests { .withExposedPorts(4317, 4318); @Autowired - private OtlpLoggingConnectionDetails connectionDetails; + private OpenTelemetryLoggingConnectionDetails connectionDetails; @Test void connectionCanBeMadeToOpenTelemetryContainer() { diff --git a/spring-boot-project/spring-boot-testcontainers-all/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers-all/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactory.java index f0153f3710..f7f4191235 100644 --- a/spring-boot-project/spring-boot-testcontainers-all/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers-all/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactory.java @@ -18,36 +18,36 @@ package org.springframework.boot.testcontainers.service.connection.otlp; import org.testcontainers.grafana.LgtmStackContainer; -import org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingConnectionDetails; -import org.springframework.boot.actuate.autoconfigure.logging.otlp.Transport; +import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingConnectionDetails; +import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.Transport; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** * {@link ContainerConnectionDetailsFactory} to create - * {@link OtlpLoggingConnectionDetails} from a + * {@link OpenTelemetryLoggingConnectionDetails} from a * {@link ServiceConnection @ServiceConnection}-annotated {@link LgtmStackContainer} using * the {@code "grafana/otel-lgtm"} image. * * @author EddĂș MelĂ©ndez */ class GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactory - extends ContainerConnectionDetailsFactory { + extends ContainerConnectionDetailsFactory { GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactory() { super(ANY_CONNECTION_NAME, - "org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingAutoConfiguration"); + "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportAutoConfiguration"); } @Override - protected OtlpLoggingConnectionDetails getContainerConnectionDetails( + protected OpenTelemetryLoggingConnectionDetails getContainerConnectionDetails( ContainerConnectionSource source) { return new OpenTelemetryLoggingContainerConnectionDetails(source); } private static final class OpenTelemetryLoggingContainerConnectionDetails - extends ContainerConnectionDetails implements OtlpLoggingConnectionDetails { + extends ContainerConnectionDetails implements OpenTelemetryLoggingConnectionDetails { private OpenTelemetryLoggingContainerConnectionDetails(ContainerConnectionSource source) { super(source); diff --git a/spring-boot-project/spring-boot-testcontainers-all/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryLoggingContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers-all/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryLoggingContainerConnectionDetailsFactory.java index 5395fdf86a..2055d06ac4 100644 --- a/spring-boot-project/spring-boot-testcontainers-all/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryLoggingContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers-all/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryLoggingContainerConnectionDetailsFactory.java @@ -19,15 +19,15 @@ package org.springframework.boot.testcontainers.service.connection.otlp; import org.testcontainers.containers.Container; import org.testcontainers.containers.GenericContainer; -import org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingConnectionDetails; -import org.springframework.boot.actuate.autoconfigure.logging.otlp.Transport; +import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingConnectionDetails; +import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.Transport; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** * {@link ContainerConnectionDetailsFactory} to create - * {@link OtlpLoggingConnectionDetails} from a + * {@link OpenTelemetryLoggingConnectionDetails} from a * {@link ServiceConnection @ServiceConnection}-annotated {@link GenericContainer} using * the {@code "otel/opentelemetry-collector-contrib"} image. * @@ -35,7 +35,7 @@ import org.springframework.boot.testcontainers.service.connection.ServiceConnect * @author Moritz Halbritter */ class OpenTelemetryLoggingContainerConnectionDetailsFactory - extends ContainerConnectionDetailsFactory, OtlpLoggingConnectionDetails> { + extends ContainerConnectionDetailsFactory, OpenTelemetryLoggingConnectionDetails> { private static final int OTLP_GRPC_PORT = 4317; @@ -43,17 +43,17 @@ class OpenTelemetryLoggingContainerConnectionDetailsFactory OpenTelemetryLoggingContainerConnectionDetailsFactory() { super("otel/opentelemetry-collector-contrib", - "org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingAutoConfiguration"); + "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportAutoConfiguration"); } @Override - protected OtlpLoggingConnectionDetails getContainerConnectionDetails( + protected OpenTelemetryLoggingConnectionDetails getContainerConnectionDetails( ContainerConnectionSource> source) { return new OpenTelemetryLoggingContainerConnectionDetails(source); } private static final class OpenTelemetryLoggingContainerConnectionDetails - extends ContainerConnectionDetails> implements OtlpLoggingConnectionDetails { + extends ContainerConnectionDetails> implements OpenTelemetryLoggingConnectionDetails { private OpenTelemetryLoggingContainerConnectionDetails(ContainerConnectionSource> source) { super(source);