Split OpenTelemetry auto-configuration

The OpenTelemetry bean is now configured in the
OpenTelemetryAutoConfiguration. This method also applies
SdkLoggerProvider and SdkMeterProvider.

Additionally, the OpenTelemetry Resource is now a bean. Resource
attributes can now be configured through properties

The resourceAttributes in OtlpProperties have been deprecated in favor
of the new one in OpenTelemetryProperties.

Closes gh-36544
Closes gh-36545
This commit is contained in:
Moritz Halbritter 2023-07-25 10:46:54 +02:00
parent 9cb5763794
commit b0615dd311
14 changed files with 417 additions and 76 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2023 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.
@ -24,6 +24,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegi
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.ConditionalOnEnabledMetricsExport;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -36,6 +37,7 @@ import org.springframework.context.annotation.Bean;
* {@link EnableAutoConfiguration Auto-configuration} for exporting metrics to OTLP.
*
* @author Eddú Meléndez
* @author Moritz Halbritter
* @since 3.0.0
*/
@AutoConfiguration(
@ -44,19 +46,23 @@ import org.springframework.context.annotation.Bean;
@ConditionalOnBean(Clock.class)
@ConditionalOnClass(OtlpMeterRegistry.class)
@ConditionalOnEnabledMetricsExport("otlp")
@EnableConfigurationProperties(OtlpProperties.class)
@EnableConfigurationProperties({ OtlpProperties.class, OpenTelemetryProperties.class })
public class OtlpMetricsExportAutoConfiguration {
private final OtlpProperties properties;
public OtlpMetricsExportAutoConfiguration(OtlpProperties properties) {
private final OpenTelemetryProperties openTelemetryProperties;
public OtlpMetricsExportAutoConfiguration(OtlpProperties properties,
OpenTelemetryProperties openTelemetryProperties) {
this.properties = properties;
this.openTelemetryProperties = openTelemetryProperties;
}
@Bean
@ConditionalOnMissingBean
public OtlpConfig otlpConfig() {
return new OtlpPropertiesConfigAdapter(this.properties);
return new OtlpPropertiesConfigAdapter(this.properties, this.openTelemetryProperties);
}
@Bean

View File

@ -23,6 +23,7 @@ import io.micrometer.registry.otlp.AggregationTemporality;
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
/**
* {@link ConfigurationProperties @ConfigurationProperties} for configuring OTLP metrics
@ -77,6 +78,7 @@ public class OtlpProperties extends StepRegistryProperties {
this.aggregationTemporality = aggregationTemporality;
}
@DeprecatedConfigurationProperty(replacement = "management.opentelemetry.resource-attributes")
public Map<String, String> getResourceAttributes() {
return this.resourceAttributes;
}

View File

@ -23,17 +23,23 @@ import io.micrometer.registry.otlp.AggregationTemporality;
import io.micrometer.registry.otlp.OtlpConfig;
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryPropertiesConfigAdapter;
import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryProperties;
import org.springframework.util.CollectionUtils;
/**
* Adapter to convert {@link OtlpProperties} to an {@link OtlpConfig}.
*
* @author Eddú Meléndez
* @author Jonatan Ivanov
* @author Moritz Halbritter
*/
class OtlpPropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapter<OtlpProperties> implements OtlpConfig {
OtlpPropertiesConfigAdapter(OtlpProperties properties) {
private final OpenTelemetryProperties openTelemetryProperties;
OtlpPropertiesConfigAdapter(OtlpProperties properties, OpenTelemetryProperties openTelemetryProperties) {
super(properties);
this.openTelemetryProperties = openTelemetryProperties;
}
@Override
@ -53,6 +59,9 @@ class OtlpPropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapter<Ot
@Override
public Map<String, String> resourceAttributes() {
if (!CollectionUtils.isEmpty(this.openTelemetryProperties.getResourceAttributes())) {
return this.openTelemetryProperties.getResourceAttributes();
}
return get(OtlpProperties::getResourceAttributes, OtlpConfig.super::resourceAttributes);
}

View File

@ -0,0 +1,77 @@
/*
* Copyright 2012-2023 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.Attributes;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.OpenTelemetrySdkBuilder;
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
/**
* {@link EnableAutoConfiguration Auto-configuration} for OpenTelemetry.
*
* @author Moritz Halbritter
* @since 3.2.0
*/
@AutoConfiguration
@ConditionalOnClass(OpenTelemetrySdk.class)
@EnableConfigurationProperties(OpenTelemetryProperties.class)
public class OpenTelemetryAutoConfiguration {
/**
* Default value for application name if {@code spring.application.name} is not set.
*/
private static final String DEFAULT_APPLICATION_NAME = "application";
@Bean
@ConditionalOnMissingBean(OpenTelemetry.class)
OpenTelemetrySdk openTelemetry(ObjectProvider<SdkTracerProvider> tracerProvider,
ObjectProvider<ContextPropagators> propagators, ObjectProvider<SdkLoggerProvider> loggerProvider,
ObjectProvider<SdkMeterProvider> meterProvider) {
OpenTelemetrySdkBuilder builder = OpenTelemetrySdk.builder();
tracerProvider.ifAvailable(builder::setTracerProvider);
propagators.ifAvailable(builder::setPropagators);
loggerProvider.ifAvailable(builder::setLoggerProvider);
meterProvider.ifAvailable(builder::setMeterProvider);
return builder.build();
}
@Bean
@ConditionalOnMissingBean
Resource openTelemetryResource(Environment environment, OpenTelemetryProperties properties) {
String applicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME);
return Resource.getDefault()
.merge(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName)))
.merge(properties.toResource());
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2012-2023 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 java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.resources.ResourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for OpenTelemetry.
*
* @author Moritz Halbritter
* @since 3.2.0
*/
@ConfigurationProperties(prefix = "management.opentelemetry")
public class OpenTelemetryProperties {
/**
* Resource attributes.
*/
private Map<String, String> resourceAttributes = new HashMap<>();
public Map<String, String> getResourceAttributes() {
return this.resourceAttributes;
}
public void setResourceAttributes(Map<String, String> resourceAttributes) {
this.resourceAttributes = resourceAttributes;
}
Resource toResource() {
ResourceBuilder builder = Resource.builder();
for (Entry<String, String> entry : this.resourceAttributes.entrySet()) {
builder.put(entry.getKey(), entry.getValue());
}
return builder.build();
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright 2012-2023 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.
*/
/**
* Auto-configuration for OpenTelemetry.
*/
package org.springframework.boot.actuate.autoconfigure.opentelemetry;

View File

@ -36,13 +36,11 @@ import io.micrometer.tracing.otel.bridge.Slf4JBaggageEventListener;
import io.micrometer.tracing.otel.bridge.Slf4JEventListener;
import io.micrometer.tracing.otel.propagation.BaggageTextMapPropagator;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.ContextStorage;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
@ -51,7 +49,6 @@ import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.SpringBootVersion;
@ -63,26 +60,20 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
* {@link EnableAutoConfiguration Auto-configuration} for OpenTelemetry.
* {@link EnableAutoConfiguration Auto-configuration} for OpenTelemetry tracing.
*
* @author Moritz Halbritter
* @author Marcin Grzejszczak
* @author Yanming Zhou
* @since 3.0.0
*/
@AutoConfiguration(before = MicrometerTracingAutoConfiguration.class)
@AutoConfiguration(value = "openTelemetryTracingAutoConfiguration", before = MicrometerTracingAutoConfiguration.class)
@ConditionalOnClass({ OtelTracer.class, SdkTracerProvider.class, OpenTelemetry.class })
@EnableConfigurationProperties(TracingProperties.class)
public class OpenTelemetryAutoConfiguration {
/**
* Default value for application name if {@code spring.application.name} is not set.
*/
private static final String DEFAULT_APPLICATION_NAME = "application";
private final TracingProperties tracingProperties;
OpenTelemetryAutoConfiguration(TracingProperties tracingProperties) {
@ -91,22 +82,9 @@ public class OpenTelemetryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
OpenTelemetry openTelemetry(SdkTracerProvider sdkTracerProvider, ContextPropagators contextPropagators) {
return OpenTelemetrySdk.builder()
.setTracerProvider(sdkTracerProvider)
.setPropagators(contextPropagators)
.build();
}
@Bean
@ConditionalOnMissingBean
SdkTracerProvider otelSdkTracerProvider(Environment environment, SpanProcessors spanProcessors, Sampler sampler,
SdkTracerProvider otelSdkTracerProvider(Resource resource, SpanProcessors spanProcessors, Sampler sampler,
ObjectProvider<SdkTracerProviderBuilderCustomizer> customizers) {
String applicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME);
Resource springResource = Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName));
SdkTracerProviderBuilder builder = SdkTracerProvider.builder()
.setSampler(sampler)
.setResource(Resource.getDefault().merge(springResource));
SdkTracerProviderBuilder builder = SdkTracerProvider.builder().setSampler(sampler).setResource(resource);
spanProcessors.forEach(builder::addSpanProcessor);
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();

View File

@ -88,6 +88,7 @@ org.springframework.boot.actuate.autoconfigure.data.mongo.MongoReactiveHealthCon
org.springframework.boot.actuate.autoconfigure.neo4j.Neo4jHealthContributorAutoConfiguration
org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration
org.springframework.boot.actuate.autoconfigure.observation.web.servlet.WebMvcObservationAutoConfiguration
org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration
org.springframework.boot.actuate.autoconfigure.quartz.QuartzEndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.r2dbc.ConnectionFactoryHealthContributorAutoConfiguration
org.springframework.boot.actuate.autoconfigure.r2dbc.R2dbcObservationAutoConfiguration

View File

@ -16,69 +16,93 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import io.micrometer.registry.otlp.AggregationTemporality;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryProperties;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
/**
* Tests for {@link OtlpPropertiesConfigAdapter}.
*
* @author Eddú Meléndez
* @author Moritz Halbritter
*/
class OtlpPropertiesConfigAdapterTests {
private OtlpProperties properties;
private OpenTelemetryProperties openTelemetryProperties;
@BeforeEach
void setUp() {
this.properties = new OtlpProperties();
this.openTelemetryProperties = new OpenTelemetryProperties();
}
@Test
void whenPropertiesUrlIsSetAdapterUrlReturnsIt() {
OtlpProperties properties = new OtlpProperties();
properties.setUrl("http://another-url:4318/v1/metrics");
assertThat(new OtlpPropertiesConfigAdapter(properties).url()).isEqualTo("http://another-url:4318/v1/metrics");
this.properties.setUrl("http://another-url:4318/v1/metrics");
assertThat(createAdapter().url()).isEqualTo("http://another-url:4318/v1/metrics");
}
@Test
void whenPropertiesAggregationTemporalityIsNotSetAdapterAggregationTemporalityReturnsCumulative() {
OtlpProperties properties = new OtlpProperties();
assertThat(new OtlpPropertiesConfigAdapter(properties).aggregationTemporality())
.isSameAs(AggregationTemporality.CUMULATIVE);
assertThat(createAdapter().aggregationTemporality()).isSameAs(AggregationTemporality.CUMULATIVE);
}
@Test
void whenPropertiesAggregationTemporalityIsSetAdapterAggregationTemporalityReturnsIt() {
OtlpProperties properties = new OtlpProperties();
properties.setAggregationTemporality(AggregationTemporality.DELTA);
assertThat(new OtlpPropertiesConfigAdapter(properties).aggregationTemporality())
.isSameAs(AggregationTemporality.DELTA);
this.properties.setAggregationTemporality(AggregationTemporality.DELTA);
assertThat(createAdapter().aggregationTemporality()).isSameAs(AggregationTemporality.DELTA);
}
@Test
void whenPropertiesResourceAttributesIsSetAdapterResourceAttributesReturnsIt() {
OtlpProperties properties = new OtlpProperties();
properties.setResourceAttributes(Map.of("service.name", "boot-service"));
assertThat(new OtlpPropertiesConfigAdapter(properties).resourceAttributes()).containsEntry("service.name",
"boot-service");
this.properties.setResourceAttributes(Map.of("service.name", "boot-service"));
assertThat(createAdapter().resourceAttributes()).containsEntry("service.name", "boot-service");
}
@Test
void whenPropertiesHeadersIsSetAdapterHeadersReturnsIt() {
OtlpProperties properties = new OtlpProperties();
properties.setHeaders(Map.of("header", "value"));
assertThat(new OtlpPropertiesConfigAdapter(properties).headers()).containsEntry("header", "value");
this.properties.setHeaders(Map.of("header", "value"));
assertThat(createAdapter().headers()).containsEntry("header", "value");
}
@Test
void whenPropertiesBaseTimeUnitIsNotSetAdapterBaseTimeUnitReturnsMillis() {
OtlpProperties properties = new OtlpProperties();
assertThat(new OtlpPropertiesConfigAdapter(properties).baseTimeUnit()).isSameAs(TimeUnit.MILLISECONDS);
assertThat(createAdapter().baseTimeUnit()).isSameAs(TimeUnit.MILLISECONDS);
}
@Test
void whenPropertiesBaseTimeUnitIsSetAdapterBaseTimeUnitReturnsIt() {
OtlpProperties properties = new OtlpProperties();
properties.setBaseTimeUnit(TimeUnit.SECONDS);
assertThat(new OtlpPropertiesConfigAdapter(properties).baseTimeUnit()).isSameAs(TimeUnit.SECONDS);
this.properties.setBaseTimeUnit(TimeUnit.SECONDS);
assertThat(createAdapter().baseTimeUnit()).isSameAs(TimeUnit.SECONDS);
}
@Test
void openTelemetryPropertiesShouldOverrideOtlpPropertiesIfNotEmpty() {
this.properties.setResourceAttributes(Map.of("a", "alpha"));
this.openTelemetryProperties.setResourceAttributes(Map.of("b", "beta"));
assertThat(createAdapter().resourceAttributes()).containsExactly(entry("b", "beta"));
}
@Test
void openTelemetryPropertiesShouldNotOverrideOtlpPropertiesIfEmpty() {
this.properties.setResourceAttributes(Map.of("a", "alpha"));
this.openTelemetryProperties.setResourceAttributes(Collections.emptyMap());
assertThat(createAdapter().resourceAttributes()).containsExactly(entry("a", "alpha"));
}
private OtlpPropertiesConfigAdapter createAdapter() {
return new OtlpPropertiesConfigAdapter(this.properties, this.openTelemetryProperties);
}
}

View File

@ -0,0 +1,157 @@
/*
* Copyright 2012-2023 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 io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
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(ResourceAttributes.SERVICE_NAME, "my-application"));
});
}
@Test
void shouldFallbackToDefaultApplicationNameIfSpringApplicationNameIsNotSet() {
this.runner.run((context) -> {
Resource resource = context.getBean(Resource.class);
assertThat(resource.getAttributes().asMap())
.contains(entry(ResourceAttributes.SERVICE_NAME, "application"));
});
}
@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)
private static class UserConfiguration {
@Bean
OpenTelemetry customOpenTelemetry() {
return mock(OpenTelemetry.class);
}
@Bean
Resource customResource() {
return Resource.getDefault();
}
}
}

View File

@ -29,6 +29,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.slf4j.MDC;
import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.ApplicationContext;
@ -151,8 +152,9 @@ class BaggagePropagationIntegrationTests {
OTEL_DEFAULT {
@Override
public ApplicationContextRunner get() {
return new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class))
return new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(
OpenTelemetryAutoConfiguration.class,
org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration.class))
.withPropertyValues("management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp",
"management.tracing.baggage.correlation.fields=country-code,bp");
}
@ -172,8 +174,9 @@ class BaggagePropagationIntegrationTests {
OTEL_W3C {
@Override
public ApplicationContextRunner get() {
return new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class))
return new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(
OpenTelemetryAutoConfiguration.class,
org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration.class))
.withPropertyValues("management.tracing.propagation.type=W3C",
"management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp",
"management.tracing.baggage.correlation.fields=country-code,bp");
@ -205,8 +208,9 @@ class BaggagePropagationIntegrationTests {
OTEL_B3 {
@Override
public ApplicationContextRunner get() {
return new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class))
return new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(
OpenTelemetryAutoConfiguration.class,
org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration.class))
.withPropertyValues("management.tracing.propagation.type=B3",
"management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp",
"management.tracing.baggage.correlation.fields=country-code,bp");
@ -216,8 +220,9 @@ class BaggagePropagationIntegrationTests {
OTEL_B3_MULTI {
@Override
public ApplicationContextRunner get() {
return new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class))
return new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(
OpenTelemetryAutoConfiguration.class,
org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration.class))
.withPropertyValues("management.tracing.propagation.type=B3_MULTI",
"management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp",
"management.tracing.baggage.correlation.fields=country-code,bp");

View File

@ -35,7 +35,6 @@ import io.micrometer.tracing.otel.bridge.OtelTracer.EventPublisher;
import io.micrometer.tracing.otel.bridge.Slf4JBaggageEventListener;
import io.micrometer.tracing.otel.bridge.Slf4JEventListener;
import io.micrometer.tracing.otel.propagation.BaggageTextMapPropagator;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.MeterProvider;
@ -83,7 +82,9 @@ import static org.mockito.Mockito.mock;
class OpenTelemetryAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class));
.withConfiguration(AutoConfigurations.of(
org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration.class,
OpenTelemetryAutoConfiguration.class));
@Test
void shouldSupplyBeans() {
@ -91,7 +92,6 @@ class OpenTelemetryAutoConfigurationTests {
assertThat(context).hasSingleBean(OtelTracer.class);
assertThat(context).hasSingleBean(EventPublisher.class);
assertThat(context).hasSingleBean(OtelCurrentTraceContext.class);
assertThat(context).hasSingleBean(OpenTelemetry.class);
assertThat(context).hasSingleBean(SdkTracerProvider.class);
assertThat(context).hasSingleBean(ContextPropagators.class);
assertThat(context).hasSingleBean(Sampler.class);
@ -123,7 +123,6 @@ class OpenTelemetryAutoConfigurationTests {
assertThat(context).doesNotHaveBean(OtelTracer.class);
assertThat(context).doesNotHaveBean(EventPublisher.class);
assertThat(context).doesNotHaveBean(OtelCurrentTraceContext.class);
assertThat(context).doesNotHaveBean(OpenTelemetry.class);
assertThat(context).doesNotHaveBean(SdkTracerProvider.class);
assertThat(context).doesNotHaveBean(ContextPropagators.class);
assertThat(context).doesNotHaveBean(Sampler.class);
@ -148,8 +147,6 @@ class OpenTelemetryAutoConfigurationTests {
assertThat(context).hasSingleBean(EventPublisher.class);
assertThat(context).hasBean("customOtelCurrentTraceContext");
assertThat(context).hasSingleBean(OtelCurrentTraceContext.class);
assertThat(context).hasBean("customOpenTelemetry");
assertThat(context).hasSingleBean(OpenTelemetry.class);
assertThat(context).hasBean("customSdkTracerProvider");
assertThat(context).hasSingleBean(SdkTracerProvider.class);
assertThat(context).hasBean("customContextPropagators");
@ -369,11 +366,6 @@ class OpenTelemetryAutoConfigurationTests {
return mock(OtelCurrentTraceContext.class);
}
@Bean
OpenTelemetry customOpenTelemetry() {
return mock(OpenTelemetry.class);
}
@Bean
SdkTracerProvider customSdkTracerProvider() {
return SdkTracerProvider.builder().build();

View File

@ -34,8 +34,8 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@ -50,9 +50,10 @@ class OtlpAutoConfigurationIntegrationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withPropertyValues("management.tracing.sampling.probability=1.0")
.withConfiguration(
AutoConfigurations.of(ObservationAutoConfiguration.class, MicrometerTracingAutoConfiguration.class,
OpenTelemetryAutoConfiguration.class, OtlpAutoConfiguration.class));
.withConfiguration(AutoConfigurations.of(ObservationAutoConfiguration.class,
MicrometerTracingAutoConfiguration.class, OpenTelemetryAutoConfiguration.class,
org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration.class,
OtlpAutoConfiguration.class));
private final MockWebServer mockWebServer = new MockWebServer();

View File

@ -67,4 +67,15 @@ include::code:MyObservationPredicate[]
The preceding example will prevent all observations whose name contains "denied".
[[actuator.observability.opentelemetry]]
=== OpenTelemetry Support
Spring Boot's actuator module includes basic support for https://opentelemetry.io/[OpenTelemetry].
It provides a bean of type `OpenTelemetry`, and if there are beans of type `SdkTracerProvider`, `ContextPropagators`, `SdkLoggerProvider` or `SdkMeterProvider` in the application context, they automatically get registered.
Additionally, it provides a `Resource` bean.
The attributes of the `Resource` can be configured via the configprop:management.opentelemetry.resource-attributes[] configuration property.
NOTE: Spring Boot does not provide auto-configuration for OpenTelemetry metrics or logging.
OpenTelemetry tracing is only auto-configured when used together with <<actuator#actuator.micrometer-tracing, Micrometer Tracing>>.
The next sections will provide more details about logging, metrics and traces.