Create spring-boot-opentelemetry module

Renames `management.otlp.logging` properties to
`management.opentelemetry.logging`.

Issue: 46149
This commit is contained in:
Phillip Webb 2025-05-27 15:27:27 -07:00
parent 3909b9d483
commit ded9701db5
57 changed files with 930 additions and 779 deletions

View File

@ -119,6 +119,7 @@ include "spring-boot-project:spring-boot-metrics"
include "spring-boot-project:spring-boot-mongodb" include "spring-boot-project:spring-boot-mongodb"
include "spring-boot-project:spring-boot-mustache" include "spring-boot-project:spring-boot-mustache"
include "spring-boot-project:spring-boot-netty" 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-parent"
include "spring-boot-project:spring-boot-pulsar" include "spring-boot-project:spring-boot-pulsar"
include "spring-boot-project:spring-boot-quartz" include "spring-boot-project:spring-boot-quartz"

View File

@ -56,6 +56,7 @@ dependencies {
optional(project(":spring-boot-project:spring-boot-jsonb")) optional(project(":spring-boot-project:spring-boot-jsonb"))
optional(project(":spring-boot-project:spring-boot-kafka")) optional(project(":spring-boot-project:spring-boot-kafka"))
optional(project(":spring-boot-project:spring-boot-metrics")) 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-r2dbc"))
optional(project(":spring-boot-project:spring-boot-restclient")) optional(project(":spring-boot-project:spring-boot-restclient"))
optional(project(":spring-boot-project:spring-boot-security-oauth2-client")) optional(project(":spring-boot-project:spring-boot-security-oauth2-client"))

View File

@ -1,61 +0,0 @@
/*
* Copyright 2012-present 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<LogRecordExporter> logRecordExporters) {
return BatchLogRecordProcessor.builder(LogRecordExporter.composite(logRecordExporters.orderedStream().toList()))
.build();
}
@Bean
@ConditionalOnMissingBean
SdkLoggerProvider otelSdkLoggerProvider(Resource resource, ObjectProvider<LogRecordProcessor> logRecordProcessors,
ObjectProvider<SdkLoggerProviderBuilderCustomizer> customizers) {
SdkLoggerProviderBuilder builder = SdkLoggerProvider.builder().setResource(resource);
logRecordProcessors.orderedStream().forEach(builder::addLogRecordProcessor);
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
}

View File

@ -1,112 +0,0 @@
/*
* Copyright 2012-present 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> 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> 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();
}
}
}

View File

@ -24,7 +24,6 @@ import io.micrometer.registry.otlp.OtlpMetricsSender;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.metrics.export.ConditionalOnEnabledMetricsExport; 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.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 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.context.properties.EnableConfigurationProperties;
import org.springframework.boot.metrics.autoconfigure.CompositeMeterRegistryAutoConfiguration; import org.springframework.boot.metrics.autoconfigure.CompositeMeterRegistryAutoConfiguration;
import org.springframework.boot.metrics.autoconfigure.MetricsAutoConfiguration; import org.springframework.boot.metrics.autoconfigure.MetricsAutoConfiguration;
import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetryProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.task.VirtualThreadTaskExecutor; import org.springframework.core.task.VirtualThreadTaskExecutor;

View File

@ -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.otlp.OtlpMetricsProperties.Meter;
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryPropertiesConfigAdapter; import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryPropertiesConfigAdapter;
import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryProperties; import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetryProperties;
import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryResourceAttributes; import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetryResourceAttributes;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;

View File

@ -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.jackson.JacksonEndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.env.EnvironmentEndpointAutoConfiguration 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.HeapDumpWebEndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.management.ThreadDumpEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.management.ThreadDumpEndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.metrics.data.RepositoryMetricsAutoConfiguration 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.client.HttpClientObservationsAutoConfiguration
org.springframework.boot.actuate.autoconfigure.observation.web.reactive.WebFluxObservationAutoConfiguration org.springframework.boot.actuate.autoconfigure.observation.web.reactive.WebFluxObservationAutoConfiguration
org.springframework.boot.actuate.autoconfigure.observation.web.servlet.WebMvcObservationAutoConfiguration 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.ConnectionFactoryHealthContributorAutoConfiguration
org.springframework.boot.actuate.autoconfigure.r2dbc.R2dbcObservationAutoConfiguration org.springframework.boot.actuate.autoconfigure.r2dbc.R2dbcObservationAutoConfiguration
org.springframework.boot.actuate.autoconfigure.sbom.SbomEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.sbom.SbomEndpointAutoConfiguration

View File

@ -1,226 +0,0 @@
/*
* Copyright 2012-present 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<LogRecordData> 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();
}
}
}

View File

@ -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.OtlpMetricsExportAutoConfiguration.PropertiesOtlpMetricsConnectionDetails;
import org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsProperties.Meter; 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 org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;

View File

@ -1,190 +0,0 @@
/*
* Copyright 2012-present 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();
}
}
}

View File

@ -30,8 +30,8 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.EnumSource;
import org.slf4j.MDC; import org.slf4j.MDC;
import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations; 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.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.ForkedClassPath; import org.springframework.boot.testsupport.classpath.ForkedClassPath;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
@ -173,7 +173,7 @@ class BaggagePropagationIntegrationTests {
@Override @Override
public ApplicationContextRunner get() { public ApplicationContextRunner get() {
return new ApplicationContextRunner().withInitializer(new OtelApplicationContextInitializer()) return new ApplicationContextRunner().withInitializer(new OtelApplicationContextInitializer())
.withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(OpenTelemetrySdkAutoConfiguration.class,
org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.class)) org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.class))
.withPropertyValues("management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", .withPropertyValues("management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp",
"management.tracing.baggage.correlation.fields=country-code,bp"); "management.tracing.baggage.correlation.fields=country-code,bp");
@ -199,7 +199,7 @@ class BaggagePropagationIntegrationTests {
@Override @Override
public ApplicationContextRunner get() { public ApplicationContextRunner get() {
return new ApplicationContextRunner().withInitializer(new OtelApplicationContextInitializer()) return new ApplicationContextRunner().withInitializer(new OtelApplicationContextInitializer())
.withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(OpenTelemetrySdkAutoConfiguration.class,
org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.class)) org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.class))
.withPropertyValues("management.tracing.propagation.type=W3C", .withPropertyValues("management.tracing.propagation.type=W3C",
"management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", "management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp",
@ -239,7 +239,7 @@ class BaggagePropagationIntegrationTests {
@Override @Override
public ApplicationContextRunner get() { public ApplicationContextRunner get() {
return new ApplicationContextRunner().withInitializer(new OtelApplicationContextInitializer()) return new ApplicationContextRunner().withInitializer(new OtelApplicationContextInitializer())
.withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(OpenTelemetrySdkAutoConfiguration.class,
org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.class)) org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.class))
.withPropertyValues("management.tracing.propagation.type=B3", .withPropertyValues("management.tracing.propagation.type=B3",
"management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", "management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp",
@ -253,7 +253,7 @@ class BaggagePropagationIntegrationTests {
@Override @Override
public ApplicationContextRunner get() { public ApplicationContextRunner get() {
return new ApplicationContextRunner().withInitializer(new OtelApplicationContextInitializer()) return new ApplicationContextRunner().withInitializer(new OtelApplicationContextInitializer())
.withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(OpenTelemetrySdkAutoConfiguration.class,
org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.class)) org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.class))
.withPropertyValues("management.tracing.propagation.type=B3_MULTI", .withPropertyValues("management.tracing.propagation.type=B3_MULTI",
"management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", "management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp",

View File

@ -90,7 +90,7 @@ class OpenTelemetryTracingAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of( .withConfiguration(AutoConfigurations.of(
org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration.class, org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetrySdkAutoConfiguration.class,
OpenTelemetryTracingAutoConfiguration.class)); OpenTelemetryTracingAutoConfiguration.class));
@Test @Test

View File

@ -47,10 +47,10 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; 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.MicrometerTracingAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingAutoConfigurationIntegrationTests.MockGrpcServer.RecordedGrpcRequest; import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingAutoConfigurationIntegrationTests.MockGrpcServer.RecordedGrpcRequest;
import org.springframework.boot.autoconfigure.AutoConfigurations; 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.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -65,7 +65,7 @@ class OtlpTracingAutoConfigurationIntegrationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withPropertyValues("management.tracing.sampling.probability=1.0") .withPropertyValues("management.tracing.sampling.probability=1.0")
.withConfiguration(AutoConfigurations.of(ObservationAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(ObservationAutoConfiguration.class,
MicrometerTracingAutoConfiguration.class, OpenTelemetryAutoConfiguration.class, MicrometerTracingAutoConfiguration.class, OpenTelemetrySdkAutoConfiguration.class,
org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.class, org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.class,
OtlpTracingAutoConfiguration.class)); OtlpTracingAutoConfiguration.class));

View File

@ -4,4 +4,6 @@ org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfi
org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.info.InfoContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.info.InfoContributorAutoConfiguration
org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration 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 org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration

View File

@ -2048,6 +2048,7 @@ bom {
"spring-boot-mustache", "spring-boot-mustache",
"spring-boot-neo4j", "spring-boot-neo4j",
"spring-boot-netty", "spring-boot-netty",
"spring-boot-opentelemetry",
"spring-boot-properties-migrator", "spring-boot-properties-migrator",
"spring-boot-pulsar", "spring-boot-pulsar",
"spring-boot-quartz", "spring-boot-quartz",

View File

@ -32,6 +32,7 @@ dependencies {
dockerTestImplementation(project(":spring-boot-project:spring-boot-jdbc")) dockerTestImplementation(project(":spring-boot-project:spring-boot-jdbc"))
dockerTestImplementation(project(":spring-boot-project:spring-boot-flyway")) dockerTestImplementation(project(":spring-boot-project:spring-boot-flyway"))
dockerTestImplementation(project(":spring-boot-project:spring-boot-liquibase")) 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-pulsar"))
dockerTestImplementation(project(":spring-boot-project:spring-boot-r2dbc")) dockerTestImplementation(project(":spring-boot-project:spring-boot-r2dbc"))
dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker")) dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker"))
@ -45,6 +46,7 @@ dependencies {
dockerTestRuntimeOnly("com.clickhouse:clickhouse-r2dbc") dockerTestRuntimeOnly("com.clickhouse:clickhouse-r2dbc")
dockerTestRuntimeOnly("com.microsoft.sqlserver:mssql-jdbc") dockerTestRuntimeOnly("com.microsoft.sqlserver:mssql-jdbc")
dockerTestRuntimeOnly("com.oracle.database.r2dbc:oracle-r2dbc") dockerTestRuntimeOnly("com.oracle.database.r2dbc:oracle-r2dbc")
dockerTestRuntimeOnly("io.opentelemetry:opentelemetry-exporter-otlp")
dockerTestRuntimeOnly("io.r2dbc:r2dbc-mssql") dockerTestRuntimeOnly("io.r2dbc:r2dbc-mssql")
dockerTestRuntimeOnly("org.postgresql:postgresql") dockerTestRuntimeOnly("org.postgresql:postgresql")
dockerTestRuntimeOnly("org.postgresql:r2dbc-postgresql") dockerTestRuntimeOnly("org.postgresql:r2dbc-postgresql")
@ -67,6 +69,7 @@ dependencies {
optional(project(":spring-boot-project:spring-boot-liquibase")) optional(project(":spring-boot-project:spring-boot-liquibase"))
optional(project(":spring-boot-project:spring-boot-mongodb")) optional(project(":spring-boot-project:spring-boot-mongodb"))
optional(project(":spring-boot-project:spring-boot-neo4j")) 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-pulsar"))
optional(project(":spring-boot-project:spring-boot-r2dbc")) optional(project(":spring-boot-project:spring-boot-r2dbc"))
optional(project(":spring-boot-project:spring-boot-zipkin")) optional(project(":spring-boot-project:spring-boot-zipkin"))

View File

@ -16,9 +16,9 @@
package org.springframework.boot.docker.compose.service.connection.otlp; 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.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 org.springframework.boot.testsupport.container.TestImage;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -32,7 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class GrafanaOpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests { class GrafanaOpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests {
@DockerComposeTest(composeFile = "otlp-compose.yaml", image = TestImage.GRAFANA_OTEL_LGTM) @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.HTTP)).startsWith("http://").endsWith("/v1/logs");
assertThat(connectionDetails.getUrl(Transport.GRPC)).startsWith("http://").endsWith("/v1/logs"); assertThat(connectionDetails.getUrl(Transport.GRPC)).startsWith("http://").endsWith("/v1/logs");
} }

View File

@ -16,9 +16,9 @@
package org.springframework.boot.docker.compose.service.connection.otlp; 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.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 org.springframework.boot.testsupport.container.TestImage;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -32,7 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class OpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests { class OpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests {
@DockerComposeTest(composeFile = "otlp-compose.yaml", image = TestImage.OPENTELEMETRY) @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.HTTP)).startsWith("http://").endsWith("/v1/logs");
assertThat(connectionDetails.getUrl(Transport.GRPC)).startsWith("http://").endsWith("/v1/logs"); assertThat(connectionDetails.getUrl(Transport.GRPC)).startsWith("http://").endsWith("/v1/logs");
} }

View File

@ -16,20 +16,20 @@
package org.springframework.boot.docker.compose.service.connection.otlp; 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.core.RunningService;
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory;
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; 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 DockerComposeConnectionDetailsFactory} to create
* {@link OtlpLoggingConnectionDetails} for an OTLP service. * {@link OpenTelemetryLoggingConnectionDetails} for an OTLP service.
* *
* @author Eddú Meléndez * @author Eddú Meléndez
*/ */
class OpenTelemetryLoggingDockerComposeConnectionDetailsFactory class OpenTelemetryLoggingDockerComposeConnectionDetailsFactory
extends DockerComposeConnectionDetailsFactory<OtlpLoggingConnectionDetails> { extends DockerComposeConnectionDetailsFactory<OpenTelemetryLoggingConnectionDetails> {
private static final String[] OPENTELEMETRY_IMAGE_NAMES = { "otel/opentelemetry-collector-contrib", private static final String[] OPENTELEMETRY_IMAGE_NAMES = { "otel/opentelemetry-collector-contrib",
"grafana/otel-lgtm" }; "grafana/otel-lgtm" };
@ -40,16 +40,17 @@ class OpenTelemetryLoggingDockerComposeConnectionDetailsFactory
OpenTelemetryLoggingDockerComposeConnectionDetailsFactory() { OpenTelemetryLoggingDockerComposeConnectionDetailsFactory() {
super(OPENTELEMETRY_IMAGE_NAMES, super(OPENTELEMETRY_IMAGE_NAMES,
"org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingAutoConfiguration"); "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportAutoConfiguration");
} }
@Override @Override
protected OtlpLoggingConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { protected OpenTelemetryLoggingConnectionDetails getDockerComposeConnectionDetails(
DockerComposeConnectionSource source) {
return new OpenTelemetryLoggingDockerComposeConnectionDetails(source.getRunningService()); return new OpenTelemetryLoggingDockerComposeConnectionDetails(source.getRunningService());
} }
private static final class OpenTelemetryLoggingDockerComposeConnectionDetails extends DockerComposeConnectionDetails private static final class OpenTelemetryLoggingDockerComposeConnectionDetails extends DockerComposeConnectionDetails
implements OtlpLoggingConnectionDetails { implements OpenTelemetryLoggingConnectionDetails {
private final String host; private final String host;

View File

@ -128,6 +128,7 @@ dependencies {
autoConfiguration(project(path: ":spring-boot-project:spring-boot-mustache", configuration: "autoConfigurationMetadata")) 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-neo4j", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-netty", 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-pulsar", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-quartz", configuration: "autoConfigurationMetadata")) autoConfiguration(project(path: ":spring-boot-project:spring-boot-quartz", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-r2dbc", configuration: "autoConfigurationMetadata")) autoConfiguration(project(path: ":spring-boot-project:spring-boot-r2dbc", configuration: "autoConfigurationMetadata"))
@ -212,6 +213,7 @@ dependencies {
configurationProperties(project(path: ":spring-boot-project:spring-boot-mustache", configuration: "configurationPropertiesMetadata")) 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-neo4j", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-netty", 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-pulsar", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-quartz", configuration: "configurationPropertiesMetadata")) configurationProperties(project(path: ":spring-boot-project:spring-boot-quartz", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-r2dbc", configuration: "configurationPropertiesMetadata")) configurationProperties(project(path: ":spring-boot-project:spring-boot-r2dbc", configuration: "configurationPropertiesMetadata"))

View File

@ -42,9 +42,10 @@ You have to provide the location of the OpenTelemetry logs endpoint to configure
[configprops,yaml] [configprops,yaml]
---- ----
management: management:
otlp: opentelemetry:
logging: 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. NOTE: The OpenTelemetry Logback appender and Log4j appender are not part of Spring Boot.

View File

@ -110,7 +110,7 @@ The following service connections are currently supported:
| javadoc:org.springframework.boot.neo4j.autoconfigure.Neo4jConnectionDetails[] | javadoc:org.springframework.boot.neo4j.autoconfigure.Neo4jConnectionDetails[]
| Containers named "neo4j" or "bitnami/neo4j" | 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" | Containers named "otel/opentelemetry-collector-contrib", "grafana/otel-lgtm"
| javadoc:org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsConnectionDetails[] | javadoc:org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsConnectionDetails[]

View File

@ -155,7 +155,7 @@ The following service connection factories are provided in the `spring-boot-test
| javadoc:org.springframework.boot.neo4j.autoconfigure.Neo4jConnectionDetails[] | javadoc:org.springframework.boot.neo4j.autoconfigure.Neo4jConnectionDetails[]
| Containers of type javadoc:{url-testcontainers-neo4j-javadoc}/org.testcontainers.containers.Neo4jContainer[] | 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[] | Containers named "otel/opentelemetry-collector-contrib" or of type javadoc:org.testcontainers.grafana.LgtmStackContainer[]
| javadoc:org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsConnectionDetails[] | javadoc:org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsConnectionDetails[]

View File

@ -0,0 +1,44 @@
/*
* Copyright 2012-present 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.
*/
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")
}

View File

@ -14,17 +14,18 @@
* limitations under the License. * 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; 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 * @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. * Address to where logs will be published.

View File

@ -0,0 +1,63 @@
/*
* Copyright 2012-present 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();
}
}
}

View File

@ -14,12 +14,12 @@
* limitations under the License. * 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.api.OpenTelemetry;
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
import io.opentelemetry.sdk.logs.SdkLoggerProvider; 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.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@ -27,15 +27,16 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for OTLP logging. * {@link EnableAutoConfiguration Auto-configuration} for OpenTelemetry logging exports.
* *
* @author Toshiaki Maki * @author Toshiaki Maki
* @since 3.4.0 * @since 4.0.0
*/ */
@AutoConfiguration @AutoConfiguration
@ConditionalOnClass({ SdkLoggerProvider.class, OpenTelemetry.class, OtlpHttpLogRecordExporter.class }) @ConditionalOnClass({ ConditionalOnEnabledLoggingExport.class, OpenTelemetry.class, SdkLoggerProvider.class })
@EnableConfigurationProperties(OtlpLoggingProperties.class) @ConditionalOnEnabledLoggingExport("opentelemetry")
@Import({ OtlpLoggingConfigurations.ConnectionDetails.class, OtlpLoggingConfigurations.Exporters.class }) @EnableConfigurationProperties(OpenTelemetryLoggingExportProperties.class)
public class OtlpLoggingAutoConfiguration { @Import({ OpenTelemetryLoggingConnectionDetailsConfiguration.class, OpenTelemetryLoggingTransportConfiguration.class })
public class OpenTelemetryLoggingExportAutoConfiguration {
} }

View File

@ -14,7 +14,7 @@
* limitations under the License. * 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.time.Duration;
import java.util.HashMap; import java.util.HashMap;
@ -23,13 +23,13 @@ import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
/** /**
* Configuration properties for exporting logs using OTLP. * Configuration properties for exporting logs using OpenTelemetry.
* *
* @author Jonatan Ivanov * @author Jonatan Ivanov
* @since 3.4.0 * @since 4.0.0
*/ */
@ConfigurationProperties("management.otlp.logging") @ConfigurationProperties("management.opentelemetry.logging.export")
public class OtlpLoggingProperties { public class OpenTelemetryLoggingExportProperties {
/** /**
* URL to the OTel collector's HTTP API. * URL to the OTel collector's HTTP API.

View File

@ -0,0 +1,75 @@
/*
* Copyright 2012-present 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> 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> 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();
}
}

View File

@ -14,13 +14,13 @@
* limitations under the License. * 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 * @author Moritz Halbritter
* @since 3.4.0 * @since 4.0.0
*/ */
public enum Transport { public enum Transport {

View File

@ -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;

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.actuate.autoconfigure.opentelemetry; package org.springframework.boot.opentelemetry.autoconfigure;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.actuate.autoconfigure.opentelemetry; package org.springframework.boot.opentelemetry.autoconfigure;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -39,9 +39,9 @@ import org.springframework.util.StringUtils;
* Resource Specification</a> * Resource Specification</a>
* *
* @author Dmytro Nosan * @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. * Default value for service name if {@code service.name} is not set.
@ -52,7 +52,7 @@ public final class OpenTelemetryResourceAttributes {
private final Map<String, String> resourceAttributes; private final Map<String, String> resourceAttributes;
private final Function<String, String> getEnv; private final Function<String, String> systemEnvironment;
/** /**
* Creates a new instance of {@link OpenTelemetryResourceAttributes}. * Creates a new instance of {@link OpenTelemetryResourceAttributes}.
@ -67,14 +67,14 @@ public final class OpenTelemetryResourceAttributes {
* Creates a new {@link OpenTelemetryResourceAttributes} instance. * Creates a new {@link OpenTelemetryResourceAttributes} instance.
* @param environment the environment * @param environment the environment
* @param resourceAttributes user-provided resource attributes to be used * @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<String, String> resourceAttributes, OpenTelemetryResourceAttributes(Environment environment, Map<String, String> resourceAttributes,
Function<String, String> getEnv) { Function<String, String> systemEnvironment) {
Assert.notNull(environment, "'environment' must not be null"); Assert.notNull(environment, "'environment' must not be null");
this.environment = environment; this.environment = environment;
this.resourceAttributes = (resourceAttributes != null) ? resourceAttributes : Collections.emptyMap(); 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) { 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; return value;
} }
byte[] bytes = value.getBytes(StandardCharsets.UTF_8); 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++) { for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i]; byte b = bytes[i];
if (b != '%') { if (b != '%') {
bos.write(b); out.write(b);
continue; continue;
} }
int u = decodeHex(bytes, i + 1); int u = decodeHex(bytes, i + 1);
int l = decodeHex(bytes, i + 2); int l = decodeHex(bytes, i + 2);
if (u >= 0 && l >= 0) { if (u >= 0 && l >= 0) {
bos.write((u << 4) + l); out.write((u << 4) + l);
} }
else { else {
throw new IllegalArgumentException( throw new IllegalArgumentException(
@ -185,7 +185,7 @@ public final class OpenTelemetryResourceAttributes {
} }
i += 2; i += 2;
} }
return bos.toString(StandardCharsets.UTF_8); return out.toString(StandardCharsets.UTF_8);
} }
private static int decodeHex(byte[] bytes, int index) { private static int decodeHex(byte[] bytes, int index) {

View File

@ -14,13 +14,17 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.actuate.autoconfigure.opentelemetry; package org.springframework.boot.opentelemetry.autoconfigure;
import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.OpenTelemetrySdkBuilder; import io.opentelemetry.sdk.OpenTelemetrySdkBuilder;
import io.opentelemetry.sdk.logs.LogRecordProcessor;
import io.opentelemetry.sdk.logs.SdkLoggerProvider; 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.metrics.SdkMeterProvider;
import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.resources.ResourceBuilder; import io.opentelemetry.sdk.resources.ResourceBuilder;
@ -33,37 +37,41 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for OpenTelemetry. * {@link EnableAutoConfiguration Auto-configuration} for the OpenTelemetry SDK.
* *
* @author Moritz Halbritter * @author Moritz Halbritter
* @since 3.2.0 * @since 4.0.0
*/ */
@AutoConfiguration @AutoConfiguration
@ConditionalOnClass(OpenTelemetrySdk.class) @ConditionalOnClass({ OpenTelemetry.class, OpenTelemetrySdk.class })
@EnableConfigurationProperties(OpenTelemetryProperties.class) @EnableConfigurationProperties(OpenTelemetryProperties.class)
public class OpenTelemetryAutoConfiguration { public class OpenTelemetrySdkAutoConfiguration {
OpenTelemetrySdkAutoConfiguration() {
}
@Bean @Bean
@ConditionalOnMissingBean(OpenTelemetry.class) @ConditionalOnMissingBean(OpenTelemetry.class)
OpenTelemetrySdk openTelemetry(ObjectProvider<SdkTracerProvider> tracerProvider, OpenTelemetrySdk openTelemetrySdk(ObjectProvider<SdkTracerProvider> openTelemetrySdkTracerProvider,
ObjectProvider<ContextPropagators> propagators, ObjectProvider<SdkLoggerProvider> loggerProvider, ObjectProvider<ContextPropagators> openTelemetryContextPropagators,
ObjectProvider<SdkMeterProvider> meterProvider) { ObjectProvider<SdkLoggerProvider> openTelemetrySdkLoggerProvider,
ObjectProvider<SdkMeterProvider> openTelemetrySdkMeterProvider) {
OpenTelemetrySdkBuilder builder = OpenTelemetrySdk.builder(); OpenTelemetrySdkBuilder builder = OpenTelemetrySdk.builder();
tracerProvider.ifAvailable(builder::setTracerProvider); openTelemetrySdkTracerProvider.ifAvailable(builder::setTracerProvider);
propagators.ifAvailable(builder::setPropagators); openTelemetryContextPropagators.ifAvailable(builder::setPropagators);
loggerProvider.ifAvailable(builder::setLoggerProvider); openTelemetrySdkLoggerProvider.ifAvailable(builder::setLoggerProvider);
meterProvider.ifAvailable(builder::setMeterProvider); openTelemetrySdkMeterProvider.ifAvailable(builder::setMeterProvider);
return builder.build(); return builder.build();
} }
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
Resource openTelemetryResource(Environment environment, OpenTelemetryProperties properties) { Resource openTelemetryResource(Environment environment, OpenTelemetryProperties properties) {
Resource resource = Resource.getDefault(); return Resource.getDefault().merge(toResource(environment, properties));
return resource.merge(toResource(environment, properties));
} }
private Resource toResource(Environment environment, OpenTelemetryProperties properties) { private Resource toResource(Environment environment, OpenTelemetryProperties properties) {
@ -72,4 +80,30 @@ public class OpenTelemetryAutoConfiguration {
return builder.build(); return builder.build();
} }
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SdkLoggerProvider.class)
static class LoggerConfiguration {
@Bean
@ConditionalOnMissingBean
BatchLogRecordProcessor openTelemetryBatchLogRecordProcessor(
ObjectProvider<LogRecordExporter> logRecordExporters) {
LogRecordExporter exporter = LogRecordExporter.composite(logRecordExporters.orderedStream().toList());
return BatchLogRecordProcessor.builder(exporter).build();
}
@Bean
@ConditionalOnMissingBean
SdkLoggerProvider openTelemetrySdkLoggerProvider(Resource openTelemetryResource,
ObjectProvider<LogRecordProcessor> logRecordProcessors,
ObjectProvider<SdkLoggerProviderBuilderCustomizer> customizers) {
SdkLoggerProviderBuilder builder = SdkLoggerProvider.builder();
builder.setResource(openTelemetryResource);
logRecordProcessors.orderedStream().forEach(builder::addLogRecordProcessor);
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
}
} }

View File

@ -14,7 +14,7 @@
* limitations under the License. * 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.SdkLoggerProvider;
import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; 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}. * that is used to create the auto-configured {@link SdkLoggerProvider}.
* *
* @author Toshiaki Maki * @author Toshiaki Maki
* @since 3.4.0 * @since 4.0.0
*/ */
@FunctionalInterface @FunctionalInterface
public interface SdkLoggerProviderBuilderCustomizer { public interface SdkLoggerProviderBuilderCustomizer {

View File

@ -17,4 +17,4 @@
/** /**
* Auto-configuration for OpenTelemetry. * Auto-configuration for OpenTelemetry.
*/ */
package org.springframework.boot.actuate.autoconfigure.opentelemetry; package org.springframework.boot.opentelemetry.autoconfigure;

View File

@ -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<java.lang.String,java.lang.String>",
"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": []
}

View File

@ -0,0 +1,2 @@
org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportAutoConfiguration
org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetrySdkAutoConfiguration

View File

@ -14,7 +14,7 @@
* limitations under the License. * 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.io.IOException;
import java.nio.charset.StandardCharsets; 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.BeforeEach;
import org.junit.jupiter.api.Test; 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.autoconfigure.AutoConfigurations;
import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetrySdkAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Integration tests for {@link OtlpLoggingAutoConfiguration}. * Integration tests for {@link OpenTelemetryLoggingExportAutoConfiguration}.
* *
* @author Toshiaki Maki * @author Toshiaki Maki
*/ */
class OtlpLoggingAutoConfigurationIntegrationTests { class OpenTelemetryLoggingExportAutoConfigurationIntegrationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withPropertyValues("spring.application.name=otlp-logs-test", .withPropertyValues("spring.application.name=otlp-logs-test",
"management.otlp.logging.headers.Authorization=Bearer my-token") "management.opentelemetry.logging.export.headers.Authorization=Bearer my-token")
.withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(OpenTelemetrySdkAutoConfiguration.class,
OpenTelemetryLoggingAutoConfiguration.class, OtlpLoggingAutoConfiguration.class)); OpenTelemetryLoggingExportAutoConfiguration.class));
private final MockWebServer mockWebServer = new MockWebServer(); private final MockWebServer mockWebServer = new MockWebServer();
@ -69,7 +68,7 @@ class OtlpLoggingAutoConfigurationIntegrationTests {
void httpLogRecordExporterShouldUseProtobufAndNoCompressionByDefault() { void httpLogRecordExporterShouldUseProtobufAndNoCompressionByDefault() {
this.mockWebServer.enqueue(new MockResponse()); this.mockWebServer.enqueue(new MockResponse());
this.contextRunner 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())) .formatted(this.mockWebServer.getPort()))
.run((context) -> { .run((context) -> {
logMessage(context); logMessage(context);
@ -89,8 +88,8 @@ class OtlpLoggingAutoConfigurationIntegrationTests {
void httpLogRecordExporterCanBeConfiguredToUseGzipCompression() { void httpLogRecordExporterCanBeConfiguredToUseGzipCompression() {
this.mockWebServer.enqueue(new MockResponse()); this.mockWebServer.enqueue(new MockResponse());
this.contextRunner 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()), "management.otlp.logging.compression=gzip") .formatted(this.mockWebServer.getPort()), "management.opentelemetry.logging.export.compression=gzip")
.run((context) -> { .run((context) -> {
logMessage(context); logMessage(context);
RecordedRequest request = this.mockWebServer.takeRequest(10, TimeUnit.SECONDS); RecordedRequest request = this.mockWebServer.takeRequest(10, TimeUnit.SECONDS);

View File

@ -14,13 +14,15 @@
* limitations under the License. * 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 java.util.function.Supplier;
import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;
import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder;
import io.opentelemetry.sdk.logs.export.LogRecordExporter; import io.opentelemetry.sdk.logs.export.LogRecordExporter;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import org.assertj.core.api.InstanceOfAssertFactories; 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.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; 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.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.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -38,131 +44,146 @@ import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests for {@link OtlpLoggingAutoConfiguration}. * Tests for {@link OpenTelemetryLoggingExportAutoConfiguration}.
* *
* @author Toshiaki Maki * @author Toshiaki Maki
* @author Moritz Halbritter * @author Moritz Halbritter
*/ */
class OtlpLoggingAutoConfigurationTests { class OpenTelemetryLoggingExportAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() private final ApplicationContextRunner contextRunner;
.withConfiguration(AutoConfigurations.of(OtlpLoggingAutoConfiguration.class));
@Test OpenTelemetryLoggingExportAutoConfigurationTests() {
void shouldNotSupplyBeansIfPropertyIsNotSet() { this.contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations
this.contextRunner.run((context) -> { .of(OpenTelemetrySdkAutoConfiguration.class, OpenTelemetryLoggingExportAutoConfiguration.class));
assertThat(context).doesNotHaveBean(OtlpLoggingConnectionDetails.class);
assertThat(context).doesNotHaveBean(OtlpHttpLogRecordExporter.class);
});
} }
@Test @Test
void shouldSupplyBeans() { void registeredInAutoConfigurationImports() {
this.contextRunner.withPropertyValues("management.otlp.logging.endpoint=http://localhost:4318/v1/logs") assertThat(ImportCandidates.load(AutoConfiguration.class, null).getCandidates())
.run((context) -> { .contains(OpenTelemetryLoggingExportAutoConfiguration.class.getName());
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);
});
} }
@ParameterizedTest @ParameterizedTest
@ValueSource(strings = { "io.opentelemetry.sdk.logs", "io.opentelemetry.api", @ValueSource(strings = { "io.opentelemetry.sdk.logs", "io.opentelemetry.api",
"io.opentelemetry.exporter.otlp.http.logs" }) "io.opentelemetry.exporter.otlp.http.logs" })
void shouldNotSupplyBeansIfDependencyIsMissing(String packageName) { void whenOpenTelemetryIsNotOnClasspathDoesNotProvideBeans(String packageName) {
this.contextRunner.withClassLoader(new FilteredClassLoader(packageName)).run((context) -> { this.contextRunner.withClassLoader(new FilteredClassLoader(packageName)).run((context) -> {
assertThat(context).doesNotHaveBean(OtlpLoggingConnectionDetails.class); assertThat(context).doesNotHaveBean(OpenTelemetryLoggingConnectionDetails.class);
assertThat(context).doesNotHaveBean(OtlpHttpLogRecordExporter.class); assertThat(context).doesNotHaveBean(OtlpHttpLogRecordExporter.class);
}); });
} }
@Test @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 this.contextRunner
.withPropertyValues("management.logging.export.enabled=false", .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) -> { .run((context) -> {
assertThat(context).hasSingleBean(OtlpLoggingConnectionDetails.class); assertThat(context).doesNotHaveBean(OpenTelemetryLoggingConnectionDetails.class);
assertThat(context).doesNotHaveBean(LogRecordExporter.class); assertThat(context).doesNotHaveBean(LogRecordExporter.class);
}); });
} }
@Test @Test
void shouldBackOffWhenOtlpLoggingExportPropertyIsNotEnabled() { void whenHasCustomHttpExporterDoesNotProvideExporterBean() {
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() {
this.contextRunner.withUserConfiguration(CustomHttpExporterConfiguration.class) this.contextRunner.withUserConfiguration(CustomHttpExporterConfiguration.class)
.run((context) -> assertThat(context).hasBean("customOtlpHttpLogRecordExporter") .run((context) -> assertThat(context).hasBean("customOtlpHttpLogRecordExporter")
.hasSingleBean(LogRecordExporter.class)); .hasSingleBean(LogRecordExporter.class));
} }
@Test @Test
void shouldBackOffWhenCustomGrpcExporterIsDefined() { void whenHasCustomGrpcExporterDoesNotProvideExporterBean() {
this.contextRunner.withUserConfiguration(CustomGrpcExporterConfiguration.class) this.contextRunner.withUserConfiguration(CustomGrpcExporterConfiguration.class)
.run((context) -> assertThat(context).hasBean("customOtlpGrpcLogRecordExporter") .run((context) -> assertThat(context).hasBean("customOtlpGrpcLogRecordExporter")
.hasSingleBean(LogRecordExporter.class)); .hasSingleBean(LogRecordExporter.class));
} }
// FIXME
@Test @Test
void shouldBackOffWhenCustomOtlpLoggingConnectionDetailsIsDefined() { void whenHasCustomLoggingConnectionDetailsDoesNotProvideExporterBean() {
this.contextRunner.withUserConfiguration(CustomOtlpLoggingConnectionDetails.class).run((context) -> { this.contextRunner.withUserConfiguration(CustomOtlpLoggingConnectionDetailsConfiguration.class)
assertThat(context).hasSingleBean(OtlpLoggingConnectionDetails.class) .run((context) -> {
.doesNotHaveBean(PropertiesOtlpLoggingConnectionDetails.class); assertThat(context).hasSingleBean(OpenTelemetryLoggingConnectionDetails.class)
OtlpHttpLogRecordExporter otlpHttpLogRecordExporter = context.getBean(OtlpHttpLogRecordExporter.class); .doesNotHaveBean(PropertiesOpenTelemetryLoggingConnectionDetails.class);
assertThat(otlpHttpLogRecordExporter).extracting("delegate.httpSender.url") OtlpHttpLogRecordExporter otlpHttpLogRecordExporter = context.getBean(OtlpHttpLogRecordExporter.class);
.isEqualTo(HttpUrl.get("https://otel.example.com/v1/logs")); assertThat(otlpHttpLogRecordExporter).extracting("delegate.httpSender.url")
}); .isEqualTo(HttpUrl.get("https://otel.example.com/v1/logs"));
});
} }
@Test @Test
void shouldUseHttpExporterIfTransportIsNotSet() { void whenHasNoTransportPropertySetUsesHttpExporter() {
this.contextRunner.withPropertyValues("management.otlp.logging.endpoint=http://localhost:4318/v1/logs") this.contextRunner
.withPropertyValues("management.opentelemetry.logging.export.endpoint=http://localhost:4318/v1/logs")
.run((context) -> { .run((context) -> {
assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class) assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class);
.hasSingleBean(LogRecordExporter.class); assertThat(context).hasSingleBean(LogRecordExporter.class);
assertThat(context).doesNotHaveBean(OtlpGrpcLogRecordExporter.class); assertThat(context).doesNotHaveBean(OtlpGrpcLogRecordExporter.class);
}); });
} }
@Test @Test
void shouldUseHttpExporterIfTransportIsSetToHttp() { void whenHasTransportPropertySetToHttpUsesHttpExporter() {
this.contextRunner this.contextRunner
.withPropertyValues("management.otlp.logging.endpoint=http://localhost:4318/v1/logs", .withPropertyValues("management.opentelemetry.logging.export.endpoint=http://localhost:4318/v1/logs",
"management.otlp.logging.transport=http") "management.opentelemetry.logging.export.transport=http")
.run((context) -> { .run((context) -> {
assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class) assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class);
.hasSingleBean(LogRecordExporter.class); assertThat(context).hasSingleBean(LogRecordExporter.class);
assertThat(context).doesNotHaveBean(OtlpGrpcLogRecordExporter.class); assertThat(context).doesNotHaveBean(OtlpGrpcLogRecordExporter.class);
}); });
} }
@Test @Test
void shouldUseGrpcExporterIfTransportIsSetToGrpc() { void whenHasTransportPropertySetToGrpcUsesGrpcExporter() {
this.contextRunner this.contextRunner
.withPropertyValues("management.otlp.logging.endpoint=http://localhost:4318/v1/logs", .withPropertyValues("management.opentelemetry.logging.export.endpoint=http://localhost:4318/v1/logs",
"management.otlp.logging.transport=grpc") "management.opentelemetry.logging.export.transport=grpc")
.run((context) -> { .run((context) -> {
assertThat(context).hasSingleBean(OtlpGrpcLogRecordExporter.class) assertThat(context).hasSingleBean(OtlpGrpcLogRecordExporter.class);
.hasSingleBean(LogRecordExporter.class); assertThat(context).hasSingleBean(LogRecordExporter.class);
assertThat(context).doesNotHaveBean(OtlpHttpLogRecordExporter.class); assertThat(context).doesNotHaveBean(OtlpHttpLogRecordExporter.class);
}); });
} }
@Test @Test
void httpShouldUseMeterProviderIfSet() { void whenHasMeterProviderBeanAddsItToHttpExporter() {
this.contextRunner.withUserConfiguration(MeterProviderConfiguration.class) 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) -> { .run((context) -> {
OtlpHttpLogRecordExporter otlpHttpLogRecordExporter = context.getBean(OtlpHttpLogRecordExporter.class); OtlpHttpLogRecordExporter otlpHttpLogRecordExporter = context.getBean(OtlpHttpLogRecordExporter.class);
assertThat(otlpHttpLogRecordExporter.toBuilder()) assertThat(otlpHttpLogRecordExporter.toBuilder())
@ -173,10 +194,10 @@ class OtlpLoggingAutoConfigurationTests {
} }
@Test @Test
void grpcShouldUseMeterProviderIfSet() { void whenHasMeterProviderBeanAddsItToGrpcExporter() {
this.contextRunner.withUserConfiguration(MeterProviderConfiguration.class) 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",
"management.otlp.logging.transport=grpc") "management.opentelemetry.logging.export.transport=grpc")
.run((context) -> { .run((context) -> {
OtlpGrpcLogRecordExporter otlpGrpcLogRecordExporter = context.getBean(OtlpGrpcLogRecordExporter.class); OtlpGrpcLogRecordExporter otlpGrpcLogRecordExporter = context.getBean(OtlpGrpcLogRecordExporter.class);
assertThat(otlpGrpcLogRecordExporter.toBuilder()) 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) @Configuration(proxyBeanMethods = false)
private static final class MeterProviderConfiguration { private static final class MeterProviderConfiguration {
@ -219,10 +270,10 @@ class OtlpLoggingAutoConfigurationTests {
} }
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
private static final class CustomOtlpLoggingConnectionDetails { private static final class CustomOtlpLoggingConnectionDetailsConfiguration {
@Bean @Bean
OtlpLoggingConnectionDetails customOtlpLoggingConnectionDetails() { OpenTelemetryLoggingConnectionDetails customOtlpLoggingConnectionDetails() {
return (transport) -> "https://otel.example.com/v1/logs"; return (transport) -> "https://otel.example.com/v1/logs";
} }

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.actuate.autoconfigure.opentelemetry; package org.springframework.boot.opentelemetry.autoconfigure;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.actuate.autoconfigure.opentelemetry; package org.springframework.boot.opentelemetry.autoconfigure;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
@ -99,7 +99,7 @@ class OpenTelemetryResourceAttributesTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
void systemGetEnvShouldBeUsedAsDefaultEnvFunction() { void systemGetEnvShouldBeUsedAsDefaultEnvFunction() {
OpenTelemetryResourceAttributes attributes = new OpenTelemetryResourceAttributes(this.environment, null); OpenTelemetryResourceAttributes attributes = new OpenTelemetryResourceAttributes(this.environment, null);
Function<String, String> getEnv = assertThat(attributes).extracting("getEnv") Function<String, String> getEnv = assertThat(attributes).extracting("systemEnvironment")
.asInstanceOf(InstanceOfAssertFactories.type(Function.class)) .asInstanceOf(InstanceOfAssertFactories.type(Function.class))
.actual(); .actual();
System.getenv().forEach((key, value) -> assertThat(getEnv.apply(key)).isEqualTo(value)); System.getenv().forEach((key, value) -> assertThat(getEnv.apply(key)).isEqualTo(value));

View File

@ -0,0 +1,364 @@
/*
* Copyright 2012-present 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<LogRecordData> 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();
}
}
}

View File

@ -44,6 +44,7 @@ dependencies {
dockerTestImplementation("org.testcontainers:junit-jupiter") dockerTestImplementation("org.testcontainers:junit-jupiter")
optional(project(":spring-boot-project:spring-boot-actuator-autoconfigure-all")) 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-tx"))
optional(project(":spring-boot-project:spring-boot-zipkin")) optional(project(":spring-boot-project:spring-boot-zipkin"))
optional("org.springframework:spring-test") optional("org.springframework:spring-test")

View File

@ -22,10 +22,10 @@ import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.beans.factory.annotation.Autowired; 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.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.testcontainers.service.connection.ServiceConnection;
import org.springframework.boot.testsupport.container.TestImage; import org.springframework.boot.testsupport.container.TestImage;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -47,7 +47,7 @@ class GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTes
static final LgtmStackContainer container = TestImage.container(LgtmStackContainer.class); static final LgtmStackContainer container = TestImage.container(LgtmStackContainer.class);
@Autowired @Autowired
private OtlpLoggingConnectionDetails connectionDetails; private OpenTelemetryLoggingConnectionDetails connectionDetails;
@Test @Test
void connectionCanBeMadeToOpenTelemetryContainer() { void connectionCanBeMadeToOpenTelemetryContainer() {
@ -58,7 +58,7 @@ class GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTes
} }
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ImportAutoConfiguration(OtlpLoggingAutoConfiguration.class) @ImportAutoConfiguration(OpenTelemetryLoggingExportAutoConfiguration.class)
static class TestConfiguration { static class TestConfiguration {
} }

View File

@ -22,10 +22,10 @@ import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.beans.factory.annotation.Autowired; 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.actuate.autoconfigure.tracing.otlp.OtlpTracingAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration; 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.testcontainers.service.connection.ServiceConnection;
import org.springframework.boot.testsupport.container.TestImage; import org.springframework.boot.testsupport.container.TestImage;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -48,7 +48,7 @@ class OpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTests {
.withExposedPorts(4317, 4318); .withExposedPorts(4317, 4318);
@Autowired @Autowired
private OtlpLoggingConnectionDetails connectionDetails; private OpenTelemetryLoggingConnectionDetails connectionDetails;
@Test @Test
void connectionCanBeMadeToOpenTelemetryContainer() { void connectionCanBeMadeToOpenTelemetryContainer() {

View File

@ -18,36 +18,36 @@ package org.springframework.boot.testcontainers.service.connection.otlp;
import org.testcontainers.grafana.LgtmStackContainer; import org.testcontainers.grafana.LgtmStackContainer;
import org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingConnectionDetails; import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingConnectionDetails;
import org.springframework.boot.actuate.autoconfigure.logging.otlp.Transport; import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.Transport;
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory;
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
/** /**
* {@link ContainerConnectionDetailsFactory} to create * {@link ContainerConnectionDetailsFactory} to create
* {@link OtlpLoggingConnectionDetails} from a * {@link OpenTelemetryLoggingConnectionDetails} from a
* {@link ServiceConnection @ServiceConnection}-annotated {@link LgtmStackContainer} using * {@link ServiceConnection @ServiceConnection}-annotated {@link LgtmStackContainer} using
* the {@code "grafana/otel-lgtm"} image. * the {@code "grafana/otel-lgtm"} image.
* *
* @author Eddú Meléndez * @author Eddú Meléndez
*/ */
class GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactory class GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactory
extends ContainerConnectionDetailsFactory<LgtmStackContainer, OtlpLoggingConnectionDetails> { extends ContainerConnectionDetailsFactory<LgtmStackContainer, OpenTelemetryLoggingConnectionDetails> {
GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactory() { GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactory() {
super(ANY_CONNECTION_NAME, super(ANY_CONNECTION_NAME,
"org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingAutoConfiguration"); "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportAutoConfiguration");
} }
@Override @Override
protected OtlpLoggingConnectionDetails getContainerConnectionDetails( protected OpenTelemetryLoggingConnectionDetails getContainerConnectionDetails(
ContainerConnectionSource<LgtmStackContainer> source) { ContainerConnectionSource<LgtmStackContainer> source) {
return new OpenTelemetryLoggingContainerConnectionDetails(source); return new OpenTelemetryLoggingContainerConnectionDetails(source);
} }
private static final class OpenTelemetryLoggingContainerConnectionDetails private static final class OpenTelemetryLoggingContainerConnectionDetails
extends ContainerConnectionDetails<LgtmStackContainer> implements OtlpLoggingConnectionDetails { extends ContainerConnectionDetails<LgtmStackContainer> implements OpenTelemetryLoggingConnectionDetails {
private OpenTelemetryLoggingContainerConnectionDetails(ContainerConnectionSource<LgtmStackContainer> source) { private OpenTelemetryLoggingContainerConnectionDetails(ContainerConnectionSource<LgtmStackContainer> source) {
super(source); super(source);

View File

@ -19,15 +19,15 @@ package org.springframework.boot.testcontainers.service.connection.otlp;
import org.testcontainers.containers.Container; import org.testcontainers.containers.Container;
import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.GenericContainer;
import org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingConnectionDetails; import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingConnectionDetails;
import org.springframework.boot.actuate.autoconfigure.logging.otlp.Transport; import org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.Transport;
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory;
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
/** /**
* {@link ContainerConnectionDetailsFactory} to create * {@link ContainerConnectionDetailsFactory} to create
* {@link OtlpLoggingConnectionDetails} from a * {@link OpenTelemetryLoggingConnectionDetails} from a
* {@link ServiceConnection @ServiceConnection}-annotated {@link GenericContainer} using * {@link ServiceConnection @ServiceConnection}-annotated {@link GenericContainer} using
* the {@code "otel/opentelemetry-collector-contrib"} image. * the {@code "otel/opentelemetry-collector-contrib"} image.
* *
@ -35,7 +35,7 @@ import org.springframework.boot.testcontainers.service.connection.ServiceConnect
* @author Moritz Halbritter * @author Moritz Halbritter
*/ */
class OpenTelemetryLoggingContainerConnectionDetailsFactory class OpenTelemetryLoggingContainerConnectionDetailsFactory
extends ContainerConnectionDetailsFactory<Container<?>, OtlpLoggingConnectionDetails> { extends ContainerConnectionDetailsFactory<Container<?>, OpenTelemetryLoggingConnectionDetails> {
private static final int OTLP_GRPC_PORT = 4317; private static final int OTLP_GRPC_PORT = 4317;
@ -43,17 +43,17 @@ class OpenTelemetryLoggingContainerConnectionDetailsFactory
OpenTelemetryLoggingContainerConnectionDetailsFactory() { OpenTelemetryLoggingContainerConnectionDetailsFactory() {
super("otel/opentelemetry-collector-contrib", super("otel/opentelemetry-collector-contrib",
"org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingAutoConfiguration"); "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportAutoConfiguration");
} }
@Override @Override
protected OtlpLoggingConnectionDetails getContainerConnectionDetails( protected OpenTelemetryLoggingConnectionDetails getContainerConnectionDetails(
ContainerConnectionSource<Container<?>> source) { ContainerConnectionSource<Container<?>> source) {
return new OpenTelemetryLoggingContainerConnectionDetails(source); return new OpenTelemetryLoggingContainerConnectionDetails(source);
} }
private static final class OpenTelemetryLoggingContainerConnectionDetails private static final class OpenTelemetryLoggingContainerConnectionDetails
extends ContainerConnectionDetails<Container<?>> implements OtlpLoggingConnectionDetails { extends ContainerConnectionDetails<Container<?>> implements OpenTelemetryLoggingConnectionDetails {
private OpenTelemetryLoggingContainerConnectionDetails(ContainerConnectionSource<Container<?>> source) { private OpenTelemetryLoggingContainerConnectionDetails(ContainerConnectionSource<Container<?>> source) {
super(source); super(source);