Drops zipkin dependency from spring-cloud-sleuth-core (#1649)
There was emmense work to prepare for decoupling of spring-cloud-sleuth-core from Zipkin. This included complete test conversion and deprecations between 2.2.x and 3.0.x. This moves all Zipkin related code to spring-cloud-sleuth-zipkin, making the primary data recording tool `SpanHandler` as opposed to `Reporter<zipkin2.Span>` For example, Wavefront and soon Stackdriver can implement `SpanHandler` and skip conversion into the Zipkin model first. `SpanHandler` also includes begin and end hooks which allow data extensions to be developed. see https://github.com/wavefrontHQ/wavefront-spring-boot
This commit is contained in:
@@ -16,24 +16,38 @@
|
||||
|
||||
package org.springframework.cloud.sleuth.zipkin2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import brave.Tag;
|
||||
import brave.TracingCustomizer;
|
||||
import brave.handler.SpanHandler;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import zipkin2.CheckResult;
|
||||
import zipkin2.Span;
|
||||
import zipkin2.reporter.AsyncReporter;
|
||||
import zipkin2.reporter.InMemoryReporterMetrics;
|
||||
import zipkin2.reporter.Reporter;
|
||||
import zipkin2.reporter.ReporterMetrics;
|
||||
import zipkin2.reporter.Sender;
|
||||
import zipkin2.reporter.brave.ZipkinSpanHandler;
|
||||
import zipkin2.reporter.metrics.micrometer.MicrometerReporterMetrics;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
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.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
@@ -45,6 +59,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
@@ -74,6 +89,22 @@ public class ZipkinAutoConfiguration {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ZipkinAutoConfiguration.class);
|
||||
|
||||
/**
|
||||
* Sort Zipkin Handlers last, so that redactions etc happen prior.
|
||||
*/
|
||||
static final Comparator<SpanHandler> SPAN_HANDLER_COMPARATOR = (o1, o2) -> {
|
||||
if (o1 instanceof ZipkinSpanHandler) {
|
||||
if (o2 instanceof ZipkinSpanHandler) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
else if (o2 instanceof ZipkinSpanHandler) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Zipkin reporter bean name. Name of the bean matters for supporting multiple tracing
|
||||
* systems.
|
||||
@@ -143,6 +174,44 @@ public class ZipkinAutoConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns one handler for as many reporters as exist. */
|
||||
@Bean
|
||||
SpanHandler zipkinSpanHandler(@Nullable List<Reporter<Span>> spanReporters,
|
||||
@Nullable Tag<Throwable> errorTag) {
|
||||
if (spanReporters == null) {
|
||||
return SpanHandler.NOOP;
|
||||
}
|
||||
|
||||
LinkedHashSet<Reporter<Span>> reporters = new LinkedHashSet<>(spanReporters);
|
||||
reporters.remove(Reporter.NOOP);
|
||||
if (spanReporters.isEmpty()) {
|
||||
return SpanHandler.NOOP;
|
||||
}
|
||||
|
||||
Reporter<Span> spanReporter = reporters.size() == 1 ? reporters.iterator().next()
|
||||
: new CompositeSpanReporter(reporters.toArray(new Reporter[0]));
|
||||
|
||||
ZipkinSpanHandler.Builder builder = ZipkinSpanHandler.newBuilder(spanReporter);
|
||||
if (errorTag != null) {
|
||||
builder.errorTag(errorTag);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/** This ensures Zipkin reporters end up after redaction, etc. */
|
||||
@Bean
|
||||
TracingCustomizer reorderZipkinHandlersLast() {
|
||||
return builder -> {
|
||||
List<SpanHandler> configuredSpanHandlers = new ArrayList<>(
|
||||
builder.spanHandlers());
|
||||
configuredSpanHandlers.sort(SPAN_HANDLER_COMPARATOR);
|
||||
builder.clearSpanHandlers();
|
||||
for (SpanHandler spanHandler : configuredSpanHandlers) {
|
||||
builder.addSpanHandler(spanHandler);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ZipkinRestTemplateCustomizer zipkinRestTemplateCustomizer(
|
||||
@@ -206,4 +275,84 @@ public class ZipkinAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnMissingClass("io.micrometer.core.instrument.MeterRegistry")
|
||||
static class TraceMetricsInMemoryConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
ReporterMetrics sleuthReporterMetrics() {
|
||||
return new InMemoryReporterMetrics();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(MeterRegistry.class)
|
||||
static class TraceMetricsMicrometerConfiguration {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnMissingBean(ReporterMetrics.class)
|
||||
static class NoReporterMetricsBeanConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(MeterRegistry.class)
|
||||
ReporterMetrics sleuthMicrometerReporterMetrics(MeterRegistry meterRegistry) {
|
||||
return MicrometerReporterMetrics.create(meterRegistry);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(MeterRegistry.class)
|
||||
ReporterMetrics sleuthReporterMetrics() {
|
||||
return new InMemoryReporterMetrics();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Zipkin conversion only happens once per mutable span
|
||||
static final class CompositeSpanReporter implements Reporter<Span> {
|
||||
|
||||
final Reporter<Span>[] reporters;
|
||||
|
||||
CompositeSpanReporter(Reporter<Span>[] reporters) {
|
||||
this.reporters = reporters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Span span) {
|
||||
for (Reporter<Span> reporter : reporters) {
|
||||
try {
|
||||
reporter.report(span);
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
// TODO: message lifted from ListReporter: this is probably too much
|
||||
// for warn level
|
||||
log.warn("Exception occurred while trying to report the span " + span,
|
||||
ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(reporters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof CompositeSpanReporter)) {
|
||||
return false;
|
||||
}
|
||||
return Arrays.equals(((CompositeSpanReporter) obj).reporters, reporters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Arrays.toString(reporters);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.sleuth.zipkin2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
@@ -26,8 +27,11 @@ import brave.handler.MutableSpan;
|
||||
import brave.handler.SpanHandler;
|
||||
import brave.propagation.TraceContext;
|
||||
import brave.sampler.Sampler;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.assertj.core.api.BDDAssertions;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -36,17 +40,24 @@ import zipkin2.Call;
|
||||
import zipkin2.CheckResult;
|
||||
import zipkin2.codec.Encoding;
|
||||
import zipkin2.reporter.AsyncReporter;
|
||||
import zipkin2.reporter.InMemoryReporterMetrics;
|
||||
import zipkin2.reporter.Reporter;
|
||||
import zipkin2.reporter.ReporterMetrics;
|
||||
import zipkin2.reporter.Sender;
|
||||
import zipkin2.reporter.activemq.ActiveMQSender;
|
||||
import zipkin2.reporter.amqp.RabbitMQSender;
|
||||
import zipkin2.reporter.brave.ZipkinSpanHandler;
|
||||
import zipkin2.reporter.kafka.KafkaSender;
|
||||
import zipkin2.reporter.metrics.micrometer.MicrometerReporterMetrics;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -57,6 +68,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.cloud.sleuth.zipkin2.ZipkinAutoConfiguration.SPAN_HANDLER_COMPARATOR;
|
||||
|
||||
/**
|
||||
* Not using {@linkplain SpringBootTest} as we need to change properties per test.
|
||||
@@ -65,6 +77,9 @@ import static org.mockito.Mockito.when;
|
||||
*/
|
||||
public class ZipkinAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(ZipkinAutoConfiguration.class));
|
||||
|
||||
public MockWebServer server = new MockWebServer();
|
||||
|
||||
@BeforeEach
|
||||
@@ -89,7 +104,55 @@ public class ZipkinAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultsToV2Endpoint() throws Exception {
|
||||
void span_handler_comparator() {
|
||||
SpanHandler handler1 = mock(SpanHandler.class);
|
||||
SpanHandler handler2 = mock(SpanHandler.class);
|
||||
ZipkinSpanHandler zipkin1 = mock(ZipkinSpanHandler.class);
|
||||
ZipkinSpanHandler zipkin2 = mock(ZipkinSpanHandler.class);
|
||||
|
||||
ArrayList<SpanHandler> spanHandlers = new ArrayList<>();
|
||||
spanHandlers.add(handler1);
|
||||
spanHandlers.add(zipkin1);
|
||||
spanHandlers.add(handler2);
|
||||
spanHandlers.add(zipkin2);
|
||||
|
||||
spanHandlers.sort(SPAN_HANDLER_COMPARATOR);
|
||||
|
||||
assertThat(spanHandlers).containsExactly(handler1, handler2, zipkin1, zipkin2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_apply_micrometer_reporter_metrics_when_meter_registry_bean_present() {
|
||||
this.contextRunner.withUserConfiguration(WithMeterRegistry.class)
|
||||
.run((context) -> {
|
||||
ReporterMetrics bean = context.getBean(ReporterMetrics.class);
|
||||
|
||||
BDDAssertions.then(bean)
|
||||
.isInstanceOf(MicrometerReporterMetrics.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_apply_in_memory_metrics_when_meter_registry_bean_missing() {
|
||||
this.contextRunner.run((context) -> {
|
||||
ReporterMetrics bean = context.getBean(ReporterMetrics.class);
|
||||
|
||||
BDDAssertions.then(bean).isInstanceOf(InMemoryReporterMetrics.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_apply_in_memory_metrics_when_meter_registry_class_missing() {
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader(MeterRegistry.class))
|
||||
.run((context) -> {
|
||||
ReporterMetrics bean = context.getBean(ReporterMetrics.class);
|
||||
|
||||
BDDAssertions.then(bean).isInstanceOf(InMemoryReporterMetrics.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultsToV2Endpoint() throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
environment().setProperty("spring.zipkin.base-url",
|
||||
this.server.url("/").toString());
|
||||
@@ -380,7 +443,7 @@ public class ZipkinAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class HandlerHanldersConfig {
|
||||
protected static class HandlersConfig {
|
||||
|
||||
@Bean
|
||||
SpanHandler handlerOne() {
|
||||
@@ -408,6 +471,26 @@ public class ZipkinAutoConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class WithMeterRegistry {
|
||||
|
||||
@Bean
|
||||
MeterRegistry meterRegistry() {
|
||||
return new SimpleMeterRegistry();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class WithReporter {
|
||||
|
||||
@Bean
|
||||
Reporter<zipkin2.Span> spanReporter() {
|
||||
return zipkin2.Span::toString;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class MultipleReportersConfig {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user