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-30156pull/30659/head
parent
f5f7fc8e9a
commit
3860eb211a
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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;
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesTests.java → spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontPropertiesMetricsExportTests.java
20
spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesTests.java → spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontPropertiesMetricsExportTests.java
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.actuate.autoconfigure.wavefront;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link WavefrontProperties}.
|
||||||
|
*
|
||||||
|
* @author Moritz Halbritter
|
||||||
|
*/
|
||||||
|
class WavefrontPropertiesTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void apiTokenIsOptionalWhenUsingProxy() {
|
||||||
|
WavefrontProperties sut = new WavefrontProperties();
|
||||||
|
sut.setUri(URI.create("proxy://localhost:2878"));
|
||||||
|
sut.setApiToken(null);
|
||||||
|
assertThat(sut.getApiTokenOrThrow()).isNull();
|
||||||
|
assertThat(sut.getEffectiveUri()).isEqualTo(URI.create("http://localhost:2878"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void apiTokenIsMandatoryWhenNotUsingProxy() {
|
||||||
|
WavefrontProperties sut = new WavefrontProperties();
|
||||||
|
sut.setUri(URI.create("http://localhost:2878"));
|
||||||
|
sut.setApiToken(null);
|
||||||
|
assertThat(sut.getEffectiveUri()).isEqualTo(URI.create("http://localhost:2878"));
|
||||||
|
assertThatThrownBy(sut::getApiTokenOrThrow).isInstanceOf(InvalidConfigurationPropertyValueException.class)
|
||||||
|
.hasMessageContaining("management.wavefront.api-token");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue