mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-10-08 02:09:06 +00:00
Merge pull request #50461 from somiljain2006
Closes gh-50461 * pr/50461: Polish "Align OTLP signal-specific properties with common fallback configuration" Align OTLP signal-specific properties with common fallback configuration
This commit is contained in:
+21
-8
@@ -39,6 +39,7 @@ import org.springframework.boot.micrometer.metrics.autoconfigure.MetricsAutoConf
|
||||
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 @@ import org.springframework.util.StringUtils;
|
||||
@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,16 +73,17 @@ public final class OtlpMetricsExportAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
OtlpMetricsConnectionDetails otlpMetricsConnectionDetails(ObjectProvider<SslBundles> sslBundles) {
|
||||
return new PropertiesOtlpMetricsConnectionDetails(this.properties, sslBundles.getIfAvailable());
|
||||
OtlpMetricsConnectionDetails otlpMetricsConnectionDetails(OtlpProperties otlpProperties,
|
||||
ObjectProvider<SslBundles> 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
|
||||
@@ -129,16 +131,27 @@ public final class OtlpMetricsExportAutoConfiguration {
|
||||
|
||||
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
|
||||
|
||||
+3
-3
@@ -53,7 +53,7 @@ public class OtlpMetricsProperties extends StepRegistryProperties {
|
||||
/**
|
||||
* Compression mode to use when exporting metrics.
|
||||
*/
|
||||
private CompressionMode compressionMode = CompressionMode.NONE;
|
||||
private @Nullable CompressionMode compressionMode;
|
||||
|
||||
/**
|
||||
* Headers for the exported metrics.
|
||||
@@ -109,11 +109,11 @@ public class OtlpMetricsProperties extends StepRegistryProperties {
|
||||
this.aggregationTemporality = aggregationTemporality;
|
||||
}
|
||||
|
||||
public CompressionMode getCompressionMode() {
|
||||
public @Nullable CompressionMode getCompressionMode() {
|
||||
return this.compressionMode;
|
||||
}
|
||||
|
||||
public void setCompressionMode(CompressionMode compressionMode) {
|
||||
public void setCompressionMode(@Nullable CompressionMode compressionMode) {
|
||||
this.compressionMode = compressionMode;
|
||||
}
|
||||
|
||||
|
||||
+20
-3
@@ -30,6 +30,7 @@ import org.springframework.boot.micrometer.metrics.autoconfigure.export.otlp.Otl
|
||||
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 +44,19 @@ import org.springframework.util.CollectionUtils;
|
||||
class OtlpMetricsPropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapter<OtlpMetricsProperties>
|
||||
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;
|
||||
@@ -75,7 +79,18 @@ class OtlpMetricsPropertiesConfigAdapter extends StepRegistryPropertiesConfigAda
|
||||
|
||||
@Override
|
||||
public CompressionMode compressionMode() {
|
||||
return obtain(OtlpMetricsProperties::getCompressionMode, OtlpConfig.super::compressionMode);
|
||||
CompressionMode compressionMode = this.properties.getCompressionMode();
|
||||
if (compressionMode != null) {
|
||||
return compressionMode;
|
||||
}
|
||||
OtlpProperties.Compression commonCompression = this.otlpProperties.getCompression();
|
||||
if (commonCompression != null) {
|
||||
return switch (commonCompression) {
|
||||
case GZIP -> CompressionMode.GZIP;
|
||||
case NONE -> CompressionMode.NONE;
|
||||
};
|
||||
}
|
||||
return OtlpConfig.super.compressionMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,7 +103,9 @@ class OtlpMetricsPropertiesConfigAdapter extends StepRegistryPropertiesConfigAda
|
||||
|
||||
@Override
|
||||
public Map<String, String> headers() {
|
||||
return obtain(OtlpMetricsProperties::getHeaders, OtlpConfig.super::headers);
|
||||
Map<String, String> headers = new LinkedHashMap<>(this.otlpProperties.getHeaders());
|
||||
headers.putAll(obtain(OtlpMetricsProperties::getHeaders, OtlpConfig.super::headers));
|
||||
return Collections.unmodifiableMap(headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+11
@@ -265,6 +265,17 @@ class OtlpMetricsExportAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
+53
-3
@@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test;
|
||||
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 +45,8 @@ class OtlpMetricsPropertiesConfigAdapterTests {
|
||||
|
||||
private OtlpMetricsProperties properties;
|
||||
|
||||
private OtlpProperties otlpProperties;
|
||||
|
||||
private OpenTelemetryProperties openTelemetryProperties;
|
||||
|
||||
private MockEnvironment environment;
|
||||
@@ -53,9 +56,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 +242,54 @@ class OtlpMetricsPropertiesConfigAdapterTests {
|
||||
}
|
||||
|
||||
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 whenPropertiesUrlIsNotSetThenUseOtlpPropertiesEndpointAsFallbackWithAppendix() {
|
||||
this.otlpProperties.setEndpoint("http://common-endpoint:4318");
|
||||
assertThat(createAdapter().url()).isEqualTo("http://common-endpoint:4318/v1/metrics");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenPropertiesUrlIsNotSetThenUseOtlpPropertiesEndpointAsFallbackWithAppendixAndTrailingSlash() {
|
||||
this.otlpProperties.setEndpoint("http://common-endpoint:4318/");
|
||||
assertThat(createAdapter().url()).isEqualTo("http://common-endpoint:4318/v1/metrics");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenPropertiesUrlIsSetThenItOverridesOtlpPropertiesEndpoint() {
|
||||
this.otlpProperties.setEndpoint("http://common-endpoint:4318");
|
||||
this.properties.setUrl("http://signal-endpoint:4318/custom/metrics");
|
||||
assertThat(createAdapter().url()).isEqualTo("http://signal-endpoint:4318/custom/metrics");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenPropertiesCompressionModeIsNotSetThenUseOtlpPropertiesCompressionAsFallback() {
|
||||
this.otlpProperties.setCompression(OtlpProperties.Compression.GZIP);
|
||||
assertThat(createAdapter().compressionMode()).isSameAs(CompressionMode.GZIP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenPropertiesCompressionModeIsSetThenItOverridesOtlpPropertiesCompression() {
|
||||
this.otlpProperties.setCompression(OtlpProperties.Compression.GZIP);
|
||||
this.properties.setCompressionMode(CompressionMode.NONE);
|
||||
assertThat(createAdapter().compressionMode()).isSameAs(CompressionMode.NONE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ class OtlpMetricsPropertiesTests extends StepRegistryPropertiesTests {
|
||||
OtlpConfig config = OtlpConfig.DEFAULT;
|
||||
assertStepRegistryDefaultValues(properties, config);
|
||||
assertThat(properties.getAggregationTemporality()).isSameAs(config.aggregationTemporality());
|
||||
assertThat(properties.getCompressionMode()).isSameAs(config.compressionMode());
|
||||
assertThat(properties.getCompressionMode()).isNull();
|
||||
assertThat(properties.getHistogramFlavor()).isSameAs(config.histogramFlavor());
|
||||
assertThat(properties.getMaxScale()).isEqualTo(config.maxScale());
|
||||
assertThat(properties.getMaxBucketCount()).isEqualTo(config.maxBucketCount());
|
||||
|
||||
+2
-1
@@ -26,6 +26,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
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 @@ import org.springframework.context.annotation.Import;
|
||||
*/
|
||||
@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 {
|
||||
|
||||
|
||||
+69
-11
@@ -16,7 +16,9 @@
|
||||
|
||||
package org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure.otlp;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
@@ -30,13 +32,16 @@ import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||
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.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.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -54,23 +59,50 @@ final class OtlpTracingConfigurations {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty("management.opentelemetry.tracing.export.otlp.endpoint")
|
||||
@Conditional(OtlpEndpointCondition.class)
|
||||
OtlpTracingConnectionDetails otlpTracingConnectionDetails(OtlpTracingProperties properties,
|
||||
ObjectProvider<SslBundles> sslBundles) {
|
||||
return new PropertiesOtlpTracingConnectionDetails(properties, sslBundles.getIfAvailable());
|
||||
OtlpProperties otlpProperties, ObjectProvider<SslBundles> 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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -80,6 +112,12 @@ final class OtlpTracingConfigurations {
|
||||
"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();
|
||||
if (endpoint != null && transport == Transport.HTTP) {
|
||||
endpoint = endpoint.endsWith("/") ? endpoint + "v1/traces" : endpoint + "/v1/traces";
|
||||
}
|
||||
}
|
||||
Assert.state(endpoint != null, "'endpoint' must not be null");
|
||||
return endpoint;
|
||||
}
|
||||
@@ -107,15 +145,17 @@ final class OtlpTracingConfigurations {
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "management.opentelemetry.tracing.export.otlp.transport", havingValue = "http",
|
||||
matchIfMissing = true)
|
||||
OtlpHttpSpanExporter otlpHttpSpanExporter(OtlpTracingProperties properties,
|
||||
OtlpHttpSpanExporter otlpHttpSpanExporter(OtlpTracingProperties properties, OtlpProperties otlpProperties,
|
||||
OtlpTracingConnectionDetails connectionDetails, ObjectProvider<MeterProvider> meterProvider,
|
||||
ObjectProvider<OtlpHttpSpanExporterBuilderCustomizer> 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);
|
||||
.setCompression(resolveCompression(properties, otlpProperties).name().toLowerCase(Locale.ROOT));
|
||||
Map<String, String> 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));
|
||||
@@ -124,21 +164,39 @@ final class OtlpTracingConfigurations {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "management.opentelemetry.tracing.export.otlp.transport", havingValue = "grpc")
|
||||
OtlpGrpcSpanExporter otlpGrpcSpanExporter(OtlpTracingProperties properties,
|
||||
OtlpGrpcSpanExporter otlpGrpcSpanExporter(OtlpTracingProperties properties, OtlpProperties otlpProperties,
|
||||
OtlpTracingConnectionDetails connectionDetails, ObjectProvider<MeterProvider> meterProvider,
|
||||
ObjectProvider<OtlpGrpcSpanExporterBuilderCustomizer> 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);
|
||||
.setCompression(resolveCompression(properties, otlpProperties).name().toLowerCase(Locale.ROOT));
|
||||
Map<String, String> 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));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private OtlpTracingProperties.Compression resolveCompression(OtlpTracingProperties properties,
|
||||
OtlpProperties otlpProperties) {
|
||||
OtlpTracingProperties.Compression compression = properties.getCompression();
|
||||
if (compression != null) {
|
||||
return compression;
|
||||
}
|
||||
OtlpProperties.Compression commonCompression = otlpProperties.getCompression();
|
||||
if (commonCompression != null) {
|
||||
return switch (commonCompression) {
|
||||
case GZIP -> OtlpTracingProperties.Compression.GZIP;
|
||||
case NONE -> OtlpTracingProperties.Compression.NONE;
|
||||
};
|
||||
}
|
||||
return OtlpTracingProperties.Compression.NONE;
|
||||
}
|
||||
|
||||
private void configureSsl(OtlpTracingConnectionDetails connectionDetails,
|
||||
SslContextConfigurer sslContextConfigurer) {
|
||||
SslBundle sslBundle = connectionDetails.getSslBundle();
|
||||
|
||||
+3
-3
@@ -59,7 +59,7 @@ public class OtlpTracingProperties {
|
||||
/**
|
||||
* Method used to compress the payload.
|
||||
*/
|
||||
private Compression compression = Compression.NONE;
|
||||
private @Nullable Compression compression;
|
||||
|
||||
/**
|
||||
* Custom HTTP headers you want to pass to the collector, for example auth headers.
|
||||
@@ -100,11 +100,11 @@ public class OtlpTracingProperties {
|
||||
this.transport = transport;
|
||||
}
|
||||
|
||||
public Compression getCompression() {
|
||||
public @Nullable Compression getCompression() {
|
||||
return this.compression;
|
||||
}
|
||||
|
||||
public void setCompression(Compression compression) {
|
||||
public void setCompression(@Nullable Compression compression) {
|
||||
this.compression = compression;
|
||||
}
|
||||
|
||||
|
||||
+53
@@ -48,6 +48,7 @@ import io.opentelemetry.api.trace.Tracer;
|
||||
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.extension.trace.propagation.B3Propagator;
|
||||
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||
import io.opentelemetry.sdk.resources.Resource;
|
||||
@@ -67,6 +68,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
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 +530,57 @@ class OpenTelemetryTracingAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@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).extracting("delegate.httpSender.headerSupplier")
|
||||
.asInstanceOf(InstanceOfAssertFactories.type(Supplier.class))
|
||||
.satisfies((headerSupplier) -> assertThat(headerSupplier.get())
|
||||
.asInstanceOf(InstanceOfAssertFactories.map(String.class, List.class))
|
||||
.containsEntry("common-header", List.of("common-value"))
|
||||
.containsEntry("tracing-header", List.of("tracing-value"))
|
||||
.containsEntry("shared-header", List.of("tracing-wins")));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendTracesPathToCommonEndpoint() {
|
||||
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(OtlpTracingAutoConfiguration.class))
|
||||
.withPropertyValues("management.opentelemetry.otlp.endpoint=http://localhost:4318")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class);
|
||||
OtlpHttpSpanExporter exporter = context.getBean(OtlpHttpSpanExporter.class);
|
||||
assertThat(exporter).extracting("delegate.httpSender.url")
|
||||
.extracting(Object::toString)
|
||||
.isEqualTo("http://localhost:4318/v1/traces");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotAppendTracesPathToTracingSpecificEndpoint() {
|
||||
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(OtlpTracingAutoConfiguration.class))
|
||||
.withPropertyValues("management.opentelemetry.otlp.endpoint=http://localhost:4318",
|
||||
"management.opentelemetry.tracing.export.otlp.endpoint=http://localhost:4318/custom/traces")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class);
|
||||
OtlpHttpSpanExporter exporter = context.getBean(OtlpHttpSpanExporter.class);
|
||||
assertThat(exporter).extracting("delegate.httpSender.url")
|
||||
.extracting(Object::toString)
|
||||
.isEqualTo("http://localhost:4318/custom/traces");
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeOpenTelemetry(ConfigurableApplicationContext context) {
|
||||
context.addApplicationListener(new OpenTelemetryEventPublisherBeansApplicationListener());
|
||||
Span.current();
|
||||
|
||||
+35
@@ -134,6 +134,41 @@ class OtlpTracingAutoConfigurationIntegrationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void httpSpanExporterFallsBackToCommonCompressionWhenSignalSpecificCompressionIsNotSet() {
|
||||
this.mockWebServer.enqueue(new MockResponse());
|
||||
this.contextRunner
|
||||
.withPropertyValues("management.opentelemetry.otlp.compression=gzip",
|
||||
"management.opentelemetry.tracing.export.otlp.endpoint=http://localhost:%d/test"
|
||||
.formatted(this.mockWebServer.getPort()))
|
||||
.run((context) -> {
|
||||
context.getBean(Tracer.class).nextSpan().name("test").end();
|
||||
assertThat(context.getBean(OtlpHttpSpanExporter.class).flush())
|
||||
.isSameAs(CompletableResultCode.ofSuccess());
|
||||
RecordedRequest request = this.mockWebServer.takeRequest(10, TimeUnit.SECONDS);
|
||||
assertThat(request).isNotNull();
|
||||
assertThat(request.getHeader("Content-Encoding")).isEqualTo("gzip");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void httpSpanExporterSignalSpecificCompressionWinsOverCommonCompression() {
|
||||
this.mockWebServer.enqueue(new MockResponse());
|
||||
this.contextRunner
|
||||
.withPropertyValues("management.opentelemetry.otlp.compression=gzip",
|
||||
"management.opentelemetry.tracing.export.otlp.compression=none",
|
||||
"management.opentelemetry.tracing.export.otlp.endpoint=http://localhost:%d/test"
|
||||
.formatted(this.mockWebServer.getPort()))
|
||||
.run((context) -> {
|
||||
context.getBean(Tracer.class).nextSpan().name("test").end();
|
||||
assertThat(context.getBean(OtlpHttpSpanExporter.class).flush())
|
||||
.isSameAs(CompletableResultCode.ofSuccess());
|
||||
RecordedRequest request = this.mockWebServer.takeRequest(10, TimeUnit.SECONDS);
|
||||
assertThat(request).isNotNull();
|
||||
assertThat(request.getHeader("Content-Encoding")).isNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void grpcSpanExporterShouldExportSpans() {
|
||||
this.contextRunner
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.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.2.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<String, String> headers = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* Method used to compress the payload.
|
||||
*/
|
||||
private @Nullable Compression compression;
|
||||
|
||||
public @Nullable String getEndpoint() {
|
||||
return this.endpoint;
|
||||
}
|
||||
|
||||
public void setEndpoint(@Nullable String endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
public Map<String, String> getHeaders() {
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
public @Nullable Compression getCompression() {
|
||||
return this.compression;
|
||||
}
|
||||
|
||||
public void setCompression(@Nullable Compression compression) {
|
||||
this.compression = compression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compression methods.
|
||||
*/
|
||||
public enum Compression {
|
||||
|
||||
/**
|
||||
* Gzip compression.
|
||||
*/
|
||||
GZIP,
|
||||
|
||||
/**
|
||||
* No compression.
|
||||
*/
|
||||
NONE
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+2
-1
@@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
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 @@ import org.springframework.context.annotation.Import;
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass({ OpenTelemetry.class, SdkLoggerProvider.class })
|
||||
@EnableConfigurationProperties(OtlpLoggingProperties.class)
|
||||
@EnableConfigurationProperties({ OtlpLoggingProperties.class, OtlpProperties.class })
|
||||
@Import({ ConnectionDetails.class, Exporters.class })
|
||||
public final class OtlpLoggingAutoConfiguration {
|
||||
|
||||
|
||||
+71
-11
@@ -16,7 +16,9 @@
|
||||
|
||||
package org.springframework.boot.opentelemetry.autoconfigure.logging.otlp;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
@@ -30,14 +32,17 @@ import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporterBuilder;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||
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.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.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -55,23 +60,50 @@ final class OtlpLoggingConfigurations {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(OtlpLoggingConnectionDetails.class)
|
||||
@ConditionalOnProperty("management.opentelemetry.logging.export.otlp.endpoint")
|
||||
@Conditional(OtlpEndpointCondition.class)
|
||||
PropertiesOtlpLoggingConnectionDetails openTelemetryLoggingConnectionDetails(OtlpLoggingProperties properties,
|
||||
ObjectProvider<SslBundles> sslBundles) {
|
||||
return new PropertiesOtlpLoggingConnectionDetails(properties, sslBundles.getIfAvailable());
|
||||
OtlpProperties otlpProperties, ObjectProvider<SslBundles> 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")
|
||||
static class LoggingEndpoint {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnProperty("management.opentelemetry.otlp.endpoint")
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -81,6 +113,12 @@ final class OtlpLoggingConfigurations {
|
||||
"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();
|
||||
if (endpoint != null && transport == Transport.HTTP) {
|
||||
endpoint = endpoint.endsWith("/") ? endpoint + "v1/logs" : endpoint + "/v1/logs";
|
||||
}
|
||||
}
|
||||
Assert.state(endpoint != null, "'endpoint' must not be null");
|
||||
return endpoint;
|
||||
}
|
||||
@@ -110,14 +148,17 @@ final class OtlpLoggingConfigurations {
|
||||
@ConditionalOnProperty(name = "management.opentelemetry.logging.export.otlp.transport", havingValue = "http",
|
||||
matchIfMissing = true)
|
||||
OtlpHttpLogRecordExporter otlpHttpLogRecordExporter(OtlpLoggingProperties properties,
|
||||
OtlpLoggingConnectionDetails connectionDetails, ObjectProvider<MeterProvider> meterProvider,
|
||||
OtlpProperties otlpProperties, OtlpLoggingConnectionDetails connectionDetails,
|
||||
ObjectProvider<MeterProvider> meterProvider,
|
||||
ObjectProvider<OtlpHttpLogRecordExporterBuilderCustomizer> 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);
|
||||
.setCompression(resolveCompression(properties, otlpProperties).name().toLowerCase(Locale.US));
|
||||
Map<String, String> 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));
|
||||
@@ -127,20 +168,39 @@ final class OtlpLoggingConfigurations {
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "management.opentelemetry.logging.export.otlp.transport", havingValue = "grpc")
|
||||
OtlpGrpcLogRecordExporter otlpGrpcLogRecordExporter(OtlpLoggingProperties properties,
|
||||
OtlpLoggingConnectionDetails connectionDetails, ObjectProvider<MeterProvider> meterProvider,
|
||||
OtlpProperties otlpProperties, OtlpLoggingConnectionDetails connectionDetails,
|
||||
ObjectProvider<MeterProvider> meterProvider,
|
||||
ObjectProvider<OtlpGrpcLogRecordExporterBuilderCustomizer> 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);
|
||||
.setCompression(resolveCompression(properties, otlpProperties).name().toLowerCase(Locale.US));
|
||||
Map<String, String> 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));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private OtlpLoggingProperties.Compression resolveCompression(OtlpLoggingProperties properties,
|
||||
OtlpProperties otlpProperties) {
|
||||
OtlpLoggingProperties.Compression compression = properties.getCompression();
|
||||
if (compression != null) {
|
||||
return compression;
|
||||
}
|
||||
OtlpProperties.Compression commonCompression = otlpProperties.getCompression();
|
||||
if (commonCompression != null) {
|
||||
return switch (commonCompression) {
|
||||
case GZIP -> OtlpLoggingProperties.Compression.GZIP;
|
||||
case NONE -> OtlpLoggingProperties.Compression.NONE;
|
||||
};
|
||||
}
|
||||
return OtlpLoggingProperties.Compression.NONE;
|
||||
}
|
||||
|
||||
private void configureSsl(OtlpLoggingConnectionDetails connectionDetails,
|
||||
SslContextConfigurer sslContextConfigurer) {
|
||||
SslBundle sslBundle = connectionDetails.getSslBundle();
|
||||
|
||||
+3
-3
@@ -59,7 +59,7 @@ public class OtlpLoggingProperties {
|
||||
/**
|
||||
* Method used to compress the payload.
|
||||
*/
|
||||
private Compression compression = Compression.NONE;
|
||||
private @Nullable Compression compression;
|
||||
|
||||
/**
|
||||
* Custom HTTP headers you want to pass to the collector, for example auth headers.
|
||||
@@ -100,11 +100,11 @@ public class OtlpLoggingProperties {
|
||||
this.transport = transport;
|
||||
}
|
||||
|
||||
public Compression getCompression() {
|
||||
public @Nullable Compression getCompression() {
|
||||
return this.compression;
|
||||
}
|
||||
|
||||
public void setCompression(Compression compression) {
|
||||
public void setCompression(@Nullable Compression compression) {
|
||||
this.compression = compression;
|
||||
}
|
||||
|
||||
|
||||
+51
@@ -18,10 +18,13 @@ package org.springframework.boot.opentelemetry.autoconfigure.logging;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
|
||||
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||
import io.opentelemetry.sdk.logs.LogLimits;
|
||||
import io.opentelemetry.sdk.logs.LogRecordProcessor;
|
||||
@@ -31,12 +34,16 @@ 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.assertj.core.api.InstanceOfAssertFactories;
|
||||
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.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 +183,50 @@ class OpenTelemetryLoggingAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@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).extracting("delegate.httpSender.headerSupplier")
|
||||
.asInstanceOf(InstanceOfAssertFactories.type(Supplier.class))
|
||||
.satisfies((headerSupplier) -> assertThat(headerSupplier.get())
|
||||
.asInstanceOf(InstanceOfAssertFactories.map(String.class, List.class))
|
||||
.containsEntry("common-header", List.of("common-value"))
|
||||
.containsEntry("logging-header", List.of("logging-value"))
|
||||
.containsEntry("shared-header", List.of("logging-wins")));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFallbackToCommonEndpointAndAppendPathForLogging() {
|
||||
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(OtlpLoggingAutoConfiguration.class))
|
||||
.withPropertyValues("management.opentelemetry.otlp.endpoint=http://common-host:4318")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(OtlpLoggingConnectionDetails.class);
|
||||
OtlpLoggingConnectionDetails connectionDetails = context.getBean(OtlpLoggingConnectionDetails.class);
|
||||
assertThat(connectionDetails.getUrl(Transport.HTTP)).isEqualTo("http://common-host:4318/v1/logs");
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class UserConfiguration {
|
||||
|
||||
|
||||
+31
@@ -108,6 +108,37 @@ class OtlpLoggingAutoConfigurationIntegrationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void httpLogRecordExporterFallsBackToCommonCompressionWhenSignalSpecificCompressionIsNotSet() {
|
||||
this.mockWebServer.enqueue(new MockResponse());
|
||||
this.contextRunner
|
||||
.withPropertyValues("management.opentelemetry.logging.export.otlp.endpoint=http://localhost:%d/v1/logs"
|
||||
.formatted(this.mockWebServer.getPort()), "management.opentelemetry.otlp.compression=gzip")
|
||||
.run((context) -> {
|
||||
logMessage(context);
|
||||
RecordedRequest request = this.mockWebServer.takeRequest(10, TimeUnit.SECONDS);
|
||||
assertThat(request).isNotNull();
|
||||
assertThat(request.getHeader("Content-Encoding")).isEqualTo("gzip");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void httpLogRecordExporterSignalSpecificCompressionWinsOverCommonCompression() {
|
||||
this.mockWebServer.enqueue(new MockResponse());
|
||||
this.contextRunner
|
||||
.withPropertyValues(
|
||||
"management.opentelemetry.logging.export.otlp.endpoint=http://localhost:%d/v1/logs"
|
||||
.formatted(this.mockWebServer.getPort()),
|
||||
"management.opentelemetry.logging.export.otlp.compression=none",
|
||||
"management.opentelemetry.otlp.compression=gzip")
|
||||
.run((context) -> {
|
||||
logMessage(context);
|
||||
RecordedRequest request = this.mockWebServer.takeRequest(10, TimeUnit.SECONDS);
|
||||
assertThat(request).isNotNull();
|
||||
assertThat(request.getHeader("Content-Encoding")).isNull();
|
||||
});
|
||||
}
|
||||
|
||||
private static void logMessage(ApplicationContext context) {
|
||||
SdkLoggerProvider loggerProvider = context.getBean(SdkLoggerProvider.class);
|
||||
loggerProvider.get("test")
|
||||
|
||||
+50
@@ -38,6 +38,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
||||
import org.springframework.boot.context.annotation.ImportCandidates;
|
||||
import org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetrySdkAutoConfiguration;
|
||||
import org.springframework.boot.opentelemetry.autoconfigure.OtlpProperties;
|
||||
import org.springframework.boot.opentelemetry.autoconfigure.logging.OpenTelemetryLoggingAutoConfiguration;
|
||||
import org.springframework.boot.opentelemetry.autoconfigure.logging.SdkLoggerProviderBuilderCustomizer;
|
||||
import org.springframework.boot.opentelemetry.autoconfigure.logging.otlp.OtlpLoggingConfigurations.ConnectionDetails.PropertiesOtlpLoggingConnectionDetails;
|
||||
@@ -321,6 +322,55 @@ class OtlpLoggingAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendLogsPathToCommonEndpointForHttpTransport() {
|
||||
OtlpLoggingProperties properties = new OtlpLoggingProperties();
|
||||
properties.setTransport(Transport.HTTP);
|
||||
OtlpProperties otlpProperties = new OtlpProperties();
|
||||
otlpProperties.setEndpoint("http://localhost:4318");
|
||||
PropertiesOtlpLoggingConnectionDetails connectionDetails = new PropertiesOtlpLoggingConnectionDetails(
|
||||
properties, otlpProperties, null);
|
||||
|
||||
assertThat(connectionDetails.getUrl(Transport.HTTP)).isEqualTo("http://localhost:4318/v1/logs");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotAppendLogsPathToCommonEndpointForGrpcTransport() {
|
||||
OtlpLoggingProperties properties = new OtlpLoggingProperties();
|
||||
properties.setTransport(Transport.GRPC);
|
||||
OtlpProperties otlpProperties = new OtlpProperties();
|
||||
otlpProperties.setEndpoint("http://localhost:4318");
|
||||
PropertiesOtlpLoggingConnectionDetails connectionDetails = new PropertiesOtlpLoggingConnectionDetails(
|
||||
properties, otlpProperties, null);
|
||||
|
||||
assertThat(connectionDetails.getUrl(Transport.GRPC)).isEqualTo("http://localhost:4318");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotAppendLogsPathToLoggingSpecificEndpoint() {
|
||||
OtlpLoggingProperties properties = new OtlpLoggingProperties();
|
||||
properties.setEndpoint("http://localhost:4318/custom/logs");
|
||||
OtlpProperties otlpProperties = new OtlpProperties();
|
||||
otlpProperties.setEndpoint("http://localhost:4318");
|
||||
PropertiesOtlpLoggingConnectionDetails connectionDetails = new PropertiesOtlpLoggingConnectionDetails(
|
||||
properties, otlpProperties, null);
|
||||
|
||||
assertThat(connectionDetails.getUrl(Transport.HTTP)).isEqualTo("http://localhost:4318/custom/logs");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendLogsPathToCommonEndpointWithTrailingSlash() {
|
||||
OtlpLoggingProperties properties = new OtlpLoggingProperties();
|
||||
|
||||
OtlpProperties otlpProperties = new OtlpProperties();
|
||||
otlpProperties.setEndpoint("http://localhost:4318/");
|
||||
|
||||
PropertiesOtlpLoggingConnectionDetails connectionDetails = new PropertiesOtlpLoggingConnectionDetails(
|
||||
properties, otlpProperties, null);
|
||||
|
||||
assertThat(connectionDetails.getUrl(Transport.HTTP)).isEqualTo("http://localhost:4318/v1/logs");
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public static class MultipleSdkLoggerProviderBuilderCustomizersConfig {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user