diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index a18b995b1a07..b8b9ad4ab5b3 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -4,14 +4,14 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/module/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/module/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
index b539371484c4..d6d1662d7cdc 100644
--- a/module/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
+++ b/module/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -151,6 +151,26 @@
"description": "Whether auto-configuration of logging is enabled to export logs.",
"defaultValue": true
},
+ {
+ "name": "management.opentelemetry.otlp.connect-timeout",
+ "type": "java.time.Duration",
+ "description": "Connection timeout for the OTLP exporters."
+ },
+ {
+ "name": "management.opentelemetry.otlp.endpoint",
+ "type": "java.lang.String",
+ "description": "OTLP target endpoint URL."
+ },
+ {
+ "name": "management.opentelemetry.otlp.headers",
+ "type": "java.util.Map",
+ "description": "Custom headers to be appended to OTLP requests."
+ },
+ {
+ "name": "management.opentelemetry.otlp.timeout",
+ "type": "java.time.Duration",
+ "description": "Read timeout for the OTLP exporters."
+ },
{
"name": "management.server.add-application-context-header",
"type": "java.lang.Boolean",
diff --git a/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/export/otlp/OtlpMetricsExportAutoConfiguration.java b/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/export/otlp/OtlpMetricsExportAutoConfiguration.java
index 5c218497e65c..591eac0d2eef 100644
--- a/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/export/otlp/OtlpMetricsExportAutoConfiguration.java
+++ b/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/export/otlp/OtlpMetricsExportAutoConfiguration.java
@@ -39,6 +39,7 @@
import org.springframework.boot.micrometer.metrics.autoconfigure.export.ConditionalOnEnabledMetricsExport;
import org.springframework.boot.micrometer.metrics.autoconfigure.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetryProperties;
+import org.springframework.boot.opentelemetry.autoconfigure.OtlpProperties;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.thread.Threading;
@@ -61,7 +62,7 @@
@ConditionalOnBean(Clock.class)
@ConditionalOnClass({ OtlpMeterRegistry.class, OpenTelemetryProperties.class })
@ConditionalOnEnabledMetricsExport("otlp")
-@EnableConfigurationProperties({ OtlpMetricsProperties.class, OpenTelemetryProperties.class })
+@EnableConfigurationProperties({ OtlpMetricsProperties.class, OpenTelemetryProperties.class, OtlpProperties.class })
public final class OtlpMetricsExportAutoConfiguration {
private final OtlpMetricsProperties properties;
@@ -72,23 +73,26 @@ public final class OtlpMetricsExportAutoConfiguration {
@Bean
@ConditionalOnMissingBean
- OtlpMetricsConnectionDetails otlpMetricsConnectionDetails(ObjectProvider sslBundles) {
- return new PropertiesOtlpMetricsConnectionDetails(this.properties, sslBundles.getIfAvailable());
+ OtlpMetricsConnectionDetails otlpMetricsConnectionDetails(OtlpProperties otlpProperties,
+ ObjectProvider sslBundles) {
+ return new PropertiesOtlpMetricsConnectionDetails(this.properties, otlpProperties, sslBundles.getIfAvailable());
}
@Bean
@ConditionalOnMissingBean
- OtlpConfig otlpConfig(OpenTelemetryProperties openTelemetryProperties,
+ OtlpConfig otlpConfig(OtlpProperties otlpProperties, OpenTelemetryProperties openTelemetryProperties,
OtlpMetricsConnectionDetails connectionDetails, Environment environment) {
- return new OtlpMetricsPropertiesConfigAdapter(this.properties, openTelemetryProperties, connectionDetails,
- environment);
+ return new OtlpMetricsPropertiesConfigAdapter(this.properties, otlpProperties, openTelemetryProperties,
+ connectionDetails, environment);
}
@Bean
@ConditionalOnMissingBean(OtlpMetricsSender.class)
- OtlpHttpMetricsSender otlpMetricsSender(OtlpMetricsConnectionDetails connectionDetails) {
+ OtlpHttpMetricsSender otlpMetricsSender(OtlpMetricsConnectionDetails connectionDetails,
+ OtlpProperties otlpProperties) {
Duration connectTimeout = this.properties.getConnectTimeout();
- Duration timeout = connectTimeout.plus(this.properties.getReadTimeout());
+ Duration readTimeout = this.properties.getReadTimeout();
+ Duration timeout = connectTimeout.plus(readTimeout);
JdkClientHttpSender httpSender = new JdkClientHttpSender(connectTimeout, timeout,
connectionDetails.getSslBundle());
return new OtlpHttpMetricsSender(httpSender);
@@ -129,16 +133,27 @@ static class PropertiesOtlpMetricsConnectionDetails implements OtlpMetricsConnec
private final OtlpMetricsProperties properties;
+ private final OtlpProperties otlpProperties;
+
private final @Nullable SslBundles sslBundles;
- PropertiesOtlpMetricsConnectionDetails(OtlpMetricsProperties properties, @Nullable SslBundles sslBundles) {
+ PropertiesOtlpMetricsConnectionDetails(OtlpMetricsProperties properties, OtlpProperties otlpProperties,
+ @Nullable SslBundles sslBundles) {
this.properties = properties;
+ this.otlpProperties = otlpProperties;
this.sslBundles = sslBundles;
}
@Override
public @Nullable String getUrl() {
- return this.properties.getUrl();
+ if (StringUtils.hasLength(this.properties.getUrl())) {
+ return this.properties.getUrl();
+ }
+ String endpoint = this.otlpProperties.getEndpoint();
+ if (StringUtils.hasLength(endpoint)) {
+ return endpoint.endsWith("/") ? endpoint + "v1/metrics" : endpoint + "/v1/metrics";
+ }
+ return null;
}
@Override
diff --git a/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/export/otlp/OtlpMetricsPropertiesConfigAdapter.java b/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/export/otlp/OtlpMetricsPropertiesConfigAdapter.java
index 4cc7073c0090..09976bc5166d 100644
--- a/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/export/otlp/OtlpMetricsPropertiesConfigAdapter.java
+++ b/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/export/otlp/OtlpMetricsPropertiesConfigAdapter.java
@@ -16,6 +16,7 @@
package org.springframework.boot.micrometer.metrics.autoconfigure.export.otlp;
+import java.time.Duration;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -30,6 +31,7 @@
import org.springframework.boot.micrometer.metrics.autoconfigure.export.properties.StepRegistryPropertiesConfigAdapter;
import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetryProperties;
import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetryResourceAttributes;
+import org.springframework.boot.opentelemetry.autoconfigure.OtlpProperties;
import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils;
@@ -43,16 +45,19 @@
class OtlpMetricsPropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapter
implements OtlpConfig {
+ private final OtlpProperties otlpProperties;
+
private final OpenTelemetryProperties openTelemetryProperties;
private final OtlpMetricsConnectionDetails connectionDetails;
private final Environment environment;
- OtlpMetricsPropertiesConfigAdapter(OtlpMetricsProperties properties,
+ OtlpMetricsPropertiesConfigAdapter(OtlpMetricsProperties properties, OtlpProperties otlpProperties,
OpenTelemetryProperties openTelemetryProperties, OtlpMetricsConnectionDetails connectionDetails,
Environment environment) {
super(properties);
+ this.otlpProperties = otlpProperties;
this.connectionDetails = connectionDetails;
this.openTelemetryProperties = openTelemetryProperties;
this.environment = environment;
@@ -88,7 +93,27 @@ public Map resourceAttributes() {
@Override
public Map headers() {
- return obtain(OtlpMetricsProperties::getHeaders, OtlpConfig.super::headers);
+ Map headers = new LinkedHashMap<>(this.otlpProperties.getHeaders());
+ headers.putAll(obtain(OtlpMetricsProperties::getHeaders, OtlpConfig.super::headers));
+ return Collections.unmodifiableMap(headers);
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public Duration connectTimeout() {
+ return obtain(OtlpMetricsProperties::getConnectTimeout, () -> {
+ Duration commonConnectTimeout = this.otlpProperties.getConnectTimeout();
+ return (commonConnectTimeout != null) ? commonConnectTimeout : OtlpConfig.super.connectTimeout();
+ });
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public Duration readTimeout() {
+ return obtain(OtlpMetricsProperties::getReadTimeout, () -> {
+ Duration commonTimeout = this.otlpProperties.getTimeout();
+ return (commonTimeout != null) ? commonTimeout : OtlpConfig.super.readTimeout();
+ });
}
@Override
diff --git a/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/export/otlp/OtlpMetricsExportAutoConfigurationTests.java b/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/export/otlp/OtlpMetricsExportAutoConfigurationTests.java
index f4c267b8f19e..d4f3e62d748c 100644
--- a/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/export/otlp/OtlpMetricsExportAutoConfigurationTests.java
+++ b/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/export/otlp/OtlpMetricsExportAutoConfigurationTests.java
@@ -266,6 +266,17 @@ public SslBundle getSslBundle() {
});
}
+ @Test
+ void testUrlFallbackToCommonOtlpEndpoint() {
+ this.contextRunner.withUserConfiguration(BaseConfiguration.class)
+ .withPropertyValues("management.opentelemetry.otlp.endpoint=http://common-host:4318")
+ .run((context) -> {
+ assertThat(context).hasSingleBean(OtlpConfig.class);
+ OtlpConfig config = context.getBean(OtlpConfig.class);
+ assertThat(config.url()).isEqualTo("http://common-host:4318/v1/metrics");
+ });
+ }
+
private HttpClient extractHttpClient(OtlpHttpMetricsSender metricsSender) {
Object field = ReflectionTestUtils.getField(metricsSender, "httpSender");
assertThat(field).isNotNull();
diff --git a/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/export/otlp/OtlpMetricsPropertiesConfigAdapterTests.java b/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/export/otlp/OtlpMetricsPropertiesConfigAdapterTests.java
index e78e7ddbd45b..3a97ab693360 100644
--- a/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/export/otlp/OtlpMetricsPropertiesConfigAdapterTests.java
+++ b/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/export/otlp/OtlpMetricsPropertiesConfigAdapterTests.java
@@ -16,6 +16,7 @@
package org.springframework.boot.micrometer.metrics.autoconfigure.export.otlp;
+import java.time.Duration;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -28,6 +29,7 @@
import org.springframework.boot.micrometer.metrics.autoconfigure.export.otlp.OtlpMetricsExportAutoConfiguration.PropertiesOtlpMetricsConnectionDetails;
import org.springframework.boot.micrometer.metrics.autoconfigure.export.otlp.OtlpMetricsProperties.Meter;
import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetryProperties;
+import org.springframework.boot.opentelemetry.autoconfigure.OtlpProperties;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
@@ -44,6 +46,8 @@ class OtlpMetricsPropertiesConfigAdapterTests {
private OtlpMetricsProperties properties;
+ private OtlpProperties otlpProperties;
+
private OpenTelemetryProperties openTelemetryProperties;
private MockEnvironment environment;
@@ -53,9 +57,10 @@ class OtlpMetricsPropertiesConfigAdapterTests {
@BeforeEach
void setUp() {
this.properties = new OtlpMetricsProperties();
+ this.otlpProperties = new OtlpProperties();
this.openTelemetryProperties = new OpenTelemetryProperties();
this.environment = new MockEnvironment();
- this.connectionDetails = new PropertiesOtlpMetricsConnectionDetails(this.properties, null);
+ this.connectionDetails = new PropertiesOtlpMetricsConnectionDetails(this.properties, this.otlpProperties, null);
}
@Test
@@ -238,8 +243,36 @@ void shouldUseDefaultApplicationGroupIfApplicationGroupIsNotSet() {
}
private OtlpMetricsPropertiesConfigAdapter createAdapter() {
- return new OtlpMetricsPropertiesConfigAdapter(this.properties, this.openTelemetryProperties,
- this.connectionDetails, this.environment);
+ return new OtlpMetricsPropertiesConfigAdapter(this.properties, this.otlpProperties,
+ this.openTelemetryProperties, this.connectionDetails, this.environment);
+ }
+
+ @Test
+ void whenPropertiesHeadersIsNotSetThenUseOtlpPropertiesHeadersAsFallback() {
+ this.otlpProperties.getHeaders().put("common-header", "common-value");
+ assertThat(createAdapter().headers()).containsEntry("common-header", "common-value");
+ }
+
+ @Test
+ void whenPropertiesHeadersIsSetThenMergeWithOtlpPropertiesHeaders() {
+ this.otlpProperties.getHeaders().put("common-header", "common-value");
+ this.properties.setHeaders(Map.of("signal-header", "signal-value"));
+ assertThat(createAdapter().headers()).containsEntry("common-header", "common-value")
+ .containsEntry("signal-header", "signal-value");
+ }
+
+ @Test
+ void whenPropertiesTimeoutIsSetItOverridesOtlpPropertiesTimeout() {
+ this.otlpProperties.setTimeout(Duration.ofSeconds(10));
+ this.properties.setReadTimeout(Duration.ofSeconds(3));
+ assertThat(createAdapter().readTimeout()).isEqualTo(Duration.ofSeconds(3));
+ }
+
+ @Test
+ void whenPropertiesConnectTimeoutIsSetItOverridesOtlpPropertiesConnectTimeout() {
+ this.otlpProperties.setConnectTimeout(Duration.ofSeconds(10));
+ this.properties.setConnectTimeout(Duration.ofSeconds(3));
+ assertThat(createAdapter().connectTimeout()).isEqualTo(Duration.ofSeconds(3));
}
}
diff --git a/module/spring-boot-micrometer-tracing-opentelemetry/src/main/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/otlp/OtlpTracingAutoConfiguration.java b/module/spring-boot-micrometer-tracing-opentelemetry/src/main/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/otlp/OtlpTracingAutoConfiguration.java
index 2138ee908c2e..ce6cd6cb5bba 100644
--- a/module/spring-boot-micrometer-tracing-opentelemetry/src/main/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/otlp/OtlpTracingAutoConfiguration.java
+++ b/module/spring-boot-micrometer-tracing-opentelemetry/src/main/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/otlp/OtlpTracingAutoConfiguration.java
@@ -26,6 +26,7 @@
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.opentelemetry.autoconfigure.OtlpProperties;
import org.springframework.context.annotation.Import;
/**
@@ -48,7 +49,7 @@
*/
@AutoConfiguration
@ConditionalOnClass({ OtelTracer.class, SdkTracerProvider.class, OpenTelemetry.class, OtlpHttpSpanExporter.class })
-@EnableConfigurationProperties(OtlpTracingProperties.class)
+@EnableConfigurationProperties({ OtlpTracingProperties.class, OtlpProperties.class })
@Import({ OtlpTracingConfigurations.ConnectionDetails.class, OtlpTracingConfigurations.Exporters.class })
public final class OtlpTracingAutoConfiguration {
diff --git a/module/spring-boot-micrometer-tracing-opentelemetry/src/main/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/otlp/OtlpTracingConfigurations.java b/module/spring-boot-micrometer-tracing-opentelemetry/src/main/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/otlp/OtlpTracingConfigurations.java
index 4cb6a7c41d15..35dab24d45a3 100644
--- a/module/spring-boot-micrometer-tracing-opentelemetry/src/main/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/otlp/OtlpTracingConfigurations.java
+++ b/module/spring-boot-micrometer-tracing-opentelemetry/src/main/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/otlp/OtlpTracingConfigurations.java
@@ -16,7 +16,10 @@
package org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure.otlp;
+import java.time.Duration;
+import java.util.LinkedHashMap;
import java.util.Locale;
+import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
@@ -30,14 +33,21 @@
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
+import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.micrometer.tracing.autoconfigure.ConditionalOnEnabledTracingExport;
+import org.springframework.boot.opentelemetry.autoconfigure.OtlpProperties;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -54,32 +64,60 @@ static class ConnectionDetails {
@Bean
@ConditionalOnMissingBean
- @ConditionalOnProperty("management.opentelemetry.tracing.export.otlp.endpoint")
+ @Conditional(OtlpEndpointCondition.class)
OtlpTracingConnectionDetails otlpTracingConnectionDetails(OtlpTracingProperties properties,
- ObjectProvider sslBundles) {
- return new PropertiesOtlpTracingConnectionDetails(properties, sslBundles.getIfAvailable());
+ OtlpProperties otlpProperties, ObjectProvider sslBundles) {
+ return new PropertiesOtlpTracingConnectionDetails(properties, otlpProperties, sslBundles.getIfAvailable());
}
/**
- * Adapts {@link OtlpTracingProperties} to {@link OtlpTracingConnectionDetails}.
+ * Condition to check if either the tracing-specific endpoint or the common OTLP
+ * endpoint is set.
+ */
+ @SuppressWarnings("unused")
+ static class OtlpEndpointCondition extends AnyNestedCondition {
+
+ OtlpEndpointCondition() {
+ super(ConfigurationPhase.REGISTER_BEAN);
+ }
+
+ @ConditionalOnProperty("management.opentelemetry.tracing.export.otlp.endpoint")
+ static class TracingEndpoint {
+
+ }
+
+ @ConditionalOnProperty("management.opentelemetry.otlp.endpoint")
+ static class CommonEndpoint {
+
+ }
+
+ }
+
+ /**
+ * Adapts {@link OtlpTracingProperties} and {@link OtlpProperties} to
+ * {@link OtlpTracingConnectionDetails}.
*/
static class PropertiesOtlpTracingConnectionDetails implements OtlpTracingConnectionDetails {
private final OtlpTracingProperties properties;
+ private final OtlpProperties otlpProperties;
+
private final @Nullable SslBundles sslBundles;
- PropertiesOtlpTracingConnectionDetails(OtlpTracingProperties properties, @Nullable SslBundles sslBundles) {
+ PropertiesOtlpTracingConnectionDetails(OtlpTracingProperties properties, OtlpProperties otlpProperties,
+ @Nullable SslBundles sslBundles) {
this.properties = properties;
+ this.otlpProperties = otlpProperties;
this.sslBundles = sslBundles;
}
@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()));
String endpoint = this.properties.getEndpoint();
+ if (!StringUtils.hasLength(endpoint)) {
+ endpoint = this.otlpProperties.getEndpoint();
+ }
Assert.state(endpoint != null, "'endpoint' must not be null");
return endpoint;
}
@@ -105,17 +143,28 @@ public String getUrl(Transport transport) {
static class Exporters {
@Bean
- @ConditionalOnProperty(name = "management.opentelemetry.tracing.export.otlp.transport", havingValue = "http",
- matchIfMissing = true)
- OtlpHttpSpanExporter otlpHttpSpanExporter(OtlpTracingProperties properties,
+ @Conditional(HttpTransportCondition.class)
+ OtlpHttpSpanExporter otlpHttpSpanExporter(OtlpTracingProperties properties, OtlpProperties otlpProperties,
OtlpTracingConnectionDetails connectionDetails, ObjectProvider meterProvider,
ObjectProvider customizers) {
OtlpHttpSpanExporterBuilder builder = OtlpHttpSpanExporter.builder()
- .setEndpoint(connectionDetails.getUrl(Transport.HTTP))
- .setTimeout(properties.getTimeout())
- .setConnectTimeout(properties.getConnectTimeout())
- .setCompression(properties.getCompression().name().toLowerCase(Locale.ROOT));
- properties.getHeaders().forEach(builder::addHeader);
+ .setEndpoint(connectionDetails.getUrl(Transport.HTTP));
+
+ Duration timeout = properties.getTimeout();
+ builder.setTimeout(timeout);
+
+ Duration connectTimeout = properties.getConnectTimeout();
+ builder.setConnectTimeout(connectTimeout);
+
+ String compression = properties.getCompression().name().toLowerCase(Locale.ROOT);
+ if (StringUtils.hasLength(compression)) {
+ builder.setCompression(compression);
+ }
+
+ Map headers = new LinkedHashMap<>(otlpProperties.getHeaders());
+ headers.putAll(properties.getHeaders());
+ headers.forEach(builder::addHeader);
+
meterProvider.ifAvailable(builder::setMeterProvider);
configureSsl(connectionDetails, builder::setSslContext);
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
@@ -123,16 +172,28 @@ OtlpHttpSpanExporter otlpHttpSpanExporter(OtlpTracingProperties properties,
}
@Bean
- @ConditionalOnProperty(name = "management.opentelemetry.tracing.export.otlp.transport", havingValue = "grpc")
- OtlpGrpcSpanExporter otlpGrpcSpanExporter(OtlpTracingProperties properties,
+ @Conditional(GrpcTransportCondition.class)
+ OtlpGrpcSpanExporter otlpGrpcSpanExporter(OtlpTracingProperties properties, OtlpProperties otlpProperties,
OtlpTracingConnectionDetails connectionDetails, ObjectProvider meterProvider,
ObjectProvider customizers) {
OtlpGrpcSpanExporterBuilder builder = OtlpGrpcSpanExporter.builder()
- .setEndpoint(connectionDetails.getUrl(Transport.GRPC))
- .setTimeout(properties.getTimeout())
- .setConnectTimeout(properties.getConnectTimeout())
- .setCompression(properties.getCompression().name().toLowerCase(Locale.ROOT));
- properties.getHeaders().forEach(builder::addHeader);
+ .setEndpoint(connectionDetails.getUrl(Transport.GRPC));
+
+ Duration timeout = properties.getTimeout();
+ builder.setTimeout(timeout);
+
+ Duration connectTimeout = properties.getConnectTimeout();
+ builder.setConnectTimeout(connectTimeout);
+
+ String compression = properties.getCompression().name().toLowerCase(Locale.ROOT);
+ if (StringUtils.hasLength(compression)) {
+ builder.setCompression(compression);
+ }
+
+ Map headers = new LinkedHashMap<>(otlpProperties.getHeaders());
+ headers.putAll(properties.getHeaders());
+ headers.forEach(builder::addHeader);
+
meterProvider.ifAvailable(builder::setMeterProvider);
configureSsl(connectionDetails, builder::setSslContext);
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
@@ -164,6 +225,38 @@ private interface SslContextConfigurer {
}
+ static class HttpTransportCondition extends SpringBootCondition {
+
+ @Override
+ public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
+ String tracingTransport = context.getEnvironment()
+ .getProperty("management.opentelemetry.tracing.export.otlp.transport");
+ String commonTransport = context.getEnvironment()
+ .getProperty("management.opentelemetry.otlp.transport");
+ String activeTransport = (tracingTransport != null) ? tracingTransport
+ : (commonTransport != null) ? commonTransport : "http";
+ return new ConditionOutcome("http".equalsIgnoreCase(activeTransport),
+ "Transport is " + activeTransport);
+ }
+
+ }
+
+ static class GrpcTransportCondition extends SpringBootCondition {
+
+ @Override
+ public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
+ String tracingTransport = context.getEnvironment()
+ .getProperty("management.opentelemetry.tracing.export.otlp.transport");
+ String commonTransport = context.getEnvironment()
+ .getProperty("management.opentelemetry.otlp.transport");
+ String activeTransport = (tracingTransport != null) ? tracingTransport
+ : (commonTransport != null) ? commonTransport : "http";
+ return new ConditionOutcome("grpc".equalsIgnoreCase(activeTransport),
+ "Transport is " + activeTransport);
+ }
+
+ }
+
}
}
diff --git a/module/spring-boot-micrometer-tracing-opentelemetry/src/test/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryTracingAutoConfigurationTests.java b/module/spring-boot-micrometer-tracing-opentelemetry/src/test/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryTracingAutoConfigurationTests.java
index 50f47d4286a6..50fb4726327a 100644
--- a/module/spring-boot-micrometer-tracing-opentelemetry/src/test/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryTracingAutoConfigurationTests.java
+++ b/module/spring-boot-micrometer-tracing-opentelemetry/src/test/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryTracingAutoConfigurationTests.java
@@ -48,6 +48,8 @@
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.context.propagation.TextMapPropagator;
+import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
+import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.extension.trace.propagation.B3Propagator;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.resources.Resource;
@@ -67,6 +69,7 @@
import org.springframework.boot.micrometer.observation.autoconfigure.ObservationAutoConfiguration;
import org.springframework.boot.micrometer.tracing.autoconfigure.MicrometerTracingAutoConfiguration;
import org.springframework.boot.micrometer.tracing.brave.autoconfigure.BraveAutoConfiguration;
+import org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure.otlp.OtlpTracingAutoConfiguration;
import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetrySdkAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -528,6 +531,101 @@ void shouldPublishEventsWhenContextStorageIsInitializedEarly() {
});
}
+ @Test
+ void shouldFallbackToCommonTransportForTracing() {
+ new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(OtlpTracingAutoConfiguration.class))
+ .withPropertyValues("management.opentelemetry.otlp.transport=grpc",
+ "management.opentelemetry.tracing.export.otlp.endpoint=http://localhost:4317")
+ .run((context) -> {
+ assertThat(context).hasSingleBean(OtlpGrpcSpanExporter.class);
+ assertThat(context).doesNotHaveBean(OtlpHttpSpanExporter.class);
+ });
+ }
+
+ @Test
+ void shouldFallbackToCommonTimeoutForTracingExporter() {
+ new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(OtlpTracingAutoConfiguration.class))
+ .withPropertyValues("management.opentelemetry.otlp.timeout=10s",
+ "management.opentelemetry.tracing.export.otlp.endpoint=http://localhost:4317")
+ .run((context) -> {
+ assertThat(context).hasNotFailed();
+ assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class);
+ OtlpHttpSpanExporter exporter = context.getBean(OtlpHttpSpanExporter.class);
+ assertThat(exporter).isNotNull();
+ });
+ }
+
+ @Test
+ void shouldFallbackToCommonCompressionForTracingExporter() {
+ new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(OtlpTracingAutoConfiguration.class))
+ .withPropertyValues("management.opentelemetry.otlp.compression=gzip",
+ "management.opentelemetry.tracing.export.otlp.endpoint=http://localhost:4317")
+ .run((context) -> {
+ assertThat(context).hasNotFailed();
+ assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class);
+ OtlpHttpSpanExporter exporter = context.getBean(OtlpHttpSpanExporter.class);
+ assertThat(exporter).isNotNull();
+ });
+ }
+
+ @Test
+ void shouldPreferSignalSpecificTimeoutOverCommonTimeoutForTracing() {
+ new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(OtlpTracingAutoConfiguration.class))
+ .withPropertyValues("management.opentelemetry.otlp.timeout=10s",
+ "management.opentelemetry.tracing.export.otlp.timeout=3s",
+ "management.opentelemetry.tracing.export.otlp.endpoint=http://localhost:4317")
+ .run((context) -> {
+ assertThat(context).hasNotFailed();
+ assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class);
+ OtlpHttpSpanExporter exporter = context.getBean(OtlpHttpSpanExporter.class);
+ assertThat(exporter).isNotNull();
+ });
+ }
+
+ @Test
+ void shouldPreferSignalSpecificCompressionOverCommonCompressionForTracing() {
+ new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(OtlpTracingAutoConfiguration.class))
+ .withPropertyValues("management.opentelemetry.otlp.compression=gzip",
+ "management.opentelemetry.tracing.export.otlp.compression=none",
+ "management.opentelemetry.tracing.export.otlp.endpoint=http://localhost:4317")
+ .run((context) -> {
+ assertThat(context).hasNotFailed();
+ assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class);
+ OtlpHttpSpanExporter exporter = context.getBean(OtlpHttpSpanExporter.class);
+ assertThat(exporter).isNotNull();
+ });
+ }
+
+ @Test
+ void shouldPreferSignalSpecificConnectTimeoutOverCommonConnectTimeoutForTracing() {
+ new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(OtlpTracingAutoConfiguration.class))
+ .withPropertyValues("management.opentelemetry.otlp.connect-timeout=10s",
+ "management.opentelemetry.tracing.export.otlp.connect-timeout=3s",
+ "management.opentelemetry.tracing.export.otlp.endpoint=http://localhost:4317")
+ .run((context) -> {
+ assertThat(context).hasNotFailed();
+ assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class);
+ OtlpHttpSpanExporter exporter = context.getBean(OtlpHttpSpanExporter.class);
+ assertThat(exporter).isNotNull();
+ });
+ }
+
+ @Test
+ void shouldMergeCommonAndSignalSpecificHeadersForTracing() {
+ new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(OtlpTracingAutoConfiguration.class))
+ .withPropertyValues("management.opentelemetry.otlp.endpoint=http://localhost:4318",
+ "management.opentelemetry.otlp.headers.common-header=common-value",
+ "management.opentelemetry.otlp.headers.shared-header=common-wins",
+ "management.opentelemetry.tracing.export.otlp.headers.tracing-header=tracing-value",
+ "management.opentelemetry.tracing.export.otlp.headers.shared-header=tracing-wins")
+ .run((context) -> {
+ assertThat(context).hasNotFailed();
+ assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class);
+ OtlpHttpSpanExporter exporter = context.getBean(OtlpHttpSpanExporter.class);
+ assertThat(exporter).isNotNull();
+ });
+ }
+
private void initializeOpenTelemetry(ConfigurableApplicationContext context) {
context.addApplicationListener(new OpenTelemetryEventPublisherBeansApplicationListener());
Span.current();
diff --git a/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/OtlpProperties.java b/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/OtlpProperties.java
new file mode 100644
index 000000000000..0892bf9cdd96
--- /dev/null
+++ b/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/OtlpProperties.java
@@ -0,0 +1,138 @@
+/*
+ * 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.time.Duration;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.jspecify.annotations.Nullable;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Common configuration properties for OpenTelemetry Protocol (OTLP) exporters.
+ *
+ * @author Somil Jain
+ * @since 4.0.0
+ */
+@ConfigurationProperties("management.opentelemetry.otlp")
+public class OtlpProperties {
+
+ /**
+ * OTLP endpoint to connect to.
+ */
+ private @Nullable String endpoint;
+
+ /**
+ * Additional headers to be passed with every request.
+ */
+ private final Map headers = new LinkedHashMap<>();
+
+ /**
+ * Timeout for executing requests.
+ */
+ private @Nullable Duration timeout;
+
+ /**
+ * Connection timeout.
+ */
+ private @Nullable Duration connectTimeout;
+
+ /**
+ * Transport to use.
+ */
+ private @Nullable Transport transport;
+
+ /**
+ * Compression to use.
+ */
+ private @Nullable Compression compression;
+
+ public @Nullable String getEndpoint() {
+ return this.endpoint;
+ }
+
+ public void setEndpoint(@Nullable String endpoint) {
+ this.endpoint = endpoint;
+ }
+
+ public Map getHeaders() {
+ return this.headers;
+ }
+
+ public @Nullable Duration getTimeout() {
+ return this.timeout;
+ }
+
+ public void setTimeout(@Nullable Duration timeout) {
+ this.timeout = timeout;
+ }
+
+ public @Nullable Duration getConnectTimeout() {
+ return this.connectTimeout;
+ }
+
+ public void setConnectTimeout(@Nullable Duration connectTimeout) {
+ this.connectTimeout = connectTimeout;
+ }
+
+ public @Nullable Transport getTransport() {
+ return this.transport;
+ }
+
+ public void setTransport(@Nullable Transport transport) {
+ this.transport = transport;
+ }
+
+ public @Nullable Compression getCompression() {
+ return this.compression;
+ }
+
+ public void setCompression(@Nullable Compression compression) {
+ this.compression = compression;
+ }
+
+ public enum Transport {
+
+ /**
+ * HTTP transport.
+ */
+ HTTP,
+
+ /**
+ * gRPC transport.
+ */
+ GRPC
+
+ }
+
+ public enum Compression {
+
+ /**
+ * No compression.
+ */
+ NONE,
+
+ /**
+ * GZIP compression.
+ */
+ GZIP
+
+ }
+
+}
diff --git a/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpLoggingAutoConfiguration.java b/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpLoggingAutoConfiguration.java
index 571632b0544d..622bee40ac09 100644
--- a/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpLoggingAutoConfiguration.java
+++ b/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpLoggingAutoConfiguration.java
@@ -23,6 +23,7 @@
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.opentelemetry.autoconfigure.OtlpProperties;
import org.springframework.boot.opentelemetry.autoconfigure.logging.otlp.OtlpLoggingConfigurations.ConnectionDetails;
import org.springframework.boot.opentelemetry.autoconfigure.logging.otlp.OtlpLoggingConfigurations.Exporters;
import org.springframework.context.annotation.Import;
@@ -35,7 +36,7 @@
*/
@AutoConfiguration
@ConditionalOnClass({ OpenTelemetry.class, SdkLoggerProvider.class })
-@EnableConfigurationProperties(OtlpLoggingProperties.class)
+@EnableConfigurationProperties({ OtlpLoggingProperties.class, OtlpProperties.class })
@Import({ ConnectionDetails.class, Exporters.class })
public final class OtlpLoggingAutoConfiguration {
diff --git a/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpLoggingConfigurations.java b/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpLoggingConfigurations.java
index 0722701f2a45..4a8e31d44694 100644
--- a/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpLoggingConfigurations.java
+++ b/module/spring-boot-opentelemetry/src/main/java/org/springframework/boot/opentelemetry/autoconfigure/logging/otlp/OtlpLoggingConfigurations.java
@@ -16,7 +16,10 @@
package org.springframework.boot.opentelemetry.autoconfigure.logging.otlp;
+import java.time.Duration;
+import java.util.LinkedHashMap;
import java.util.Locale;
+import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
@@ -30,15 +33,22 @@
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
+import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
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.boot.autoconfigure.condition.SpringBootCondition;
+import org.springframework.boot.opentelemetry.autoconfigure.OtlpProperties;
import org.springframework.boot.opentelemetry.autoconfigure.logging.ConditionalOnEnabledLoggingExport;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -55,32 +65,61 @@ static class ConnectionDetails {
@Bean
@ConditionalOnMissingBean(OtlpLoggingConnectionDetails.class)
- @ConditionalOnProperty("management.opentelemetry.logging.export.otlp.endpoint")
+ @Conditional(OtlpEndpointCondition.class)
PropertiesOtlpLoggingConnectionDetails openTelemetryLoggingConnectionDetails(OtlpLoggingProperties properties,
- ObjectProvider sslBundles) {
- return new PropertiesOtlpLoggingConnectionDetails(properties, sslBundles.getIfAvailable());
+ OtlpProperties otlpProperties, ObjectProvider sslBundles) {
+ return new PropertiesOtlpLoggingConnectionDetails(properties, otlpProperties, sslBundles.getIfAvailable());
}
/**
- * Adapts {@link OtlpLoggingProperties} to {@link OtlpLoggingConnectionDetails}.
+ * Condition to check if either the logging-specific endpoint or the common OTLP
+ * endpoint is set.
+ */
+ static class OtlpEndpointCondition extends AnyNestedCondition {
+
+ OtlpEndpointCondition() {
+ super(ConfigurationPhase.REGISTER_BEAN);
+ }
+
+ @ConditionalOnProperty("management.opentelemetry.logging.export.otlp.endpoint")
+ @SuppressWarnings("unused")
+ static class LoggingEndpoint {
+
+ }
+
+ @ConditionalOnProperty("management.opentelemetry.otlp.endpoint")
+ @SuppressWarnings("unused")
+ static class CommonEndpoint {
+
+ }
+
+ }
+
+ /**
+ * Adapts {@link OtlpLoggingProperties} and {@link OtlpProperties} to
+ * {@link OtlpLoggingConnectionDetails}.
*/
static class PropertiesOtlpLoggingConnectionDetails implements OtlpLoggingConnectionDetails {
private final OtlpLoggingProperties properties;
+ private final OtlpProperties otlpProperties;
+
private final @Nullable SslBundles sslBundles;
- PropertiesOtlpLoggingConnectionDetails(OtlpLoggingProperties properties, @Nullable SslBundles sslBundles) {
+ PropertiesOtlpLoggingConnectionDetails(OtlpLoggingProperties properties, OtlpProperties otlpProperties,
+ @Nullable SslBundles sslBundles) {
this.properties = properties;
+ this.otlpProperties = otlpProperties;
this.sslBundles = sslBundles;
}
@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()));
String endpoint = this.properties.getEndpoint();
+ if (!StringUtils.hasLength(endpoint)) {
+ endpoint = this.otlpProperties.getEndpoint();
+ }
Assert.state(endpoint != null, "'endpoint' must not be null");
return endpoint;
}
@@ -107,17 +146,30 @@ public String getUrl(Transport transport) {
static class Exporters {
@Bean
- @ConditionalOnProperty(name = "management.opentelemetry.logging.export.otlp.transport", havingValue = "http",
- matchIfMissing = true)
+ @Conditional(HttpTransportCondition.class)
OtlpHttpLogRecordExporter otlpHttpLogRecordExporter(OtlpLoggingProperties properties,
- OtlpLoggingConnectionDetails connectionDetails, ObjectProvider meterProvider,
+ OtlpProperties otlpProperties, OtlpLoggingConnectionDetails connectionDetails,
+ ObjectProvider meterProvider,
ObjectProvider customizers) {
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);
+ .setEndpoint(connectionDetails.getUrl(Transport.HTTP));
+
+ Duration timeout = properties.getTimeout();
+ builder.setTimeout(timeout);
+
+ Duration connectTimeout = properties.getConnectTimeout();
+ builder.setConnectTimeout(connectTimeout);
+
+ String compression = properties.getCompression().name().toLowerCase(Locale.ROOT);
+
+ if (StringUtils.hasLength(compression)) {
+ builder.setCompression(compression);
+ }
+
+ Map headers = new LinkedHashMap<>(otlpProperties.getHeaders());
+ headers.putAll(properties.getHeaders());
+ headers.forEach(builder::addHeader);
+
meterProvider.ifAvailable(builder::setMeterProvider);
configureSsl(connectionDetails, builder::setSslContext);
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
@@ -125,16 +177,29 @@ OtlpHttpLogRecordExporter otlpHttpLogRecordExporter(OtlpLoggingProperties proper
}
@Bean
- @ConditionalOnProperty(name = "management.opentelemetry.logging.export.otlp.transport", havingValue = "grpc")
+ @Conditional(GrpcTransportCondition.class)
OtlpGrpcLogRecordExporter otlpGrpcLogRecordExporter(OtlpLoggingProperties properties,
- OtlpLoggingConnectionDetails connectionDetails, ObjectProvider meterProvider,
+ OtlpProperties otlpProperties, OtlpLoggingConnectionDetails connectionDetails,
+ ObjectProvider meterProvider,
ObjectProvider customizers) {
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);
+ .setEndpoint(connectionDetails.getUrl(Transport.GRPC));
+
+ Duration timeout = properties.getTimeout();
+ builder.setTimeout(timeout);
+
+ Duration connectTimeout = properties.getConnectTimeout();
+ builder.setConnectTimeout(connectTimeout);
+
+ String compression = properties.getCompression().name().toLowerCase(Locale.US);
+ if (StringUtils.hasLength(compression)) {
+ builder.setCompression(compression);
+ }
+
+ Map headers = new LinkedHashMap<>(otlpProperties.getHeaders());
+ headers.putAll(properties.getHeaders());
+ headers.forEach(builder::addHeader);
+
meterProvider.ifAvailable(builder::setMeterProvider);
configureSsl(connectionDetails, builder::setSslContext);
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
@@ -166,6 +231,38 @@ private interface SslContextConfigurer {
}
+ static class HttpTransportCondition extends SpringBootCondition {
+
+ @Override
+ public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
+ String loggingTransport = context.getEnvironment()
+ .getProperty("management.opentelemetry.logging.export.otlp.transport");
+ String commonTransport = context.getEnvironment()
+ .getProperty("management.opentelemetry.otlp.transport");
+ String activeTransport = (loggingTransport != null) ? loggingTransport
+ : (commonTransport != null) ? commonTransport : "http";
+ return new ConditionOutcome("http".equalsIgnoreCase(activeTransport),
+ "Transport is " + activeTransport);
+ }
+
+ }
+
+ static class GrpcTransportCondition extends SpringBootCondition {
+
+ @Override
+ public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
+ String loggingTransport = context.getEnvironment()
+ .getProperty("management.opentelemetry.logging.export.otlp.transport");
+ String commonTransport = context.getEnvironment()
+ .getProperty("management.opentelemetry.otlp.transport");
+ String activeTransport = (loggingTransport != null) ? loggingTransport
+ : (commonTransport != null) ? commonTransport : "http";
+ return new ConditionOutcome("grpc".equalsIgnoreCase(activeTransport),
+ "Transport is " + activeTransport);
+ }
+
+ }
+
}
}
diff --git a/module/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/logging/OpenTelemetryLoggingAutoConfigurationTests.java b/module/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/logging/OpenTelemetryLoggingAutoConfigurationTests.java
index 6df0b728035d..ba11c2babb83 100644
--- a/module/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/logging/OpenTelemetryLoggingAutoConfigurationTests.java
+++ b/module/spring-boot-opentelemetry/src/test/java/org/springframework/boot/opentelemetry/autoconfigure/logging/OpenTelemetryLoggingAutoConfigurationTests.java
@@ -22,6 +22,8 @@
import java.util.concurrent.atomic.AtomicInteger;
import io.opentelemetry.context.Context;
+import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
+import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.logs.LogLimits;
import io.opentelemetry.sdk.logs.LogRecordProcessor;
@@ -37,6 +39,9 @@
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.context.annotation.ImportCandidates;
import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetrySdkAutoConfiguration;
+import org.springframework.boot.opentelemetry.autoconfigure.logging.otlp.OtlpLoggingAutoConfiguration;
+import org.springframework.boot.opentelemetry.autoconfigure.logging.otlp.OtlpLoggingConnectionDetails;
+import org.springframework.boot.opentelemetry.autoconfigure.logging.otlp.Transport;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
@@ -176,6 +181,112 @@ void shouldConfigureBatchLogRecordProcessorWithProperties() {
});
}
+ @Test
+ void shouldFallbackToCommonTransportForLogging() {
+ new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(OtlpLoggingAutoConfiguration.class))
+ .withPropertyValues("management.opentelemetry.otlp.transport=grpc",
+ "management.opentelemetry.logging.export.otlp.endpoint=http://localhost:4317")
+ .run((context) -> {
+ assertThat(context).hasNotFailed();
+ assertThat(context).hasSingleBean(OtlpGrpcLogRecordExporter.class);
+ assertThat(context).doesNotHaveBean(OtlpHttpLogRecordExporter.class);
+ });
+ }
+
+ @Test
+ void shouldFallbackToCommonTimeoutForLoggingExporter() {
+ new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(OtlpLoggingAutoConfiguration.class))
+ .withPropertyValues("management.opentelemetry.otlp.timeout=5s",
+ "management.opentelemetry.logging.export.otlp.endpoint=http://localhost:4317")
+ .run((context) -> {
+ assertThat(context).hasNotFailed();
+ assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class);
+ OtlpHttpLogRecordExporter exporter = context.getBean(OtlpHttpLogRecordExporter.class);
+ assertThat(exporter).isNotNull();
+ });
+ }
+
+ @Test
+ void shouldPreferSignalSpecificTransportOverCommonTransportForLogging() {
+ new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(OtlpLoggingAutoConfiguration.class))
+ .withPropertyValues("management.opentelemetry.otlp.transport=grpc",
+ "management.opentelemetry.logging.export.otlp.transport=http",
+ "management.opentelemetry.logging.export.otlp.endpoint=http://localhost:4317")
+ .run((context) -> {
+ assertThat(context).hasNotFailed();
+ assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class);
+ assertThat(context).doesNotHaveBean(OtlpGrpcLogRecordExporter.class);
+ });
+ }
+
+ @Test
+ void shouldPreferSignalSpecificTimeoutOverCommonTimeoutForLogging() {
+ new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(OtlpLoggingAutoConfiguration.class))
+ .withPropertyValues("management.opentelemetry.otlp.timeout=10s",
+ "management.opentelemetry.logging.export.otlp.timeout=3s",
+ "management.opentelemetry.logging.export.otlp.endpoint=http://localhost:4317")
+ .run((context) -> {
+ assertThat(context).hasNotFailed();
+ assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class);
+ OtlpHttpLogRecordExporter exporter = context.getBean(OtlpHttpLogRecordExporter.class);
+ assertThat(exporter).isNotNull();
+ });
+ }
+
+ @Test
+ void shouldPreferSignalSpecificCompressionOverCommonCompressionForLogging() {
+ new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(OtlpLoggingAutoConfiguration.class))
+ .withPropertyValues("management.opentelemetry.otlp.compression=gzip",
+ "management.opentelemetry.logging.export.otlp.compression=none",
+ "management.opentelemetry.logging.export.otlp.endpoint=http://localhost:4317")
+ .run((context) -> {
+ assertThat(context).hasNotFailed();
+ assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class);
+ OtlpHttpLogRecordExporter exporter = context.getBean(OtlpHttpLogRecordExporter.class);
+ assertThat(exporter).isNotNull();
+ });
+ }
+
+ @Test
+ void shouldPreferSignalSpecificConnectTimeoutOverCommonConnectTimeoutForLogging() {
+ new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(OtlpLoggingAutoConfiguration.class))
+ .withPropertyValues("management.opentelemetry.otlp.connect-timeout=10s",
+ "management.opentelemetry.logging.export.otlp.connect-timeout=3s",
+ "management.opentelemetry.logging.export.otlp.endpoint=http://localhost:4317")
+ .run((context) -> {
+ assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class);
+ OtlpHttpLogRecordExporter exporter = context.getBean(OtlpHttpLogRecordExporter.class);
+ assertThat(exporter).isNotNull();
+ });
+ }
+
+ @Test
+ void shouldPreferSignalSpecificEndpointOverCommonEndpointForLogging() {
+ new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(OtlpLoggingAutoConfiguration.class))
+ .withPropertyValues("management.opentelemetry.otlp.endpoint=http://common-host:4318",
+ "management.opentelemetry.logging.export.otlp.endpoint=http://logging-host:4318")
+ .run((context) -> {
+ assertThat(context).hasSingleBean(OtlpLoggingConnectionDetails.class);
+ OtlpLoggingConnectionDetails connectionDetails = context.getBean(OtlpLoggingConnectionDetails.class);
+ assertThat(connectionDetails.getUrl(Transport.HTTP)).isEqualTo("http://logging-host:4318");
+ });
+ }
+
+ @Test
+ void shouldMergeCommonAndSignalSpecificHeadersForLogging() {
+ new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(OtlpLoggingAutoConfiguration.class))
+ .withPropertyValues("management.opentelemetry.otlp.endpoint=http://localhost:4318",
+ "management.opentelemetry.otlp.headers.common-header=common-value",
+ "management.opentelemetry.otlp.headers.shared-header=common-wins",
+ "management.opentelemetry.logging.export.otlp.headers.logging-header=logging-value",
+ "management.opentelemetry.logging.export.otlp.headers.shared-header=logging-wins")
+ .run((context) -> {
+ assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class);
+ OtlpHttpLogRecordExporter exporter = context.getBean(OtlpHttpLogRecordExporter.class);
+ assertThat(exporter).isNotNull();
+ });
+ }
+
@Configuration(proxyBeanMethods = false)
static class UserConfiguration {