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:
Adrian Cole
2020-05-19 08:22:54 +08:00
committed by GitHub
parent 31fb3001c5
commit 4ed3db9a27
13 changed files with 274 additions and 280 deletions

View File

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

View File

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