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
pull/30659/head
Moritz Halbritter 3 years ago committed by Moritz Halbritter
parent f5f7fc8e9a
commit 3860eb211a

@ -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")

@ -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.CompositeMeterRegi
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.ConditionalOnEnabledMetricsExport;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.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 @@ import org.springframework.util.unit.DataSize;
*/
@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 class WavefrontMetricsExportAutoConfiguration {
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();
}
}

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

@ -19,18 +19,24 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront;
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<WavefrontProperties>
implements WavefrontConfig {
public class WavefrontPropertiesConfigAdapter
extends PushRegistryPropertiesConfigAdapter<WavefrontProperties.Metrics.Export> 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 class WavefrontPropertiesConfigAdapter extends PushRegistryPropertiesConf
}
@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);
}
}

@ -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<SpanHandler> spanHandlers,
List<TracingCustomizer> 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<CurrentTraceContext.ScopeDecorator> scopeDecorators,
List<CurrentTraceContextCustomizer> 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();
}
}
}

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

@ -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 {
}

@ -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<SpanProcessor> 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<TextMapPropagator> 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> 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();
}
}
}

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

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

@ -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());
}
}

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

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

@ -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<Span> spanBytesEncoder() {
return SpanBytesEncoder.JSON_V2;
}
}

@ -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<Span> spanReporter(Sender sender, BytesEncoder<Span> encoder) {
return AsyncReporter.builder(sender).build(encoder);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ZipkinSpanHandler.class)
static class BraveConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(Reporter.class)
SpanHandler zipkinSpanHandler(Reporter<Span> spanReporter) {
return ZipkinSpanHandler.newBuilder(spanReporter).build();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ZipkinSpanExporter.class)
static class OpenTelemetryConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(Sender.class)
ZipkinSpanExporter zipkinSpanExporter(BytesEncoder<Span> encoder, Sender sender) {
return ZipkinSpanExporter.builder().setEncoder(encoder).setSender(sender).build();
}
}
}

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

@ -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<byte[]> encodedSpans) {
return encoding().listSizeInBytes(encodedSpans);
}
@Override
public int messageSizeInBytes(int encodedSizeInBytes) {
return encoding().listSizeInBytes(encodedSizeInBytes);
}
@Override
public Call<Void> sendSpans(List<byte[]> 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<Void> {
/**
* 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<byte[]> 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<Void> callback) {
try {
doExecute();
callback.onSuccess(null);
}
catch (IOException | RuntimeException ex) {
callback.onError(ex);
}
}
@Override
public Call<Void> 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();
}
}
}

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

@ -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();
}
}

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

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

@ -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"
}
},
{

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

@ -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.AfterEach;
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 @@ class SpringApplicationHierarchyTests {
@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 @@ class SpringApplicationHierarchyTests {
@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 {
}

@ -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.junit.jupiter.api.Test;
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 @@ class WebEndpointsAutoConfigurationIntegrationTests {
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 {

@ -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 @@ public abstract class PushRegistryPropertiesConfigAdapterTests<P extends PushReg
}
@Test
void whenPropertiesBatchSizeIsSetAdapterBatchSizeReturnsIt() {
protected void whenPropertiesBatchSizeIsSetAdapterBatchSizeReturnsIt() {
P properties = createProperties();
properties.setBatchSize(10042);
assertThat(createConfigAdapter(properties).batchSize()).isEqualTo(10042);

@ -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.Bean;
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 @@ class WavefrontMetricsExportAutoConfigurationTests {
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 @@ class WavefrontMetricsExportAutoConfigurationTests {
.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 @@ class WavefrontMetricsExportAutoConfigurationTests {
@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 @@ class WavefrontMetricsExportAutoConfigurationTests {
@Configuration(proxyBeanMethods = false)
static class BaseConfiguration {
@Bean
WavefrontSender customWavefrontSender() {
return Mockito.mock(WavefrontSender.class);
}
@Bean
Clock clock() {
return Clock.SYSTEM;

@ -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 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link WavefrontPropertiesConfigAdapter}.
*
* @author Stephane Nicoll
* @author Moritz Halbritter
*/
class WavefrontPropertiesConfigAdapterTests
extends PushRegistryPropertiesConfigAdapterTests<WavefrontProperties, WavefrontPropertiesConfigAdapter> {
class WavefrontPropertiesConfigAdapterTests extends
PushRegistryPropertiesConfigAdapterTests<WavefrontProperties.Metrics.Export, WavefrontPropertiesConfigAdapter> {
@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);
}
}

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

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

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

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

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

@ -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<Integer> queue = new ArrayBlockingQueue<Integer>(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<Integer> queue = new ArrayBlockingQueue<Integer>(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();
}
}

@ -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();
}
}
}

@ -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<byte[]> encodedSpans) {
return encoding().listSizeInBytes(encodedSpans);
}
@Override
public Call<Void> sendSpans(List<byte[]> encodedSpans) {
return new Call.Base<>() {
@Override
public Call<Void> clone() {
return this;
}
@Override
protected Void doExecute() {
return null;
}
@Override
protected void doEnqueue(Callback<Void> callback) {
}
};
}
}

@ -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<Span> customBytesEncoder() {
return SpanBytesEncoder.JSON_V2;
}
}
}

@ -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<Span> reporter() {
return Mockito.mock(Reporter.class);
}
}
@Configuration(proxyBeanMethods = false)
private static class CustomConfiguration {
@Bean
SpanHandler customSpanHandler() {
return Mockito.mock(SpanHandler.class);
}
}
}

@ -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<Span> spanBytesEncoder() {
return SpanBytesEncoder.JSON_V2;
}
}
}

@ -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<Span> customReporter() {
return Mockito.mock(Reporter.class);
}
}
@Configuration(proxyBeanMethods = false)
private static class BaseConfiguration {
@Bean
@ConditionalOnMissingBean
BytesEncoder<Span> spanBytesEncoder() {
return SpanBytesEncoder.JSON_V2;
}
}
}

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

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

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

@ -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());
}
}

@ -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");
}
}

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

@ -589,8 +589,6 @@ If you are exporting metrics to https://www.wavefront.com/[Wavefront] directly,
----
management:
wavefront:
metrics:
export:
api-token: "YOUR_API_TOKEN"
----
@ -600,8 +598,6 @@ Alternatively, you can use a Wavefront sidecar or an internal proxy in your envi
----
management:
wavefront:
metrics:
export:
uri: "proxy://localhost:2878"
----

Loading…
Cancel
Save