From 07543a4e23309beed4f2979257dfe924e44bbc89 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Wed, 16 Mar 2022 15:59:33 +0100 Subject: [PATCH] Implement auto-configurations for Brave and OpenTelemetry - Configure Zipkin - Configure Wavefront - Configure Brave - Configure OpenTelemetry - Configure Micrometer Tracing bridges for OpenTelemetry and Brave - Create the ObservationHandler for tracing with Micrometer Closes gh-30156 --- .../build.gradle | 5 + ...vefrontMetricsExportAutoConfiguration.java | 38 +-- .../export/wavefront/WavefrontProperties.java | 132 -------- .../WavefrontPropertiesConfigAdapter.java | 34 +- .../tracing/BraveAutoConfiguration.java | 129 ++++++++ .../MicrometerTracingAutoConfiguration.java | 46 +++ .../OpenTelemetryAutoConfiguration.java | 39 +++ .../tracing/OpenTelemetryConfigurations.java | 151 +++++++++ .../tracing/TracingProperties.java | 56 ++++ .../autoconfigure/tracing/package-info.java | 20 ++ .../wavefront/MeterRegistrySpanMetrics.java | 73 +++++ .../WavefrontTracingAutoConfiguration.java | 136 ++++++++ .../tracing/wavefront/package-info.java | 20 ++ .../zipkin/ZipkinAutoConfiguration.java | 57 ++++ .../tracing/zipkin/ZipkinConfigurations.java | 109 +++++++ .../tracing/zipkin/ZipkinProperties.java | 71 +++++ .../zipkin/ZipkinRestTemplateSender.java | 170 ++++++++++ .../tracing/zipkin/package-info.java | 20 ++ .../wavefront/WavefrontAutoConfiguration.java | 61 ++++ .../wavefront/WavefrontProperties.java | 290 ++++++++++++++++++ .../autoconfigure/wavefront/package-info.java | 20 ++ ...itional-spring-configuration-metadata.json | 14 +- ...ot.autoconfigure.AutoConfiguration.imports | 6 + .../SpringApplicationHierarchyTests.java | 22 +- ...intsAutoConfigurationIntegrationTests.java | 8 +- ...hRegistryPropertiesConfigAdapterTests.java | 4 +- ...ntMetricsExportAutoConfigurationTests.java | 69 +---- ...WavefrontPropertiesConfigAdapterTests.java | 49 ++- .../tracing/BraveAutoConfigurationTests.java | 156 ++++++++++ ...crometerTracingAutoConfigurationTests.java | 88 ++++++ ...gurationsMicrometerConfigurationTests.java | 111 +++++++ ...ryConfigurationsSdkConfigurationTests.java | 112 +++++++ ...onfigurationsTracerConfigurationTests.java | 90 ++++++ .../MeterRegistrySpanMetricsTests.java | 101 ++++++ ...avefrontTracingAutoConfigurationTests.java | 192 ++++++++++++ .../tracing/zipkin/NoopSender.java | 62 ++++ .../zipkin/ZipkinAutoConfigurationTests.java | 71 +++++ ...ConfigurationsBraveConfigurationTests.java | 92 ++++++ ...ationsOpenTelemetryConfigurationTests.java | 104 +++++++ ...figurationsReporterConfigurationTests.java | 97 ++++++ ...onfigurationsSenderConfigurationTests.java | 100 ++++++ .../zipkin/ZipkinRestTemplateSenderTests.java | 122 ++++++++ .../WavefrontAutoConfigurationTests.java | 101 ++++++ ...avefrontPropertiesMetricsExportTests.java} | 20 +- .../wavefront/WavefrontPropertiesTests.java | 54 ++++ .../spring-boot-dependencies/build.gradle | 18 +- .../src/docs/asciidoc/actuator/metrics.adoc | 8 +- 47 files changed, 3244 insertions(+), 304 deletions(-) delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontProperties.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryConfigurations.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/package-info.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/MeterRegistrySpanMetrics.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/package-info.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurations.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinProperties.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateSender.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/package-info.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontProperties.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/package-info.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryConfigurationsMicrometerConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryConfigurationsSdkConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryConfigurationsTracerConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/MeterRegistrySpanMetricsTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/NoopSender.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsBraveConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsOpenTelemetryConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsReporterConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsSenderConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateSenderTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontAutoConfigurationTests.java rename spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/{metrics/export/wavefront/WavefrontPropertiesTests.java => wavefront/WavefrontPropertiesMetricsExportTests.java} (58%) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontPropertiesTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle index 0e2363c67599..f8e30f8a1afc 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle @@ -50,6 +50,9 @@ dependencies { optional("io.micrometer:micrometer-observation") optional("io.micrometer:micrometer-core") optional("io.micrometer:micrometer-tracing-api") + optional("io.micrometer:micrometer-tracing-bridge-brave") + optional("io.micrometer:micrometer-tracing-bridge-otel") + optional("io.micrometer:micrometer-tracing-reporter-wavefront") optional("io.micrometer:micrometer-registry-appoptics") optional("io.micrometer:micrometer-registry-atlas") { exclude group: "javax.inject", module: "javax.inject" @@ -75,6 +78,8 @@ dependencies { optional("io.micrometer:micrometer-registry-signalfx") optional("io.micrometer:micrometer-registry-statsd") optional("io.micrometer:micrometer-registry-wavefront") + optional("io.zipkin.reporter2:zipkin-sender-urlconnection") + optional("io.opentelemetry:opentelemetry-exporter-zipkin") optional("io.projectreactor.netty:reactor-netty-http") optional("io.r2dbc:r2dbc-pool") optional("io.r2dbc:r2dbc-spi") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontMetricsExportAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontMetricsExportAutoConfiguration.java index 9524db72cfdf..386bec08d282 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontMetricsExportAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontMetricsExportAutoConfiguration.java @@ -16,10 +16,7 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront; -import java.time.Duration; - import com.wavefront.sdk.common.WavefrontSender; -import com.wavefront.sdk.common.clients.WavefrontClient.Builder; import io.micrometer.core.instrument.Clock; import io.micrometer.wavefront.WavefrontConfig; import io.micrometer.wavefront.WavefrontMeterRegistry; @@ -28,16 +25,15 @@ import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.export.ConditionalOnEnabledMetricsExport; import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront.WavefrontProperties.Sender; +import org.springframework.boot.actuate.autoconfigure.wavefront.WavefrontAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.wavefront.WavefrontProperties; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 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.context.properties.EnableConfigurationProperties; -import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.context.annotation.Bean; -import org.springframework.util.unit.DataSize; /** * {@link EnableAutoConfiguration Auto-configuration} for exporting metrics to Wavefront. @@ -49,29 +45,17 @@ */ @AutoConfiguration( before = { CompositeMeterRegistryAutoConfiguration.class, SimpleMetricsExportAutoConfiguration.class }, - after = MetricsAutoConfiguration.class) -@ConditionalOnBean(Clock.class) + after = { MetricsAutoConfiguration.class, WavefrontAutoConfiguration.class }) +@ConditionalOnBean({ Clock.class, WavefrontSender.class }) @ConditionalOnClass({ WavefrontMeterRegistry.class, WavefrontSender.class }) @ConditionalOnEnabledMetricsExport("wavefront") @EnableConfigurationProperties(WavefrontProperties.class) public class WavefrontMetricsExportAutoConfiguration { - private final WavefrontProperties properties; - - public WavefrontMetricsExportAutoConfiguration(WavefrontProperties properties) { - this.properties = properties; - } - - @Bean - @ConditionalOnMissingBean - public WavefrontConfig wavefrontConfig() { - return new WavefrontPropertiesConfigAdapter(this.properties); - } - @Bean @ConditionalOnMissingBean - public WavefrontSender wavefrontSender(WavefrontConfig wavefrontConfig) { - return createWavefrontSender(wavefrontConfig); + public WavefrontConfig wavefrontConfig(WavefrontProperties properties) { + return new WavefrontPropertiesConfigAdapter(properties); } @Bean @@ -81,14 +65,4 @@ public WavefrontMeterRegistry wavefrontMeterRegistry(WavefrontConfig wavefrontCo return WavefrontMeterRegistry.builder(wavefrontConfig).clock(clock).wavefrontSender(wavefrontSender).build(); } - private WavefrontSender createWavefrontSender(WavefrontConfig wavefrontConfig) { - Builder builder = WavefrontMeterRegistry.getDefaultSenderBuilder(wavefrontConfig); - PropertyMapper mapper = PropertyMapper.get().alwaysApplyingWhenNonNull(); - Sender sender = this.properties.getSender(); - mapper.from(sender.getMaxQueueSize()).to(builder::maxQueueSize); - mapper.from(sender.getFlushInterval()).asInt(Duration::getSeconds).to(builder::flushIntervalSeconds); - mapper.from(sender.getMessageSize()).asInt(DataSize::toBytes).to(builder::messageSizeBytes); - return builder.build(); - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontProperties.java deleted file mode 100644 index d43c54da907f..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontProperties.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront; - -import java.net.URI; -import java.time.Duration; - -import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.PushRegistryProperties; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.util.unit.DataSize; - -/** - * {@link ConfigurationProperties @ConfigurationProperties} for configuring Wavefront - * metrics export. - * - * @author Jon Schneider - * @author Stephane Nicoll - * @since 2.0.0 - */ -@ConfigurationProperties("management.wavefront.metrics.export") -public class WavefrontProperties extends PushRegistryProperties { - - /** - * URI to ship metrics to. - */ - private URI uri = URI.create("https://longboard.wavefront.com"); - - /** - * Unique identifier for the app instance that is the source of metrics being - * published to Wavefront. Defaults to the local host name. - */ - private String source; - - /** - * API token used when publishing metrics directly to the Wavefront API host. - */ - private String apiToken; - - /** - * Global prefix to separate metrics originating from this app's instrumentation from - * those originating from other Wavefront integrations when viewed in the Wavefront - * UI. - */ - private String globalPrefix; - - private final Sender sender = new Sender(); - - public URI getUri() { - return this.uri; - } - - public void setUri(URI uri) { - this.uri = uri; - } - - public String getSource() { - return this.source; - } - - public void setSource(String source) { - this.source = source; - } - - public String getApiToken() { - return this.apiToken; - } - - public void setApiToken(String apiToken) { - this.apiToken = apiToken; - } - - public String getGlobalPrefix() { - return this.globalPrefix; - } - - public void setGlobalPrefix(String globalPrefix) { - this.globalPrefix = globalPrefix; - } - - public Sender getSender() { - return this.sender; - } - - public static class Sender { - - private int maxQueueSize = 50000; - - private Duration flushInterval = Duration.ofSeconds(1); - - private DataSize messageSize = DataSize.ofBytes(Integer.MAX_VALUE); - - public int getMaxQueueSize() { - return this.maxQueueSize; - } - - public void setMaxQueueSize(int maxQueueSize) { - this.maxQueueSize = maxQueueSize; - } - - public Duration getFlushInterval() { - return this.flushInterval; - } - - public void setFlushInterval(Duration flushInterval) { - this.flushInterval = flushInterval; - } - - public DataSize getMessageSize() { - return this.messageSize; - } - - public void setMessageSize(DataSize messageSize) { - this.messageSize = messageSize; - } - - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapter.java index a46e5c4ee5bc..8422593ff124 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapter.java @@ -19,18 +19,24 @@ import io.micrometer.wavefront.WavefrontConfig; import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.PushRegistryPropertiesConfigAdapter; +import org.springframework.boot.actuate.autoconfigure.wavefront.WavefrontProperties; +import org.springframework.boot.actuate.autoconfigure.wavefront.WavefrontProperties.Metrics.Export; /** - * Adapter to convert {@link WavefrontProperties} to a {@link WavefrontConfig}. + * Adapter to convert {@link WavefrontProperties.Metrics} to a {@link WavefrontConfig}. * * @author Jon Schneider + * @author Moritz Halbritter * @since 2.0.0 */ -public class WavefrontPropertiesConfigAdapter extends PushRegistryPropertiesConfigAdapter - implements WavefrontConfig { +public class WavefrontPropertiesConfigAdapter + extends PushRegistryPropertiesConfigAdapter implements WavefrontConfig { + + private final WavefrontProperties properties; public WavefrontPropertiesConfigAdapter(WavefrontProperties properties) { - super(properties); + super(properties.getMetrics().getExport()); + this.properties = properties; } @Override @@ -39,32 +45,28 @@ public String prefix() { } @Override - public String get(String k) { - return null; + public String uri() { + return this.properties.getEffectiveUri().toString(); } @Override - public String uri() { - return get(this::getUriAsString, WavefrontConfig.DEFAULT_DIRECT::uri); + public String source() { + return this.properties.getSourceOrDefault(); } @Override - public String source() { - return get(WavefrontProperties::getSource, WavefrontConfig.super::source); + public int batchSize() { + return this.properties.getSender().getBatchSize(); } @Override public String apiToken() { - return get(WavefrontProperties::getApiToken, WavefrontConfig.super::apiToken); + return this.properties.getApiTokenOrThrow(); } @Override public String globalPrefix() { - return get(WavefrontProperties::getGlobalPrefix, WavefrontConfig.super::globalPrefix); - } - - private String getUriAsString(WavefrontProperties properties) { - return (properties.getUri() != null) ? properties.getUri().toString() : null; + return get(Export::getGlobalPrefix, WavefrontConfig.super::globalPrefix); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java new file mode 100644 index 000000000000..e084680152af --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java @@ -0,0 +1,129 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import java.util.List; + +import brave.Tracing; +import brave.Tracing.Builder; +import brave.TracingCustomizer; +import brave.handler.SpanHandler; +import brave.propagation.B3Propagation; +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.ScopeDecorator; +import brave.propagation.CurrentTraceContextCustomizer; +import brave.propagation.Propagation.Factory; +import brave.propagation.ThreadLocalCurrentTraceContext; +import brave.sampler.Sampler; +import io.micrometer.tracing.brave.bridge.BraveBaggageManager; +import io.micrometer.tracing.brave.bridge.BraveCurrentTraceContext; +import io.micrometer.tracing.brave.bridge.BraveTracer; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Brave. + * + * @author Moritz Halbritter + * @since 3.0.0 + */ +@AutoConfiguration(before = MicrometerTracingAutoConfiguration.class) +@ConditionalOnClass(brave.Tracer.class) +@EnableConfigurationProperties(TracingProperties.class) +public class BraveAutoConfiguration { + + /** + * Default value for application name if {@code spring.application.name} is not set. + */ + private static final String DEFAULT_APPLICATION_NAME = "application"; + + @Bean + @ConditionalOnMissingBean + public Tracing braveTracing(Environment environment, List spanHandlers, + List tracingCustomizers, CurrentTraceContext currentTraceContext, + Factory propagationFactory, Sampler sampler) { + String applicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME); + Builder builder = Tracing.newBuilder().currentTraceContext(currentTraceContext) + .propagationFactory(propagationFactory).sampler(sampler).localServiceName(applicationName); + for (SpanHandler spanHandler : spanHandlers) { + builder.addSpanHandler(spanHandler); + } + for (TracingCustomizer tracingCustomizer : tracingCustomizers) { + tracingCustomizer.customize(builder); + } + return builder.build(); + } + + @Bean + @ConditionalOnMissingBean + public brave.Tracer braveTracer(Tracing tracing) { + return tracing.tracer(); + } + + @Bean + @ConditionalOnMissingBean + public CurrentTraceContext braveCurrentTraceContext(List scopeDecorators, + List currentTraceContextCustomizers) { + ThreadLocalCurrentTraceContext.Builder builder = ThreadLocalCurrentTraceContext.newBuilder(); + for (ScopeDecorator scopeDecorator : scopeDecorators) { + builder.addScopeDecorator(scopeDecorator); + } + for (CurrentTraceContextCustomizer currentTraceContextCustomizer : currentTraceContextCustomizers) { + currentTraceContextCustomizer.customize(builder); + } + return builder.build(); + } + + @Bean + @ConditionalOnMissingBean + public Factory bravePropagationFactory() { + return B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.SINGLE_NO_PARENT).build(); + } + + @Bean + @ConditionalOnMissingBean + public Sampler braveSampler(TracingProperties properties) { + return Sampler.create(properties.getSampling().getProbability()); + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(BraveTracer.class) + static class BraveMicrometer { + + @Bean + @ConditionalOnMissingBean + BraveTracer braveTracerBridge(brave.Tracer tracer, CurrentTraceContext currentTraceContext, + BraveBaggageManager braveBaggageManager) { + return new BraveTracer(tracer, new BraveCurrentTraceContext(currentTraceContext), braveBaggageManager); + } + + @Bean + @ConditionalOnMissingBean + BraveBaggageManager braveBaggageManager() { + return new BraveBaggageManager(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java new file mode 100644 index 000000000000..5d279b54c2f5 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.handler.DefaultTracingObservationHandler; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for the Micrometer Tracing API. + * + * @author Moritz Halbritter + * @since 3.0.0 + */ +@AutoConfiguration +@ConditionalOnClass(Tracer.class) +public class MicrometerTracingAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(Tracer.class) + public DefaultTracingObservationHandler defaultTracingObservationHandler(Tracer tracer) { + return new DefaultTracingObservationHandler(tracer); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java new file mode 100644 index 000000000000..006eaf39b658 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryConfigurations.MicrometerConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryConfigurations.SdkConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryConfigurations.TracerConfiguration; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Import; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for OpenTelemetry. + * + * It uses imports on {@link OpenTelemetryConfigurations} to guarantee the correct + * configuration ordering. + * + * @author Moritz Halbritter + * @since 3.0.0 + */ +@AutoConfiguration(before = MicrometerTracingAutoConfiguration.class) +@Import({ SdkConfiguration.class, TracerConfiguration.class, MicrometerConfiguration.class }) +public class OpenTelemetryAutoConfiguration { + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryConfigurations.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryConfigurations.java new file mode 100644 index 000000000000..fe95f30916ad --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryConfigurations.java @@ -0,0 +1,151 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import java.util.List; +import java.util.stream.Collectors; + +import io.micrometer.tracing.otel.bridge.OtelBaggageManager; +import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; +import io.micrometer.tracing.otel.bridge.OtelTracer; +import io.micrometer.tracing.otel.bridge.OtelTracer.EventPublisher; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; +import io.opentelemetry.sdk.trace.SpanProcessor; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; + +import org.springframework.boot.SpringBootVersion; +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.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +/** + * Configurations for Open Telemetry. Those are imported by + * {@link OpenTelemetryAutoConfiguration}. + * + * @author Moritz Halbritter + */ +class OpenTelemetryConfigurations { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(SdkTracerProvider.class) + @EnableConfigurationProperties(TracingProperties.class) + static class SdkConfiguration { + + /** + * Default value for application name if {@code spring.application.name} is not + * set. + */ + private static final String DEFAULT_APPLICATION_NAME = "application"; + + @Bean + @ConditionalOnMissingBean + OpenTelemetry openTelemetry(SdkTracerProvider sdkTracerProvider, ContextPropagators contextPropagators) { + return OpenTelemetrySdk.builder().setTracerProvider(sdkTracerProvider).setPropagators(contextPropagators) + .build(); + } + + @Bean + @ConditionalOnMissingBean + SdkTracerProvider otelSdkTracerProvider(Environment environment, List spanProcessors, + Sampler sampler) { + String applicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME); + SdkTracerProviderBuilder builder = SdkTracerProvider.builder().setSampler(sampler) + .setResource(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName))); + for (SpanProcessor spanProcessor : spanProcessors) { + builder.addSpanProcessor(spanProcessor); + } + return builder.build(); + } + + @Bean + @ConditionalOnMissingBean + ContextPropagators otelContextPropagators(List textMapPropagators) { + return ContextPropagators.create(TextMapPropagator.composite(textMapPropagators)); + } + + @Bean + @ConditionalOnMissingBean + Sampler otelSampler(TracingProperties properties) { + return Sampler.traceIdRatioBased(properties.getSampling().getProbability()); + } + + @Bean + @ConditionalOnMissingBean + SpanProcessor otelSpanProcessor(List spanExporter) { + return SpanProcessor.composite(spanExporter.stream() + .map((exporter) -> BatchSpanProcessor.builder(exporter).build()).collect(Collectors.toList())); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(Tracer.class) + static class TracerConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(OpenTelemetry.class) + Tracer otelTracer(OpenTelemetry openTelemetry) { + return openTelemetry.getTracer("org.springframework.boot", SpringBootVersion.getVersion()); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(OtelTracer.class) + static class MicrometerConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(Tracer.class) + OtelTracer micrometerOtelTracer(Tracer tracer, EventPublisher eventPublisher, + OtelCurrentTraceContext otelCurrentTraceContext) { + return new OtelTracer(tracer, otelCurrentTraceContext, eventPublisher, + new OtelBaggageManager(otelCurrentTraceContext, List.of(), List.of())); + } + + @Bean + @ConditionalOnMissingBean + EventPublisher otelTracerEventPublisher() { + return (event) -> { + }; + } + + @Bean + @ConditionalOnMissingBean + OtelCurrentTraceContext otelCurrentTraceContext() { + return new OtelCurrentTraceContext(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java new file mode 100644 index 000000000000..56ce52a93a5a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for tracing. + * + * @author Moritz Halbritter + * @since 3.0.0 + */ +@ConfigurationProperties("management.tracing") +public class TracingProperties { + + /** + * Sampling configuration. + */ + private final Sampling sampling = new Sampling(); + + public Sampling getSampling() { + return this.sampling; + } + + public static class Sampling { + + /** + * Probability in the range from 0.0 to 1.0 that a trace will be sampled. + */ + private float probability = 0.10f; + + public float getProbability() { + return this.probability; + } + + public void setProbability(float probability) { + this.probability = probability; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/package-info.java new file mode 100644 index 000000000000..64e6f7cad706 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for Micrometer Tracing. + */ +package org.springframework.boot.actuate.autoconfigure.tracing; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/MeterRegistrySpanMetrics.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/MeterRegistrySpanMetrics.java new file mode 100644 index 000000000000..1f9d4e0325e3 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/MeterRegistrySpanMetrics.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.wavefront; + +import java.util.concurrent.BlockingQueue; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.tracing.reporter.wavefront.SpanMetrics; + +/** + * Bridges {@link SpanMetrics} to a {@link MeterRegistry}. + * + * @author Moritz Halbritter + */ +class MeterRegistrySpanMetrics implements SpanMetrics { + + private final Counter spansReceived; + + private final Counter spansDropped; + + private final Counter reportErrors; + + private final MeterRegistry meterRegistry; + + MeterRegistrySpanMetrics(MeterRegistry meterRegistry) { + this.meterRegistry = meterRegistry; + this.spansReceived = meterRegistry.counter("wavefront.reporter.spans.received"); + this.spansDropped = meterRegistry.counter("wavefront.reporter.spans.dropped"); + this.reportErrors = meterRegistry.counter("wavefront.reporter.errors"); + } + + @Override + public void reportDropped() { + this.spansDropped.increment(); + } + + @Override + public void reportReceived() { + this.spansReceived.increment(); + } + + @Override + public void reportErrors() { + this.reportErrors.increment(); + } + + @Override + public void registerQueueSize(BlockingQueue queue) { + this.meterRegistry.gauge("wavefront.reporter.queue.size", queue, (q) -> (double) q.size()); + } + + @Override + public void registerQueueRemainingCapacity(BlockingQueue queue) { + this.meterRegistry.gauge("wavefront.reporter.queue.remaining_capacity", queue, + (q) -> (double) q.remainingCapacity()); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfiguration.java new file mode 100644 index 000000000000..9eea91397ac4 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfiguration.java @@ -0,0 +1,136 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.wavefront; + +import java.util.Set; + +import brave.handler.SpanHandler; +import com.wavefront.sdk.common.WavefrontSender; +import com.wavefront.sdk.common.application.ApplicationTags; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.tracing.reporter.wavefront.SpanMetrics; +import io.micrometer.tracing.reporter.wavefront.WavefrontBraveSpanHandler; +import io.micrometer.tracing.reporter.wavefront.WavefrontOtelSpanHandler; +import io.micrometer.tracing.reporter.wavefront.WavefrontSpanHandler; +import io.opentelemetry.sdk.trace.export.SpanExporter; + +import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.wavefront.WavefrontAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.wavefront.WavefrontProperties; +import org.springframework.boot.actuate.autoconfigure.wavefront.WavefrontProperties.Tracing; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +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.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Wavefront. + * + * @author Moritz Halbritter + * @since 3.0.0 + */ +@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class, + WavefrontAutoConfiguration.class }) +@EnableConfigurationProperties(WavefrontProperties.class) +@ConditionalOnBean(WavefrontSender.class) +public class WavefrontTracingAutoConfiguration { + + /** + * Default value for application name if {@code spring.application.name} is not set. + */ + private static final String DEFAULT_APPLICATION_NAME = "application"; + + @Bean + @ConditionalOnMissingBean + public ApplicationTags applicationTags(Environment environment, WavefrontProperties properties) { + String springApplicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME); + Tracing tracing = properties.getTracing(); + String applicationName = (tracing.getApplicationName() != null) ? tracing.getApplicationName() + : springApplicationName; + String serviceName = (tracing.getServiceName() != null) ? tracing.getServiceName() : springApplicationName; + return new ApplicationTags.Builder(applicationName, serviceName).build(); + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(WavefrontSpanHandler.class) + static class WavefrontMicrometer { + + @Bean + @ConditionalOnMissingBean + WavefrontSpanHandler wavefrontSpanHandler(WavefrontProperties properties, WavefrontSender wavefrontSender, + SpanMetrics spanMetrics, ApplicationTags applicationTags) { + return new WavefrontSpanHandler(properties.getSender().getMaxQueueSize(), wavefrontSender, spanMetrics, + properties.getSourceOrDefault(), applicationTags, Set.of()); + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnBean(MeterRegistry.class) + static class MeterRegistrySpanMetricsConfiguration { + + @Bean + @ConditionalOnMissingBean + MeterRegistrySpanMetrics meterRegistrySpanMetrics(MeterRegistry meterRegistry) { + return new MeterRegistrySpanMetrics(meterRegistry); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingBean(MeterRegistry.class) + static class NoopSpanMetricsConfiguration { + + @Bean + @ConditionalOnMissingBean + SpanMetrics meterRegistrySpanMetrics() { + return SpanMetrics.NOOP; + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(SpanHandler.class) + static class WavefrontBrave { + + @Bean + @ConditionalOnMissingBean + WavefrontBraveSpanHandler wavefrontBraveSpanHandler(WavefrontSpanHandler wavefrontSpanHandler) { + return new WavefrontBraveSpanHandler(wavefrontSpanHandler); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(SpanExporter.class) + static class WavefrontOpenTelemetry { + + @Bean + @ConditionalOnMissingBean + WavefrontOtelSpanHandler wavefrontOtelSpanHandler(WavefrontSpanHandler wavefrontSpanHandler) { + return new WavefrontOtelSpanHandler(wavefrontSpanHandler); + } + + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/package-info.java new file mode 100644 index 000000000000..5ded81bd8e2f --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for tracing with Wavefront. + */ +package org.springframework.boot.actuate.autoconfigure.tracing.wavefront; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfiguration.java new file mode 100644 index 000000000000..89862e4e6635 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfiguration.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; + +import zipkin2.Span; +import zipkin2.codec.BytesEncoder; +import zipkin2.codec.SpanBytesEncoder; +import zipkin2.reporter.Sender; + +import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.BraveConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.OpenTelemetryConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.ReporterConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.SenderConfiguration; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Zipkin. + * + * It uses imports on {@link ZipkinConfigurations} to guarantee the correct configuration + * ordering. + * + * @author Moritz Halbritter + * @since 3.0.0 + */ +@AutoConfiguration(after = RestTemplateAutoConfiguration.class) +@ConditionalOnClass(Sender.class) +@Import({ SenderConfiguration.class, ReporterConfiguration.class, BraveConfiguration.class, + OpenTelemetryConfiguration.class }) +public class ZipkinAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public BytesEncoder spanBytesEncoder() { + return SpanBytesEncoder.JSON_V2; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurations.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurations.java new file mode 100644 index 000000000000..cd7c96347fec --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurations.java @@ -0,0 +1,109 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; + +import brave.handler.SpanHandler; +import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter; +import zipkin2.Span; +import zipkin2.codec.BytesEncoder; +import zipkin2.reporter.AsyncReporter; +import zipkin2.reporter.Reporter; +import zipkin2.reporter.Sender; +import zipkin2.reporter.brave.ZipkinSpanHandler; +import zipkin2.reporter.urlconnection.URLConnectionSender; + +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.ConditionalOnMissingClass; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +/** + * Configurations for Zipkin. Those are imported by {@link ZipkinAutoConfiguration}. + * + * @author Moritz Halbritter + */ +class ZipkinConfigurations { + + @Configuration(proxyBeanMethods = false) + @EnableConfigurationProperties(ZipkinProperties.class) + static class SenderConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnClass(URLConnectionSender.class) + Sender urlConnectionSender(ZipkinProperties properties) { + return URLConnectionSender.newBuilder().connectTimeout((int) properties.getConnectTimeout().getSeconds()) + .readTimeout((int) properties.getReadTimeout().getSeconds()).endpoint(properties.getEndpoint()) + .build(); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(RestTemplateBuilder.class) + @ConditionalOnMissingClass("zipkin2.reporter.urlconnection.URLConnectionSender") + Sender restTemplateSender(ZipkinProperties properties, RestTemplateBuilder restTemplateBuilder) { + RestTemplate restTemplate = restTemplateBuilder.setConnectTimeout(properties.getConnectTimeout()) + .setReadTimeout(properties.getReadTimeout()).build(); + return new ZipkinRestTemplateSender(properties.getEndpoint(), restTemplate); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ReporterConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(Sender.class) + Reporter spanReporter(Sender sender, BytesEncoder encoder) { + return AsyncReporter.builder(sender).build(encoder); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(ZipkinSpanHandler.class) + static class BraveConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(Reporter.class) + SpanHandler zipkinSpanHandler(Reporter spanReporter) { + return ZipkinSpanHandler.newBuilder(spanReporter).build(); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(ZipkinSpanExporter.class) + static class OpenTelemetryConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(Sender.class) + ZipkinSpanExporter zipkinSpanExporter(BytesEncoder encoder, Sender sender) { + return ZipkinSpanExporter.builder().setEncoder(encoder).setSender(sender).build(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinProperties.java new file mode 100644 index 000000000000..088ae8ee42b1 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinProperties.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; + +import java.time.Duration; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for {@link ZipkinAutoConfiguration}. + * + * @author Moritz Halbritter + * @since 3.0.0 + */ +@ConfigurationProperties("management.zipkin.tracing") +public class ZipkinProperties { + + /** + * URL to the Zipkin API. + */ + private String endpoint = "http://localhost:9411/api/v2/spans"; + + /** + * Connection timeout for requests to Zipkin. + */ + private Duration connectTimeout = Duration.ofSeconds(1); + + /** + * Read timeout for requests to Zipkin. + */ + private Duration readTimeout = Duration.ofSeconds(10); + + public String getEndpoint() { + return this.endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public Duration getConnectTimeout() { + return this.connectTimeout; + } + + public void setConnectTimeout(Duration connectTimeout) { + this.connectTimeout = connectTimeout; + } + + public Duration getReadTimeout() { + return this.readTimeout; + } + + public void setReadTimeout(Duration readTimeout) { + this.readTimeout = readTimeout; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateSender.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateSender.java new file mode 100644 index 000000000000..c3fea73f761f --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateSender.java @@ -0,0 +1,170 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.zip.GZIPOutputStream; + +import zipkin2.Call; +import zipkin2.Callback; +import zipkin2.CheckResult; +import zipkin2.codec.Encoding; +import zipkin2.reporter.BytesMessageEncoder; +import zipkin2.reporter.ClosedSenderException; +import zipkin2.reporter.Sender; + +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.util.unit.DataSize; +import org.springframework.web.client.RestTemplate; + +/** + * A Zipkin {@link Sender} which uses {@link RestTemplate} for HTTP communication. + * Supports automatic compression with gzip. + * + * @author Moritz Halbritter + */ +class ZipkinRestTemplateSender extends Sender { + + private static final DataSize MESSAGE_MAX_BYTES = DataSize.ofKilobytes(512); + + private final String endpoint; + + private final RestTemplate restTemplate; + + private volatile boolean closed; + + ZipkinRestTemplateSender(String endpoint, RestTemplate restTemplate) { + this.endpoint = endpoint; + this.restTemplate = restTemplate; + } + + @Override + public Encoding encoding() { + return Encoding.JSON; + } + + @Override + public int messageMaxBytes() { + return (int) MESSAGE_MAX_BYTES.toBytes(); + } + + @Override + public int messageSizeInBytes(List encodedSpans) { + return encoding().listSizeInBytes(encodedSpans); + } + + @Override + public int messageSizeInBytes(int encodedSizeInBytes) { + return encoding().listSizeInBytes(encodedSizeInBytes); + } + + @Override + public Call sendSpans(List encodedSpans) { + if (this.closed) { + throw new ClosedSenderException(); + } + return new HttpCall(this.endpoint, BytesMessageEncoder.JSON.encode(encodedSpans), this.restTemplate); + } + + @Override + public CheckResult check() { + try { + sendSpans(List.of()).execute(); + return CheckResult.OK; + } + catch (IOException | RuntimeException ex) { + return CheckResult.failed(ex); + } + } + + @Override + public void close() throws IOException { + this.closed = true; + } + + private static class HttpCall extends Call.Base { + + /** + * Only use gzip compression on data which is bigger than this in bytes. + */ + private static final DataSize COMPRESSION_THRESHOLD = DataSize.ofKilobytes(1); + + private final String endpoint; + + private final byte[] body; + + private final RestTemplate restTemplate; + + HttpCall(String endpoint, byte[] body, RestTemplate restTemplate) { + this.endpoint = endpoint; + this.body = body; + this.restTemplate = restTemplate; + } + + @Override + protected Void doExecute() throws IOException { + HttpHeaders headers = new HttpHeaders(); + headers.set("b3", "0"); + headers.set("Content-Type", "application/json"); + byte[] body; + if (needsCompression(this.body)) { + headers.set("Content-Encoding", "gzip"); + body = compress(this.body); + } + else { + body = this.body; + } + HttpEntity request = new HttpEntity<>(body, headers); + this.restTemplate.exchange(this.endpoint, HttpMethod.POST, request, Void.class); + return null; + } + + private boolean needsCompression(byte[] body) { + return body.length > COMPRESSION_THRESHOLD.toBytes(); + } + + @Override + protected void doEnqueue(Callback callback) { + try { + doExecute(); + callback.onSuccess(null); + } + catch (IOException | RuntimeException ex) { + callback.onError(ex); + } + } + + @Override + public Call clone() { + return new HttpCall(this.endpoint, this.body, this.restTemplate); + } + + private byte[] compress(byte[] input) throws IOException { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + try (GZIPOutputStream gzip = new GZIPOutputStream(result)) { + gzip.write(input); + } + return result.toByteArray(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/package-info.java new file mode 100644 index 000000000000..10dc7a7e8663 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for tracing with Zipkin. + */ +package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontAutoConfiguration.java new file mode 100644 index 000000000000..e6e044f71fc5 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontAutoConfiguration.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.wavefront; + +import java.time.Duration; + +import com.wavefront.sdk.common.WavefrontSender; +import com.wavefront.sdk.common.clients.WavefrontClient.Builder; + +import org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront.WavefrontMetricsExportAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.wavefront.WavefrontTracingAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.util.unit.DataSize; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Wavefront common infrastructure. + * Metrics are auto-configured in {@link WavefrontMetricsExportAutoConfiguration}, tracing + * is auto-configured in {@link WavefrontTracingAutoConfiguration}. + * + * @author Moritz Halbritter + * @since 3.0.0 + */ +@AutoConfiguration +@ConditionalOnClass(WavefrontSender.class) +@EnableConfigurationProperties(WavefrontProperties.class) +public class WavefrontAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public WavefrontSender wavefrontSender(WavefrontProperties properties) { + Builder builder = new Builder(properties.getEffectiveUri().toString(), properties.getApiTokenOrThrow()); + PropertyMapper mapper = PropertyMapper.get().alwaysApplyingWhenNonNull(); + WavefrontProperties.Sender sender = properties.getSender(); + mapper.from(sender.getMaxQueueSize()).to(builder::maxQueueSize); + mapper.from(sender.getFlushInterval()).asInt(Duration::getSeconds).to(builder::flushIntervalSeconds); + mapper.from(sender.getMessageSize()).asInt(DataSize::toBytes).to(builder::messageSizeBytes); + mapper.from(sender.getBatchSize()).to(builder::batchSize); + return builder.build(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontProperties.java new file mode 100644 index 000000000000..33576b4af9ea --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontProperties.java @@ -0,0 +1,290 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.wavefront; + +import java.net.InetAddress; +import java.net.URI; +import java.net.UnknownHostException; +import java.time.Duration; + +import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.PushRegistryProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; +import org.springframework.util.unit.DataSize; + +/** + * Configuration properties to configure Wavefront. + * + * @author Moritz Halbritter + * @since 3.0.0 + */ +@ConfigurationProperties(prefix = "management.wavefront") +public class WavefrontProperties { + + /** + * URI to ship metrics and traces to. + */ + private URI uri = URI.create("https://longboard.wavefront.com"); + + /** + * Unique identifier for the app instance that is the source of metrics being + * published to Wavefront. Defaults to the local host name. + */ + private String source; + + /** + * API token used when publishing metrics directly to the Wavefront API host. + */ + private String apiToken; + + /** + * Sender configuration. + */ + private final Sender sender = new Sender(); + + /** + * Metrics configuration. + */ + private final Metrics metrics = new Metrics(); + + /** + * Tracing configuration. + */ + private final Tracing tracing = new Tracing(); + + public Sender getSender() { + return this.sender; + } + + public Metrics getMetrics() { + return this.metrics; + } + + public Tracing getTracing() { + return this.tracing; + } + + public URI getUri() { + return this.uri; + } + + public void setUri(URI uri) { + this.uri = uri; + } + + public String getSource() { + return this.source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getApiToken() { + return this.apiToken; + } + + public void setApiToken(String apiToken) { + this.apiToken = apiToken; + } + + /** + * Returns the effective URI of the wavefront instance. This will not be the same URI + * given through {@link #setUri(URI)} when a proxy is used. + * @return the effective URI of the wavefront instance + */ + public URI getEffectiveUri() { + if (usesProxy()) { + // See io.micrometer.wavefront.WavefrontMeterRegistry.getWavefrontReportingUri + return URI.create(this.uri.toString().replace("proxy://", "http://")); + } + return this.uri; + } + + /** + * Returns the API token or throws an exception if the API token is mandatory. If a + * proxy is used, the API token is optional. + * @return the API token + */ + public String getApiTokenOrThrow() { + if (this.apiToken == null && !usesProxy()) { + throw new InvalidConfigurationPropertyValueException("management.wavefront.api-token", null, + "This property is mandatory whenever publishing directly to the Wavefront API"); + } + return this.apiToken; + } + + public String getSourceOrDefault() { + if (this.source != null) { + return this.source; + } + return getSourceDefault(); + } + + private String getSourceDefault() { + try { + return InetAddress.getLocalHost().getHostName(); + } + catch (UnknownHostException ex) { + return "unknown"; + } + } + + private boolean usesProxy() { + return "proxy".equals(this.uri.getScheme()); + } + + public static class Sender { + + /** + * Maximum size of queued messages. + */ + private int maxQueueSize = 50000; + + /** + * Flush interval to send queued messages. + */ + private Duration flushInterval = Duration.ofSeconds(1); + + /** + * Maximum size of a message. + */ + private DataSize messageSize = DataSize.ofBytes(Integer.MAX_VALUE); + + /** + * Number of measurements per request to use for Wavefront. If more measurements + * are found, then multiple requests will be made. + */ + private int batchSize = 10000; + + public int getMaxQueueSize() { + return this.maxQueueSize; + } + + public void setMaxQueueSize(int maxQueueSize) { + this.maxQueueSize = maxQueueSize; + } + + public Duration getFlushInterval() { + return this.flushInterval; + } + + public void setFlushInterval(Duration flushInterval) { + this.flushInterval = flushInterval; + } + + public DataSize getMessageSize() { + return this.messageSize; + } + + public void setMessageSize(DataSize messageSize) { + this.messageSize = messageSize; + } + + public int getBatchSize() { + return this.batchSize; + } + + public void setBatchSize(int batchSize) { + this.batchSize = batchSize; + } + + } + + public static class Metrics { + + /** + * Export configuration. + */ + private Export export = new Export(); + + public Export getExport() { + return this.export; + } + + public void setExport(Export export) { + this.export = export; + } + + public static class Export extends PushRegistryProperties { + + /** + * Global prefix to separate metrics originating from this app's + * instrumentation from those originating from other Wavefront integrations + * when viewed in the Wavefront UI. + */ + private String globalPrefix; + + public String getGlobalPrefix() { + return this.globalPrefix; + } + + public void setGlobalPrefix(String globalPrefix) { + this.globalPrefix = globalPrefix; + } + + /** + * See {@link PushRegistryProperties#getBatchSize()}. + */ + @Override + public Integer getBatchSize() { + throw new UnsupportedOperationException("Use Sender.getBatchSize() instead"); + } + + /** + * See {@link PushRegistryProperties#setBatchSize(Integer)}. + */ + @Override + public void setBatchSize(Integer batchSize) { + throw new UnsupportedOperationException("Use Sender.setBatchSize(int) instead"); + } + + } + + } + + public static class Tracing { + + /** + * Application name. Defaults to 'spring.application.name'. + */ + private String applicationName; + + /** + * Service name. Defaults to 'spring.application.name'. + */ + private String serviceName; + + public String getServiceName() { + return this.serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getApplicationName() { + return this.applicationName; + } + + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/package-info.java new file mode 100644 index 000000000000..213eb6cccf64 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2022 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. + */ + +/** + * Classes shared between Wavefront tracing and metrics. + */ +package org.springframework.boot.actuate.autoconfigure.wavefront; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index ce048422e5d4..316290f82b82 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -1832,7 +1832,7 @@ "type": "java.lang.String", "deprecation": { "level": "error", - "replacement": "management.wavefront.metrics.export.api-token" + "replacement": "management.wavefront.api-token" } }, { @@ -1840,7 +1840,7 @@ "type": "java.lang.Integer", "deprecation": { "level": "error", - "replacement": "management.wavefront.metrics.export.batch-size" + "replacement": "management.wavefront.sender.batch-size" } }, { @@ -1885,7 +1885,7 @@ "type": "java.time.Duration", "deprecation": { "level": "error", - "replacement": "management.wavefront.metrics.export.sender.flush-interval" + "replacement": "management.wavefront.sender.flush-interval" } }, { @@ -1893,7 +1893,7 @@ "type": "java.lang.Integer", "deprecation": { "level": "error", - "replacement": "management.wavefront.metrics.export.sender.max-queue-size" + "replacement": "management.wavefront.sender.max-queue-size" } }, { @@ -1901,7 +1901,7 @@ "type": "org.springframework.util.unit.DataSize", "deprecation": { "level": "error", - "replacement": "management.wavefront.metrics.export.sender.message-size" + "replacement": "management.wavefront.sender.message-size" } }, { @@ -1909,7 +1909,7 @@ "type": "java.lang.String", "deprecation": { "level": "error", - "replacement": "management.wavefront.metrics.export.source" + "replacement": "management.wavefront.source" } }, { @@ -1925,7 +1925,7 @@ "type": "java.net.URI", "deprecation": { "level": "error", - "replacement": "management.wavefront.metrics.export.uri" + "replacement": "management.wavefront.uri" } }, { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 0ba704f63db8..cb999708e2c5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -95,6 +95,12 @@ org.springframework.boot.actuate.autoconfigure.startup.StartupEndpointAutoConfig org.springframework.boot.actuate.autoconfigure.system.DiskSpaceHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceAutoConfiguration org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceEndpointAutoConfiguration +org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfiguration +org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration +org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration +org.springframework.boot.actuate.autoconfigure.tracing.wavefront.WavefrontTracingAutoConfiguration +org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinAutoConfiguration +org.springframework.boot.actuate.autoconfigure.wavefront.WavefrontAutoConfiguration org.springframework.boot.actuate.autoconfigure.web.mappings.MappingsEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.web.reactive.ReactiveManagementContextAutoConfiguration org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/SpringApplicationHierarchyTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/SpringApplicationHierarchyTests.java index fb5ec4725693..9996e2debea0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/SpringApplicationHierarchyTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/SpringApplicationHierarchyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -20,6 +20,8 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.wavefront.WavefrontAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; import org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration; @@ -69,10 +71,11 @@ void testChild() { @Configuration @EnableAutoConfiguration(exclude = { ElasticsearchDataAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class, CassandraAutoConfiguration.class, - CassandraDataAutoConfiguration.class, MongoDataAutoConfiguration.class, - MongoReactiveDataAutoConfiguration.class, Neo4jAutoConfiguration.class, Neo4jDataAutoConfiguration.class, - Neo4jRepositoriesAutoConfiguration.class, RedisAutoConfiguration.class, - RedisRepositoriesAutoConfiguration.class, FlywayAutoConfiguration.class, MetricsAutoConfiguration.class }) + CassandraDataAutoConfiguration.class, MicrometerTracingAutoConfiguration.class, + MongoDataAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class, Neo4jAutoConfiguration.class, + Neo4jDataAutoConfiguration.class, Neo4jRepositoriesAutoConfiguration.class, RedisAutoConfiguration.class, + RedisRepositoriesAutoConfiguration.class, FlywayAutoConfiguration.class, MetricsAutoConfiguration.class, + WavefrontAutoConfiguration.class }) static class Parent { } @@ -80,10 +83,11 @@ static class Parent { @Configuration @EnableAutoConfiguration(exclude = { ElasticsearchDataAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class, CassandraAutoConfiguration.class, - CassandraDataAutoConfiguration.class, MongoDataAutoConfiguration.class, - MongoReactiveDataAutoConfiguration.class, Neo4jAutoConfiguration.class, Neo4jDataAutoConfiguration.class, - Neo4jRepositoriesAutoConfiguration.class, RedisAutoConfiguration.class, - RedisRepositoriesAutoConfiguration.class, FlywayAutoConfiguration.class, MetricsAutoConfiguration.class }) + CassandraDataAutoConfiguration.class, MicrometerTracingAutoConfiguration.class, + MongoDataAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class, Neo4jAutoConfiguration.class, + Neo4jDataAutoConfiguration.class, Neo4jRepositoriesAutoConfiguration.class, RedisAutoConfiguration.class, + RedisRepositoriesAutoConfiguration.class, FlywayAutoConfiguration.class, MetricsAutoConfiguration.class, + WavefrontAutoConfiguration.class }) static class Child { } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebEndpointsAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebEndpointsAutoConfigurationIntegrationTests.java index c25193341f45..c945ccc5769d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebEndpointsAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebEndpointsAutoConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -20,6 +20,9 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.wavefront.WavefrontAutoConfiguration; import org.springframework.boot.actuate.health.HealthEndpointWebExtension; import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -80,7 +83,8 @@ private ReactiveWebApplicationContextRunner reactiveWebRunner() { MongoReactiveAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class, RepositoryRestMvcAutoConfiguration.class, HazelcastAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class, SolrAutoConfiguration.class, RedisAutoConfiguration.class, - RedisRepositoriesAutoConfiguration.class, MetricsAutoConfiguration.class }) + RedisRepositoriesAutoConfiguration.class, MetricsAutoConfiguration.class, WavefrontAutoConfiguration.class, + BraveAutoConfiguration.class, OpenTelemetryAutoConfiguration.class }) @SpringBootConfiguration static class WebEndpointTestApplication { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/properties/PushRegistryPropertiesConfigAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/properties/PushRegistryPropertiesConfigAdapterTests.java index 29b2a1ec5ca3..94c604a3f1d9 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/properties/PushRegistryPropertiesConfigAdapterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/properties/PushRegistryPropertiesConfigAdapterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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. @@ -51,7 +51,7 @@ void whenPropertiesEnabledIsSetAdapterEnabledReturnsIt() { } @Test - void whenPropertiesBatchSizeIsSetAdapterBatchSizeReturnsIt() { + protected void whenPropertiesBatchSizeIsSetAdapterBatchSizeReturnsIt() { P properties = createProperties(); properties.setBatchSize(10042); assertThat(createConfigAdapter(properties).batchSize()).isEqualTo(10042); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontMetricsExportAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontMetricsExportAutoConfigurationTests.java index f00a07d514dd..0a300f5b42c8 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontMetricsExportAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontMetricsExportAutoConfigurationTests.java @@ -16,14 +16,12 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront; -import java.util.concurrent.LinkedBlockingQueue; - import com.wavefront.sdk.common.WavefrontSender; import io.micrometer.core.instrument.Clock; import io.micrometer.wavefront.WavefrontConfig; import io.micrometer.wavefront.WavefrontMeterRegistry; -import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -31,7 +29,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -51,28 +48,22 @@ void backsOffWithoutAClock() { this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(WavefrontMeterRegistry.class)); } - @Test - void failsWithoutAnApiTokenWhenPublishingDirectly() { - this.contextRunner.withUserConfiguration(BaseConfiguration.class) - .run((context) -> assertThat(context).hasFailed()); - } - @Test void autoConfigurationCanBeDisabledWithDefaultsEnabledProperty() { this.contextRunner.withUserConfiguration(BaseConfiguration.class) - .withPropertyValues("management.wavefront.metrics.export.api-token=abcde", + .withPropertyValues("management.wavefront.api-token=abcde", "management.defaults.metrics.export.enabled=false") .run((context) -> assertThat(context).doesNotHaveBean(WavefrontMeterRegistry.class) - .doesNotHaveBean(WavefrontConfig.class).doesNotHaveBean(WavefrontSender.class)); + .doesNotHaveBean(WavefrontConfig.class)); } @Test void autoConfigurationCanBeDisabledWithSpecificEnabledProperty() { this.contextRunner.withUserConfiguration(BaseConfiguration.class) - .withPropertyValues("management.wavefront.metrics.export.api-token=abcde", + .withPropertyValues("management.wavefront.api-token=abcde", "management.wavefront.metrics.export.enabled=false") .run((context) -> assertThat(context).doesNotHaveBean(WavefrontMeterRegistry.class) - .doesNotHaveBean(WavefrontConfig.class).doesNotHaveBean(WavefrontSender.class)); + .doesNotHaveBean(WavefrontConfig.class)); } @Test @@ -83,51 +74,10 @@ void allowsConfigToBeCustomized() { .hasSingleBean(WavefrontSender.class).hasBean("customConfig")); } - @Test - void defaultWavefrontSenderSettingsAreConsistent() { - this.contextRunner.withUserConfiguration(BaseConfiguration.class) - .withPropertyValues("management.wavefront.metrics.export.api-token=abcde").run((context) -> { - WavefrontProperties properties = new WavefrontProperties(); - WavefrontSender sender = context.getBean(WavefrontSender.class); - assertThat(sender) - .extracting("metricsBuffer", as(InstanceOfAssertFactories.type(LinkedBlockingQueue.class))) - .satisfies((queue) -> assertThat(queue.remainingCapacity() + queue.size()) - .isEqualTo(properties.getSender().getMaxQueueSize())); - assertThat(sender).hasFieldOrPropertyWithValue("batchSize", properties.getBatchSize()); - assertThat(sender).hasFieldOrPropertyWithValue("messageSizeBytes", - (int) properties.getSender().getMessageSize().toBytes()); - }); - } - - @Test - void configureWavefrontSender() { - this.contextRunner.withUserConfiguration(BaseConfiguration.class) - .withPropertyValues("management.wavefront.metrics.export.api-token=abcde", - "management.wavefront.metrics.export.batch-size=50", - "management.wavefront.metrics.export.sender.max-queue-size=100", - "management.wavefront.metrics.export.sender.message-size=1KB") - .run((context) -> { - WavefrontSender sender = context.getBean(WavefrontSender.class); - assertThat(sender).hasFieldOrPropertyWithValue("batchSize", 50); - assertThat(sender) - .extracting("metricsBuffer", as(InstanceOfAssertFactories.type(LinkedBlockingQueue.class))) - .satisfies((queue) -> assertThat(queue.remainingCapacity() + queue.size()).isEqualTo(100)); - assertThat(sender).hasFieldOrPropertyWithValue("messageSizeBytes", 1024); - }); - } - - @Test - void allowsWavefrontSenderToBeCustomized() { - this.contextRunner.withUserConfiguration(CustomSenderConfiguration.class) - .run((context) -> assertThat(context).hasSingleBean(Clock.class) - .hasSingleBean(WavefrontMeterRegistry.class).hasSingleBean(WavefrontConfig.class) - .hasSingleBean(WavefrontSender.class).hasBean("customSender")); - } - @Test void allowsRegistryToBeCustomized() { this.contextRunner.withUserConfiguration(CustomRegistryConfiguration.class) - .withPropertyValues("management.wavefront.metrics.export.api-token=abcde") + .withPropertyValues("management.wavefront.api-token=abcde") .run((context) -> assertThat(context).hasSingleBean(Clock.class).hasSingleBean(WavefrontConfig.class) .hasSingleBean(WavefrontMeterRegistry.class).hasBean("customRegistry")); } @@ -135,7 +85,7 @@ void allowsRegistryToBeCustomized() { @Test void stopsMeterRegistryWhenContextIsClosed() { this.contextRunner.withUserConfiguration(BaseConfiguration.class) - .withPropertyValues("management.wavefront.metrics.export.api-token=abcde").run((context) -> { + .withPropertyValues("management.wavefront.api-token=abcde").run((context) -> { WavefrontMeterRegistry registry = context.getBean(WavefrontMeterRegistry.class); assertThat(registry.isClosed()).isFalse(); context.close(); @@ -146,6 +96,11 @@ void stopsMeterRegistryWhenContextIsClosed() { @Configuration(proxyBeanMethods = false) static class BaseConfiguration { + @Bean + WavefrontSender customWavefrontSender() { + return Mockito.mock(WavefrontSender.class); + } + @Bean Clock clock() { return Clock.SYSTEM; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapterTests.java index 82a5b6d31d54..145765b78358 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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. @@ -16,11 +16,11 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront; -import java.net.URI; - import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.PushRegistryPropertiesConfigAdapterTests; +import org.springframework.boot.actuate.autoconfigure.wavefront.WavefrontProperties; +import org.springframework.boot.actuate.autoconfigure.wavefront.WavefrontProperties.Metrics.Export; import static org.assertj.core.api.Assertions.assertThat; @@ -28,46 +28,35 @@ * Tests for {@link WavefrontPropertiesConfigAdapter}. * * @author Stephane Nicoll + * @author Moritz Halbritter */ -class WavefrontPropertiesConfigAdapterTests - extends PushRegistryPropertiesConfigAdapterTests { +class WavefrontPropertiesConfigAdapterTests extends + PushRegistryPropertiesConfigAdapterTests { @Override - protected WavefrontProperties createProperties() { - return new WavefrontProperties(); + protected WavefrontProperties.Metrics.Export createProperties() { + return new WavefrontProperties.Metrics.Export(); } @Override - protected WavefrontPropertiesConfigAdapter createConfigAdapter(WavefrontProperties properties) { + protected WavefrontPropertiesConfigAdapter createConfigAdapter(WavefrontProperties.Metrics.Export export) { + WavefrontProperties properties = new WavefrontProperties(); + properties.getMetrics().setExport(export); return new WavefrontPropertiesConfigAdapter(properties); } - @Test - void whenPropertiesUriIsSetAdapterUriReturnsIt() { - WavefrontProperties properties = createProperties(); - properties.setUri(URI.create("https://wavefront.example.com")); - assertThat(createConfigAdapter(properties).uri()).isEqualTo("https://wavefront.example.com"); - } - - @Test - void whenPropertiesSourceIsSetAdapterSourceReturnsIt() { - WavefrontProperties properties = createProperties(); - properties.setSource("test"); - assertThat(createConfigAdapter(properties).source()).isEqualTo("test"); - } - - @Test - void whenPropertiesApiTokenIsSetAdapterApiTokenReturnsIt() { - WavefrontProperties properties = createProperties(); - properties.setApiToken("ABC123"); - assertThat(createConfigAdapter(properties).apiToken()).isEqualTo("ABC123"); - } - @Test void whenPropertiesGlobalPrefixIsSetAdapterGlobalPrefixReturnsIt() { - WavefrontProperties properties = createProperties(); + Export properties = createProperties(); properties.setGlobalPrefix("test"); assertThat(createConfigAdapter(properties).globalPrefix()).isEqualTo("test"); } + @Override + protected void whenPropertiesBatchSizeIsSetAdapterBatchSizeReturnsIt() { + WavefrontProperties properties = new WavefrontProperties(); + properties.getSender().setBatchSize(10042); + assertThat(createConfigAdapter(properties.getMetrics().getExport()).batchSize()).isEqualTo(10042); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java new file mode 100644 index 000000000000..e0cd2e147053 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java @@ -0,0 +1,156 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import brave.Tracer; +import brave.Tracing; +import brave.propagation.CurrentTraceContext; +import brave.propagation.Propagation.Factory; +import brave.sampler.Sampler; +import io.micrometer.tracing.brave.bridge.BraveBaggageManager; +import io.micrometer.tracing.brave.bridge.BraveTracer; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link BraveAutoConfiguration}. + * + * @author Moritz Halbritter + */ +class BraveAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(BraveAutoConfiguration.class)); + + @Test + void shouldSupplyBraveBeans() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(Tracing.class); + assertThat(context).hasSingleBean(Tracer.class); + assertThat(context).hasSingleBean(CurrentTraceContext.class); + assertThat(context).hasSingleBean(Factory.class); + assertThat(context).hasSingleBean(Sampler.class); + }); + } + + @Test + void shouldBackOffOnCustomBraveBeans() { + this.contextRunner.withUserConfiguration(CustomBraveConfiguration.class).run((context) -> { + assertThat(context).hasBean("customTracing"); + assertThat(context).hasSingleBean(Tracing.class); + assertThat(context).hasBean("customTracer"); + assertThat(context).hasSingleBean(Tracer.class); + assertThat(context).hasBean("customCurrentTraceContext"); + assertThat(context).hasSingleBean(CurrentTraceContext.class); + assertThat(context).hasBean("customFactory"); + assertThat(context).hasSingleBean(Factory.class); + assertThat(context).hasBean("customSampler"); + assertThat(context).hasSingleBean(Sampler.class); + }); + } + + @Test + void shouldSupplyMicrometerBeans() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(BraveTracer.class); + assertThat(context).hasSingleBean(BraveBaggageManager.class); + }); + } + + @Test + void shouldBackOffOnCustomMicrometerBraveBeans() { + this.contextRunner.withUserConfiguration(CustomMicrometerBraveConfiguration.class).run((context) -> { + assertThat(context).hasBean("customBraveTracer"); + assertThat(context).hasSingleBean(BraveTracer.class); + assertThat(context).hasBean("customBraveBaggageManager"); + assertThat(context).hasSingleBean(BraveBaggageManager.class); + }); + } + + @Test + void shouldNotSupplyBraveBeansIfBraveIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("brave")).run((context) -> { + assertThat(context).doesNotHaveBean(Tracing.class); + assertThat(context).doesNotHaveBean(Tracer.class); + assertThat(context).doesNotHaveBean(CurrentTraceContext.class); + assertThat(context).doesNotHaveBean(Factory.class); + assertThat(context).doesNotHaveBean(Sampler.class); + }); + } + + @Test + void shouldNotSupplyMicrometerBeansIfMicrometerIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer")).run((context) -> { + assertThat(context).doesNotHaveBean(BraveTracer.class); + assertThat(context).doesNotHaveBean(BraveBaggageManager.class); + }); + } + + @Configuration(proxyBeanMethods = false) + private static class CustomBraveConfiguration { + + @Bean + Tracing customTracing() { + return Mockito.mock(Tracing.class); + } + + @Bean + Tracer customTracer() { + return Mockito.mock(Tracer.class); + } + + @Bean + CurrentTraceContext customCurrentTraceContext() { + return Mockito.mock(CurrentTraceContext.class); + } + + @Bean + Factory customFactory() { + return Mockito.mock(Factory.class); + } + + @Bean + Sampler customSampler() { + return Mockito.mock(Sampler.class); + } + + } + + @Configuration(proxyBeanMethods = false) + private static class CustomMicrometerBraveConfiguration { + + @Bean + BraveTracer customBraveTracer() { + return Mockito.mock(BraveTracer.class); + } + + @Bean + BraveBaggageManager customBraveBaggageManager() { + return Mockito.mock(BraveBaggageManager.class); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java new file mode 100644 index 000000000000..97e602e6bf0c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.handler.DefaultTracingObservationHandler; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link MicrometerTracingAutoConfiguration}. + * + * @author Moritz Halbritter + */ +class MicrometerTracingAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(MicrometerTracingAutoConfiguration.class)); + + @Test + void shouldSupplyBeans() { + this.contextRunner.withUserConfiguration(TracerConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(DefaultTracingObservationHandler.class)); + } + + @Test + void shouldBackOffOnCustomBeans() { + this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { + assertThat(context).hasBean("customDefaultTracingObservationHandler"); + assertThat(context).hasSingleBean(DefaultTracingObservationHandler.class); + }); + } + + @Test + void shouldNotSupplyBeansIfMicrometerIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer")) + .run((context) -> assertThat(context).doesNotHaveBean(DefaultTracingObservationHandler.class)); + } + + @Test + void shouldNotSupplyDefaultTracingObservationHandlerIfTracerIsMissing() { + this.contextRunner + .run((context) -> assertThat(context).doesNotHaveBean(DefaultTracingObservationHandler.class)); + } + + @Configuration(proxyBeanMethods = false) + private static class TracerConfiguration { + + @Bean + Tracer tracer() { + return Mockito.mock(Tracer.class); + } + + } + + @Configuration(proxyBeanMethods = false) + private static class CustomConfiguration { + + @Bean + DefaultTracingObservationHandler customDefaultTracingObservationHandler() { + return Mockito.mock(DefaultTracingObservationHandler.class); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryConfigurationsMicrometerConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryConfigurationsMicrometerConfigurationTests.java new file mode 100644 index 000000000000..6204e462d785 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryConfigurationsMicrometerConfigurationTests.java @@ -0,0 +1,111 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; +import io.micrometer.tracing.otel.bridge.OtelTracer; +import io.micrometer.tracing.otel.bridge.OtelTracer.EventPublisher; +import io.opentelemetry.api.trace.Tracer; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryConfigurations.MicrometerConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link MicrometerConfiguration}. + * + * @author Moritz Halbritter + */ +class OpenTelemetryConfigurationsMicrometerConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(MicrometerConfiguration.class)); + + @Test + void shouldSupplyBeans() { + this.contextRunner.withUserConfiguration(TracerConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(OtelTracer.class); + assertThat(context).hasSingleBean(EventPublisher.class); + assertThat(context).hasSingleBean(OtelCurrentTraceContext.class); + }); + } + + @Test + void shouldNotSupplyBeansIfMicrometerTracingBridgeOtelIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer.tracing.otel")) + .withUserConfiguration(TracerConfiguration.class).run((context) -> { + assertThat(context).doesNotHaveBean(OtelTracer.class); + assertThat(context).doesNotHaveBean(EventPublisher.class); + assertThat(context).doesNotHaveBean(OtelCurrentTraceContext.class); + }); + } + + @Test + void shouldNotSupplyOtelTracerIfTracerIsMissing() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(OtelTracer.class)); + } + + @Test + void shouldBackOffOnCustomBeans() { + this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { + assertThat(context).hasBean("customOtelTracer"); + assertThat(context).hasSingleBean(OtelTracer.class); + assertThat(context).hasBean("customEventPublisher"); + assertThat(context).hasSingleBean(EventPublisher.class); + assertThat(context).hasBean("customOtelCurrentTraceContext"); + assertThat(context).hasSingleBean(OtelCurrentTraceContext.class); + }); + } + + @Configuration(proxyBeanMethods = false) + private static class CustomConfiguration { + + @Bean + OtelTracer customOtelTracer() { + return Mockito.mock(OtelTracer.class); + } + + @Bean + EventPublisher customEventPublisher() { + return Mockito.mock(EventPublisher.class); + } + + @Bean + OtelCurrentTraceContext customOtelCurrentTraceContext() { + return Mockito.mock(OtelCurrentTraceContext.class); + } + + } + + @Configuration(proxyBeanMethods = false) + private static class TracerConfiguration { + + @Bean + Tracer tracer() { + return Mockito.mock(Tracer.class); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryConfigurationsSdkConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryConfigurationsSdkConfigurationTests.java new file mode 100644 index 000000000000..6930e77b87f7 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryConfigurationsSdkConfigurationTests.java @@ -0,0 +1,112 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.SpanProcessor; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryConfigurations.SdkConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SdkConfiguration}. + * + * @author Moritz Halbritter + */ +class OpenTelemetryConfigurationsSdkConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SdkConfiguration.class)); + + @Test + void shouldSupplyBeans() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(OpenTelemetry.class); + assertThat(context).hasSingleBean(SdkTracerProvider.class); + assertThat(context).hasSingleBean(ContextPropagators.class); + assertThat(context).hasSingleBean(Sampler.class); + assertThat(context).hasSingleBean(SpanProcessor.class); + }); + } + + @Test + void shouldBackOffOnCustomBeans() { + this.contextRunner.withUserConfiguration(CustomBeans.class).run((context) -> { + assertThat(context).hasBean("customOpenTelemetry"); + assertThat(context).hasSingleBean(OpenTelemetry.class); + assertThat(context).hasBean("customSdkTracerProvider"); + assertThat(context).hasSingleBean(SdkTracerProvider.class); + assertThat(context).hasBean("customContextPropagators"); + assertThat(context).hasSingleBean(ContextPropagators.class); + assertThat(context).hasBean("customSampler"); + assertThat(context).hasSingleBean(Sampler.class); + assertThat(context).hasBean("customSpanProcessor"); + assertThat(context).hasSingleBean(SpanProcessor.class); + }); + } + + @Test + void shouldNotSupplyBeansIfSdkIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.opentelemetry.sdk")).run((context) -> { + assertThat(context).doesNotHaveBean(OpenTelemetry.class); + assertThat(context).doesNotHaveBean(SdkTracerProvider.class); + assertThat(context).doesNotHaveBean(ContextPropagators.class); + assertThat(context).doesNotHaveBean(Sampler.class); + assertThat(context).doesNotHaveBean(SpanProcessor.class); + }); + } + + private static class CustomBeans { + + @Bean + OpenTelemetry customOpenTelemetry() { + return Mockito.mock(OpenTelemetry.class); + } + + @Bean + SdkTracerProvider customSdkTracerProvider() { + return SdkTracerProvider.builder().build(); + } + + @Bean + ContextPropagators customContextPropagators() { + return Mockito.mock(ContextPropagators.class); + } + + @Bean + Sampler customSampler() { + return Mockito.mock(Sampler.class); + } + + @Bean + SpanProcessor customSpanProcessor() { + return Mockito.mock(SpanProcessor.class); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryConfigurationsTracerConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryConfigurationsTracerConfigurationTests.java new file mode 100644 index 000000000000..2227eb89d78b --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryConfigurationsTracerConfigurationTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Tracer; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryConfigurations.TracerConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link TracerConfiguration}. + * + * @author Moritz Halbritter + */ +class OpenTelemetryConfigurationsTracerConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(TracerConfiguration.class)); + + @Test + void shouldSupplyBeans() { + this.contextRunner.withUserConfiguration(OpenTelemetryConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(Tracer.class)); + } + + @Test + void shouldNotSupplyBeansIfApiIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.opentelemetry.api")) + .run((context) -> assertThat(context).doesNotHaveBean(Tracer.class)); + } + + @Test + void shouldNotSupplyTracerIfOpenTelemetryIsMissing() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(Tracer.class)); + } + + @Test + void shouldBackOffOnCustomBeans() { + this.contextRunner.withUserConfiguration(OpenTelemetryConfiguration.class, CustomConfiguration.class) + .run((context) -> { + assertThat(context).hasBean("customTracer"); + assertThat(context).hasSingleBean(Tracer.class); + }); + + } + + @Configuration(proxyBeanMethods = false) + private static class OpenTelemetryConfiguration { + + @Bean + OpenTelemetry tracer() { + return Mockito.mock(OpenTelemetry.class); + } + + } + + @Configuration(proxyBeanMethods = false) + private static class CustomConfiguration { + + @Bean + Tracer customTracer() { + return Mockito.mock(Tracer.class); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/MeterRegistrySpanMetricsTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/MeterRegistrySpanMetricsTests.java new file mode 100644 index 000000000000..cf0f3b575ac2 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/MeterRegistrySpanMetricsTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.wavefront; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link MeterRegistrySpanMetrics}. + * + * @author Moritz Halbritter + */ +class MeterRegistrySpanMetricsTests { + + private SimpleMeterRegistry meterRegistry; + + private MeterRegistrySpanMetrics sut; + + @BeforeEach + void setUp() { + this.meterRegistry = new SimpleMeterRegistry(); + this.sut = new MeterRegistrySpanMetrics(this.meterRegistry); + } + + @Test + void reportDroppedShouldIncreaseCounter() { + this.sut.reportDropped(); + assertThat(getCounterValue("wavefront.reporter.spans.dropped")).isEqualTo(1); + this.sut.reportDropped(); + assertThat(getCounterValue("wavefront.reporter.spans.dropped")).isEqualTo(2); + } + + @Test + void reportReceivedShouldIncreaseCounter() { + this.sut.reportReceived(); + assertThat(getCounterValue("wavefront.reporter.spans.received")).isEqualTo(1); + this.sut.reportReceived(); + assertThat(getCounterValue("wavefront.reporter.spans.received")).isEqualTo(2); + } + + @Test + void reportErrorsShouldIncreaseCounter() { + this.sut.reportErrors(); + assertThat(getCounterValue("wavefront.reporter.errors")).isEqualTo(1); + this.sut.reportErrors(); + assertThat(getCounterValue("wavefront.reporter.errors")).isEqualTo(2); + } + + @Test + void registerQueueSizeShouldCreateGauge() { + BlockingQueue queue = new ArrayBlockingQueue(2); + this.sut.registerQueueSize(queue); + assertThat(getGaugeValue("wavefront.reporter.queue.size")).isEqualTo(0); + queue.offer(1); + assertThat(getGaugeValue("wavefront.reporter.queue.size")).isEqualTo(1); + } + + @Test + void registerQueueRemainingCapacityShouldCreateGauge() { + BlockingQueue queue = new ArrayBlockingQueue(2); + this.sut.registerQueueRemainingCapacity(queue); + assertThat(getGaugeValue("wavefront.reporter.queue.remaining_capacity")).isEqualTo(2); + queue.offer(1); + assertThat(getGaugeValue("wavefront.reporter.queue.remaining_capacity")).isEqualTo(1); + } + + private double getGaugeValue(String name) { + Gauge gauge = this.meterRegistry.find(name).gauge(); + assertThat(gauge).withFailMessage("Gauge '%s' not found", name).isNotNull(); + return gauge.value(); + } + + private double getCounterValue(String name) { + Counter counter = this.meterRegistry.find(name).counter(); + assertThat(counter).withFailMessage("Counter '%s' not found", name).isNotNull(); + return counter.count(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfigurationTests.java new file mode 100644 index 000000000000..5479337330b9 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfigurationTests.java @@ -0,0 +1,192 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.wavefront; + +import com.wavefront.sdk.common.WavefrontSender; +import com.wavefront.sdk.common.application.ApplicationTags; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.tracing.reporter.wavefront.SpanMetrics; +import io.micrometer.tracing.reporter.wavefront.WavefrontBraveSpanHandler; +import io.micrometer.tracing.reporter.wavefront.WavefrontOtelSpanHandler; +import io.micrometer.tracing.reporter.wavefront.WavefrontSpanHandler; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link WavefrontTracingAutoConfiguration}. + * + * @author Moritz Halbritter + */ +class WavefrontTracingAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(WavefrontTracingAutoConfiguration.class)); + + @Test + void shouldSupplyBeans() { + this.contextRunner.withUserConfiguration(WavefrontSenderConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(ApplicationTags.class); + assertThat(context).hasSingleBean(WavefrontSpanHandler.class); + assertThat(context).hasSingleBean(SpanMetrics.class); + assertThat(context).hasSingleBean(WavefrontBraveSpanHandler.class); + assertThat(context).hasSingleBean(WavefrontOtelSpanHandler.class); + }); + } + + @Test + void shouldNotSupplyBeansIfWavefrontSenderIsMissing() { + this.contextRunner.run((context) -> { + assertThat(context).doesNotHaveBean(ApplicationTags.class); + assertThat(context).doesNotHaveBean(WavefrontSpanHandler.class); + assertThat(context).doesNotHaveBean(SpanMetrics.class); + assertThat(context).doesNotHaveBean(WavefrontBraveSpanHandler.class); + assertThat(context).doesNotHaveBean(WavefrontOtelSpanHandler.class); + }); + } + + @Test + void shouldNotSupplyBeansIfMicrometerReporterWavefrontIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer.tracing.reporter.wavefront")) + .withUserConfiguration(WavefrontSenderConfiguration.class).run((context) -> { + assertThat(context).doesNotHaveBean(WavefrontSpanHandler.class); + assertThat(context).doesNotHaveBean(SpanMetrics.class); + assertThat(context).doesNotHaveBean(WavefrontBraveSpanHandler.class); + assertThat(context).doesNotHaveBean(WavefrontOtelSpanHandler.class); + }); + } + + @Test + void shouldSupplyMeterRegistrySpanMetricsIfMeterRegistryIsAvailable() { + this.contextRunner.withUserConfiguration(WavefrontSenderConfiguration.class, MeterRegistryConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(SpanMetrics.class); + assertThat(context).hasSingleBean(MeterRegistrySpanMetrics.class); + }); + } + + @Test + void shouldNotSupplyWavefrontBraveSpanHandlerIfBraveIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("brave")) + .withUserConfiguration(WavefrontSenderConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(WavefrontBraveSpanHandler.class)); + } + + @Test + void shouldNotSupplyWavefrontOtelSpanHandlerIfOtelIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.opentelemetry.sdk.trace")) + .withUserConfiguration(WavefrontSenderConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(WavefrontOtelSpanHandler.class)); + } + + @Test + void shouldHaveADefaultApplicationName() { + this.contextRunner.withUserConfiguration(WavefrontSenderConfiguration.class).run((context) -> { + ApplicationTags applicationTags = context.getBean(ApplicationTags.class); + assertThat(applicationTags.getApplication()).isEqualTo("application"); + }); + } + + @Test + void shouldHonorConfigProperties() { + this.contextRunner.withUserConfiguration(WavefrontSenderConfiguration.class) + .withPropertyValues("spring.application.name=super-application", + "management.wavefront.tracing.service-name=super-service") + .run((context) -> { + ApplicationTags applicationTags = context.getBean(ApplicationTags.class); + assertThat(applicationTags.getApplication()).isEqualTo("super-application"); + assertThat(applicationTags.getService()).isEqualTo("super-service"); + }); + } + + @Test + void shouldBackOffOnCustomBeans() { + this.contextRunner.withUserConfiguration(WavefrontSenderConfiguration.class, CustomConfiguration.class) + .run((context) -> { + assertThat(context).hasBean("customApplicationTags"); + assertThat(context).hasSingleBean(ApplicationTags.class); + assertThat(context).hasBean("customWavefrontSpanHandler"); + assertThat(context).hasSingleBean(WavefrontSpanHandler.class); + assertThat(context).hasBean("customSpanMetrics"); + assertThat(context).hasSingleBean(SpanMetrics.class); + assertThat(context).hasBean("customWavefrontBraveSpanHandler"); + assertThat(context).hasSingleBean(WavefrontBraveSpanHandler.class); + assertThat(context).hasBean("customWavefrontOtelSpanHandler"); + assertThat(context).hasSingleBean(WavefrontOtelSpanHandler.class); + }); + } + + @Configuration(proxyBeanMethods = false) + private static class CustomConfiguration { + + @Bean + ApplicationTags customApplicationTags() { + return Mockito.mock(ApplicationTags.class); + } + + @Bean + WavefrontSpanHandler customWavefrontSpanHandler() { + return Mockito.mock(WavefrontSpanHandler.class); + } + + @Bean + SpanMetrics customSpanMetrics() { + return Mockito.mock(SpanMetrics.class); + } + + @Bean + WavefrontBraveSpanHandler customWavefrontBraveSpanHandler() { + return Mockito.mock(WavefrontBraveSpanHandler.class); + } + + @Bean + WavefrontOtelSpanHandler customWavefrontOtelSpanHandler() { + return Mockito.mock(WavefrontOtelSpanHandler.class); + } + + } + + @Configuration(proxyBeanMethods = false) + private static class WavefrontSenderConfiguration { + + @Bean + WavefrontSender wavefrontSender() { + return mock(WavefrontSender.class); + } + + } + + @Configuration(proxyBeanMethods = false) + private static class MeterRegistryConfiguration { + + @Bean + MeterRegistry meterRegistry() { + return new SimpleMeterRegistry(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/NoopSender.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/NoopSender.java new file mode 100644 index 000000000000..48a7926e7818 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/NoopSender.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; + +import java.util.List; + +import zipkin2.Call; +import zipkin2.Callback; +import zipkin2.codec.Encoding; +import zipkin2.reporter.Sender; + +class NoopSender extends Sender { + + @Override + public Encoding encoding() { + return Encoding.JSON; + } + + @Override + public int messageMaxBytes() { + return 1024; + } + + @Override + public int messageSizeInBytes(List encodedSpans) { + return encoding().listSizeInBytes(encodedSpans); + } + + @Override + public Call sendSpans(List encodedSpans) { + return new Call.Base<>() { + @Override + public Call clone() { + return this; + } + + @Override + protected Void doExecute() { + return null; + } + + @Override + protected void doEnqueue(Callback callback) { + } + }; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfigurationTests.java new file mode 100644 index 000000000000..4b15fdda923f --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfigurationTests.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; + +import org.junit.jupiter.api.Test; +import zipkin2.Span; +import zipkin2.codec.BytesEncoder; +import zipkin2.codec.SpanBytesEncoder; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ZipkinAutoConfiguration}. + * + * @author Moritz Halbritter + */ +class ZipkinAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(ZipkinAutoConfiguration.class)); + + @Test + void shouldSupplyBeans() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(BytesEncoder.class)); + } + + @Test + void shouldNotSupplyBeansIfZipkinReporterIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("zipkin2.reporter")) + .run((context) -> assertThat(context).doesNotHaveBean(BytesEncoder.class)); + } + + @Test + void shouldBackOffOnCustomBeans() { + this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { + assertThat(context).hasBean("customBytesEncoder"); + assertThat(context).hasSingleBean(BytesEncoder.class); + }); + } + + @Configuration(proxyBeanMethods = false) + private static class CustomConfiguration { + + @Bean + BytesEncoder customBytesEncoder() { + return SpanBytesEncoder.JSON_V2; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsBraveConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsBraveConfigurationTests.java new file mode 100644 index 000000000000..2ac0c6f56559 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsBraveConfigurationTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; + +import brave.handler.SpanHandler; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import zipkin2.Span; +import zipkin2.reporter.Reporter; + +import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.BraveConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link BraveConfiguration}. + * + * @author Moritz Halbritter + */ +class ZipkinConfigurationsBraveConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(BraveConfiguration.class)); + + @Test + void shouldSupplyBeans() { + this.contextRunner.withUserConfiguration(ReporterConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(SpanHandler.class)); + } + + @Test + void shouldNotSupplySpanHandlerIfReporterIsMissing() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(SpanHandler.class)); + } + + @Test + void shouldNotSupplyIfZipkinReporterBraveIsNotOnClasspath() { + this.contextRunner.withClassLoader(new FilteredClassLoader("zipkin2.reporter.brave")) + .withUserConfiguration(ReporterConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(SpanHandler.class)); + + } + + @Test + void shouldBackOffOnCustomBeans() { + this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { + assertThat(context).hasBean("customSpanHandler"); + assertThat(context).hasSingleBean(SpanHandler.class); + }); + } + + @Configuration(proxyBeanMethods = false) + private static class ReporterConfiguration { + + @Bean + @SuppressWarnings("unchecked") + Reporter reporter() { + return Mockito.mock(Reporter.class); + } + + } + + @Configuration(proxyBeanMethods = false) + private static class CustomConfiguration { + + @Bean + SpanHandler customSpanHandler() { + return Mockito.mock(SpanHandler.class); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsOpenTelemetryConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsOpenTelemetryConfigurationTests.java new file mode 100644 index 000000000000..14ba3e413d20 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsOpenTelemetryConfigurationTests.java @@ -0,0 +1,104 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; + +import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter; +import org.junit.jupiter.api.Test; +import zipkin2.Span; +import zipkin2.codec.BytesEncoder; +import zipkin2.codec.SpanBytesEncoder; +import zipkin2.reporter.Sender; + +import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.OpenTelemetryConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OpenTelemetryConfiguration}. + * + * @author Moritz Halbritter + */ +class ZipkinConfigurationsOpenTelemetryConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(BaseConfiguration.class, OpenTelemetryConfiguration.class)); + + @Test + void shouldSupplyBeans() { + this.contextRunner.withUserConfiguration(SenderConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(ZipkinSpanExporter.class)); + } + + @Test + void shouldNotSupplyZipkinSpanExporterIfSenderIsMissing() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ZipkinSpanExporter.class)); + } + + @Test + void shouldNotSupplyZipkinSpanExporterIfNotOnClasspath() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.opentelemetry.exporter.zipkin")) + .withUserConfiguration(SenderConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(ZipkinSpanExporter.class)); + + } + + @Test + void shouldBackOffOnCustomBeans() { + this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { + assertThat(context).hasBean("customZipkinSpanExporter"); + assertThat(context).hasSingleBean(ZipkinSpanExporter.class); + }); + } + + @Configuration(proxyBeanMethods = false) + private static class SenderConfiguration { + + @Bean + Sender sender() { + return new NoopSender(); + } + + } + + @Configuration(proxyBeanMethods = false) + private static class CustomConfiguration { + + @Bean + ZipkinSpanExporter customZipkinSpanExporter() { + return ZipkinSpanExporter.builder().build(); + } + + } + + @Configuration(proxyBeanMethods = false) + private static class BaseConfiguration { + + @Bean + @ConditionalOnMissingBean + BytesEncoder spanBytesEncoder() { + return SpanBytesEncoder.JSON_V2; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsReporterConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsReporterConfigurationTests.java new file mode 100644 index 000000000000..e5d115927d18 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsReporterConfigurationTests.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import zipkin2.Span; +import zipkin2.codec.BytesEncoder; +import zipkin2.codec.SpanBytesEncoder; +import zipkin2.reporter.Reporter; +import zipkin2.reporter.Sender; + +import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.ReporterConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ReporterConfiguration}. + * + * @author Moritz Halbritter + */ +class ZipkinConfigurationsReporterConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(BaseConfiguration.class, ReporterConfiguration.class)); + + @Test + void shouldSupplyBeans() { + this.contextRunner.withUserConfiguration(SenderConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(Reporter.class)); + } + + @Test + void shouldNotSupplyReporterIfSenderIsMissing() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(Reporter.class)); + } + + @Test + void shouldBackOffOnCustomBeans() { + this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { + assertThat(context).hasBean("customReporter"); + assertThat(context).hasSingleBean(Reporter.class); + }); + } + + @Configuration(proxyBeanMethods = false) + private static class SenderConfiguration { + + @Bean + Sender sender() { + return new NoopSender(); + } + + } + + @Configuration(proxyBeanMethods = false) + private static class CustomConfiguration { + + @Bean + @SuppressWarnings("unchecked") + Reporter customReporter() { + return Mockito.mock(Reporter.class); + } + + } + + @Configuration(proxyBeanMethods = false) + private static class BaseConfiguration { + + @Bean + @ConditionalOnMissingBean + BytesEncoder spanBytesEncoder() { + return SpanBytesEncoder.JSON_V2; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsSenderConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsSenderConfigurationTests.java new file mode 100644 index 000000000000..48a697e5c38e --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsSenderConfigurationTests.java @@ -0,0 +1,100 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import zipkin2.reporter.Sender; +import zipkin2.reporter.urlconnection.URLConnectionSender; + +import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.SenderConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SenderConfiguration}. + * + * @author Moritz Halbritter + */ +class ZipkinConfigurationsSenderConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SenderConfiguration.class)); + + @Test + void shouldSupplyBeans() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(Sender.class); + assertThat(context).hasSingleBean(URLConnectionSender.class); + assertThat(context).doesNotHaveBean(ZipkinRestTemplateSender.class); + }); + } + + @Test + void shouldUseRestTemplateSenderIfUrlConnectionSenderIsNotAvailable() { + this.contextRunner.withUserConfiguration(RestTemplateConfiguration.class) + .withClassLoader(new FilteredClassLoader("zipkin2.reporter.urlconnection")).run((context) -> { + assertThat(context).doesNotHaveBean(URLConnectionSender.class); + assertThat(context).hasSingleBean(Sender.class); + assertThat(context).hasSingleBean(ZipkinRestTemplateSender.class); + }); + } + + @Test + void shouldNotSupplyRestTemplateSenderIfNoBuilderIsAvailable() { + this.contextRunner.run((context) -> { + assertThat(context).doesNotHaveBean(ZipkinRestTemplateSender.class); + assertThat(context).hasSingleBean(Sender.class); + assertThat(context).hasSingleBean(URLConnectionSender.class); + }); + } + + @Test + void shouldBackOffOnCustomBeans() { + this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { + assertThat(context).hasBean("customSender"); + assertThat(context).hasSingleBean(Sender.class); + }); + } + + @Configuration(proxyBeanMethods = false) + private static class RestTemplateConfiguration { + + @Bean + RestTemplateBuilder restTemplateBuilder() { + return new RestTemplateBuilder(); + } + + } + + @Configuration(proxyBeanMethods = false) + private static class CustomConfiguration { + + @Bean + Sender customSender() { + return Mockito.mock(Sender.class); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateSenderTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateSenderTests.java new file mode 100644 index 000000000000..526d45be7df8 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateSenderTests.java @@ -0,0 +1,122 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import zipkin2.CheckResult; +import zipkin2.reporter.ClosedSenderException; + +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.content; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.header; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus; + +/** + * Tests for {@link ZipkinRestTemplateSender}. + * + * @author Moritz Halbritter + */ +class ZipkinRestTemplateSenderTests { + + private static final String ZIPKIN_URL = "http://localhost:9411/api/v2/spans"; + + private MockRestServiceServer mockServer; + + private ZipkinRestTemplateSender sut; + + @BeforeEach + void setUp() { + RestTemplate restTemplate = new RestTemplate(); + this.mockServer = MockRestServiceServer.createServer(restTemplate); + this.sut = new ZipkinRestTemplateSender(ZIPKIN_URL, restTemplate); + } + + @AfterEach + void tearDown() { + this.mockServer.verify(); + } + + @Test + void checkShouldSendEmptySpanList() { + this.mockServer.expect(requestTo(ZIPKIN_URL)).andExpect(method(HttpMethod.POST)) + .andExpect(content().string("[]")).andRespond(withStatus(HttpStatus.ACCEPTED)); + assertThat(this.sut.check()).isEqualTo(CheckResult.OK); + } + + @Test + void checkShouldNotRaiseException() { + this.mockServer.expect(requestTo(ZIPKIN_URL)).andExpect(method(HttpMethod.POST)) + .andRespond(withStatus(HttpStatus.INTERNAL_SERVER_ERROR)); + CheckResult result = this.sut.check(); + assertThat(result.ok()).isFalse(); + assertThat(result.error()).hasMessageContaining("500 Internal Server Error"); + } + + @Test + void sendSpansShouldSendSpansToZipkin() throws IOException { + this.mockServer.expect(requestTo(ZIPKIN_URL)).andExpect(method(HttpMethod.POST)) + .andExpect(content().contentType("application/json")).andExpect(content().string("[span1,span2]")) + .andRespond(withStatus(HttpStatus.ACCEPTED)); + this.sut.sendSpans(List.of(toByteArray("span1"), toByteArray("span2"))).execute(); + } + + @Test + void sendSpansShouldThrowOnHttpFailure() throws IOException { + this.mockServer.expect(requestTo(ZIPKIN_URL)).andExpect(method(HttpMethod.POST)) + .andRespond(withStatus(HttpStatus.INTERNAL_SERVER_ERROR)); + assertThatThrownBy(() -> this.sut.sendSpans(List.of()).execute()) + .hasMessageContaining("500 Internal Server Error"); + } + + @Test + void sendSpansShouldThrowIfCloseWasCalled() throws IOException { + this.sut.close(); + assertThatThrownBy(() -> this.sut.sendSpans(List.of())).isInstanceOf(ClosedSenderException.class); + } + + @Test + void sendSpansShouldCompressData() throws IOException { + String uncompressed = "a".repeat(10000); + // This is gzip compressed 10000 times 'a' + byte[] compressed = Base64.getDecoder() + .decode("H4sIAAAAAAAA/+3BMQ0AAAwDIKFLj/k3UR8NcA8AAAAAAAAAAAADUsAZfeASJwAA"); + this.mockServer.expect(requestTo(ZIPKIN_URL)).andExpect(method(HttpMethod.POST)) + .andExpect(header("Content-Encoding", "gzip")).andExpect(content().contentType("application/json")) + .andExpect(content().bytes(compressed)).andRespond(withStatus(HttpStatus.ACCEPTED)); + this.sut.sendSpans(List.of(toByteArray(uncompressed))).execute(); + } + + private byte[] toByteArray(String input) { + return input.getBytes(StandardCharsets.UTF_8); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontAutoConfigurationTests.java new file mode 100644 index 000000000000..2f20023e8f13 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontAutoConfigurationTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.wavefront; + +import java.util.concurrent.LinkedBlockingQueue; + +import com.wavefront.sdk.common.WavefrontSender; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.as; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link WavefrontAutoConfiguration}. + * + * @author Moritz Halbritter + */ +class WavefrontAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(WavefrontAutoConfiguration.class)); + + @Test + void shouldNotFailIfWavefrontIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("com.wavefront")) + .run(((context) -> assertThat(context).doesNotHaveBean(WavefrontSender.class))); + } + + @Test + void failsWithoutAnApiTokenWhenPublishingDirectly() { + this.contextRunner.run((context) -> assertThat(context).hasFailed()); + } + + @Test + void defaultWavefrontSenderSettingsAreConsistent() { + this.contextRunner.withPropertyValues("management.wavefront.api-token=abcde").run((context) -> { + WavefrontProperties properties = new WavefrontProperties(); + WavefrontSender sender = context.getBean(WavefrontSender.class); + assertThat(sender) + .extracting("metricsBuffer", as(InstanceOfAssertFactories.type(LinkedBlockingQueue.class))) + .satisfies((queue) -> assertThat(queue.remainingCapacity() + queue.size()) + .isEqualTo(properties.getSender().getMaxQueueSize())); + assertThat(sender).hasFieldOrPropertyWithValue("batchSize", properties.getSender().getBatchSize()); + assertThat(sender).hasFieldOrPropertyWithValue("messageSizeBytes", + (int) properties.getSender().getMessageSize().toBytes()); + }); + } + + @Test + void configureWavefrontSender() { + this.contextRunner.withPropertyValues("management.wavefront.api-token=abcde", + "management.wavefront.sender.batch-size=50", "management.wavefront.sender.max-queue-size=100", + "management.wavefront.sender.message-size=1KB").run((context) -> { + WavefrontSender sender = context.getBean(WavefrontSender.class); + assertThat(sender).hasFieldOrPropertyWithValue("batchSize", 50); + assertThat(sender) + .extracting("metricsBuffer", as(InstanceOfAssertFactories.type(LinkedBlockingQueue.class))) + .satisfies((queue) -> assertThat(queue.remainingCapacity() + queue.size()).isEqualTo(100)); + assertThat(sender).hasFieldOrPropertyWithValue("messageSizeBytes", 1024); + }); + } + + @Test + void allowsWavefrontSenderToBeCustomized() { + this.contextRunner.withUserConfiguration(CustomSenderConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(WavefrontSender.class).hasBean("customSender")); + } + + @Configuration(proxyBeanMethods = false) + static class CustomSenderConfiguration { + + @Bean + WavefrontSender customSender() { + return mock(WavefrontSender.class); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontPropertiesMetricsExportTests.java similarity index 58% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesTests.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontPropertiesMetricsExportTests.java index 4ec6050d468c..f467aa7c374a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontPropertiesMetricsExportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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. @@ -14,29 +14,31 @@ * limitations under the License. */ -package org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront; +package org.springframework.boot.actuate.autoconfigure.wavefront; import io.micrometer.wavefront.WavefrontConfig; import org.junit.jupiter.api.Test; -import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.PushRegistryPropertiesTests; - import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link WavefrontProperties}. + * Tests for {@link WavefrontProperties.Metrics.Export}. * * @author Stephane Nicoll + * @author Moritz Halbritter */ -class WavefrontPropertiesTests extends PushRegistryPropertiesTests { +class WavefrontPropertiesMetricsExportTests { @Test + @SuppressWarnings("deprecation") void defaultValuesAreConsistent() { - WavefrontProperties properties = new WavefrontProperties(); + WavefrontProperties.Metrics.Export properties = new WavefrontProperties.Metrics.Export(); WavefrontConfig config = WavefrontConfig.DEFAULT_DIRECT; - assertStepRegistryDefaultValues(properties, config); - assertThat(properties.getUri().toString()).isEqualTo(config.uri()); + assertThat(properties.getConnectTimeout()).isEqualTo(config.connectTimeout()); assertThat(properties.getGlobalPrefix()).isEqualTo(config.globalPrefix()); + assertThat(properties.getReadTimeout()).isEqualTo(config.readTimeout()); + assertThat(properties.getStep()).isEqualTo(config.step()); + assertThat(properties.isEnabled()).isEqualTo(config.enabled()); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontPropertiesTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontPropertiesTests.java new file mode 100644 index 000000000000..b5bcbd1e21ec --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontPropertiesTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.wavefront; + +import java.net.URI; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Tests for {@link WavefrontProperties}. + * + * @author Moritz Halbritter + */ +class WavefrontPropertiesTests { + + @Test + void apiTokenIsOptionalWhenUsingProxy() { + WavefrontProperties sut = new WavefrontProperties(); + sut.setUri(URI.create("proxy://localhost:2878")); + sut.setApiToken(null); + assertThat(sut.getApiTokenOrThrow()).isNull(); + assertThat(sut.getEffectiveUri()).isEqualTo(URI.create("http://localhost:2878")); + } + + @Test + void apiTokenIsMandatoryWhenNotUsingProxy() { + WavefrontProperties sut = new WavefrontProperties(); + sut.setUri(URI.create("http://localhost:2878")); + sut.setApiToken(null); + assertThat(sut.getEffectiveUri()).isEqualTo(URI.create("http://localhost:2878")); + assertThatThrownBy(sut::getApiTokenOrThrow).isInstanceOf(InvalidConfigurationPropertyValueException.class) + .hasMessageContaining("management.wavefront.api-token"); + } + +} diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 4f296434153b..6f3491e642bb 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -990,8 +990,8 @@ bom { } library("Micrometer Tracing", "1.0.0-SNAPSHOT") { group("io.micrometer") { - modules = [ - "micrometer-tracing-api" + imports = [ + "micrometer-tracing-bom" ] } } @@ -1074,6 +1074,13 @@ bom { ] } } + library("OpenTelemetry", "1.12.0") { + group("io.opentelemetry") { + imports = [ + "opentelemetry-bom" + ] + } + } library("Oracle Database", "21.5.0.0") { group("com.oracle.database.jdbc") { imports = [ @@ -1579,6 +1586,13 @@ bom { ] } } + library("Zipkin", "2.16.3") { + group("io.zipkin.reporter2") { + modules = [ + "zipkin-sender-urlconnection" + ] + } + } } generateMetadataFileForMavenPublication { diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc index bb175b6d64bd..848c00341dce 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc @@ -589,9 +589,7 @@ If you are exporting metrics to https://www.wavefront.com/[Wavefront] directly, ---- management: wavefront: - metrics: - export: - api-token: "YOUR_API_TOKEN" + api-token: "YOUR_API_TOKEN" ---- Alternatively, you can use a Wavefront sidecar or an internal proxy in your environment to forward metrics data to the Wavefront API host: @@ -600,9 +598,7 @@ Alternatively, you can use a Wavefront sidecar or an internal proxy in your envi ---- management: wavefront: - metrics: - export: - uri: "proxy://localhost:2878" + uri: "proxy://localhost:2878" ---- NOTE: If you publish metrics to a Wavefront proxy (as described in https://docs.wavefront.com/proxies_installing.html[the Wavefront documentation]), the host must be in the `proxy://HOST:PORT` format.