diff --git a/docs/src/main/asciidoc/intro.adoc b/docs/src/main/asciidoc/intro.adoc index 9d2feaa3bf..f8eaf8eed9 100644 --- a/docs/src/main/asciidoc/intro.adoc +++ b/docs/src/main/asciidoc/intro.adoc @@ -417,6 +417,18 @@ dependencies { <2> Add the dependency to `spring-cloud-starter-zipkin`. That way, all nested dependencies get downloaded. <3> To automatically configure RabbitMQ, add the `spring-rabbit` dependency. +=== Overriding the auto-configuration of Zipkin + +Spring Cloud Sleuth supports sending traces to multiple tracing systems as of version 2.1.0. +In order to get this to work, every tracing system needs to have a `Reporter` and `Sender`. +If you want to override the provided beans you need to give them a specific name. +To do this you can use respectively `ZipkinAutoConfiguration.REPORTER_BEAN_NAME` and `ZipkinAutoConfiguration.SENDER_BEAN_NAME`. + +[source,java] +---- +include::../../../../spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/ZipkinAutoConfigurationTests.java[tags=override_default_beans,indent=0] +---- + == Additional Resources You can watch a video of https://twitter.com/reshmi9k[Reshmi Krishna] and https://twitter.com/mgrzejszczak[Marcin Grzejszczak] talking about Spring Cloud diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java index c766f0adea..f2a6a80e86 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2018 the original author or authors. + * Copyright 2013-2019 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. @@ -32,7 +32,9 @@ import brave.sampler.Sampler; import org.springframework.util.StringUtils; import zipkin2.Span; +import zipkin2.reporter.InMemoryReporterMetrics; import zipkin2.reporter.Reporter; +import zipkin2.reporter.ReporterMetrics; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -51,6 +53,7 @@ * * @author Spencer Gibb * @author Marcin Grzejszczak + * @author Tim Ysewyn * @since 2.0.0 */ @Configuration @@ -68,6 +71,9 @@ public class TraceAutoConfiguration { */ public static final String DEFAULT_SERVICE_NAME = "default"; + @Autowired(required = false) + List> spanReporters = new ArrayList<>(); + @Autowired(required = false) List spanAdjusters = new ArrayList<>(); @@ -86,13 +92,12 @@ public class TraceAutoConfiguration { Tracing tracing( @Value("${spring.zipkin.service.name:${spring.application.name:default}}") String serviceName, Propagation.Factory factory, CurrentTraceContext currentTraceContext, - Reporter reporter, Sampler sampler, ErrorParser errorParser, - SleuthProperties sleuthProperties) { + Sampler sampler, ErrorParser errorParser, SleuthProperties sleuthProperties) { Tracing.Builder builder = Tracing.newBuilder().sampler(sampler) .errorParser(errorParser) .localServiceName(StringUtils.isEmpty(serviceName) ? DEFAULT_SERVICE_NAME : serviceName) .propagationFactory(factory).currentTraceContext(currentTraceContext) - .spanReporter(adjustedReporter(reporter)) + .spanReporter(compositeReporter()) .traceId128Bit(sleuthProperties.isTraceId128()) .supportsJoin(sleuthProperties.isSupportsJoin()); for (FinishedSpanHandler finishedSpanHandlerFactory : this.finishedSpanHandlers) { @@ -101,13 +106,15 @@ Tracing tracing( return builder.build(); } - private Reporter adjustedReporter(Reporter delegate) { + private Reporter compositeReporter() { return (span) -> { Span spanToAdjust = span; for (SpanAdjuster spanAdjuster : this.spanAdjusters) { spanToAdjust = spanAdjuster.adjust(spanToAdjust); } - delegate.report(spanToAdjust); + for (Reporter spanReporter : this.spanReporters) { + spanReporter.report(spanToAdjust); + } }; } @@ -173,6 +180,12 @@ CurrentTraceContext.Builder sleuthCurrentTraceContextBuilder() { return ThreadLocalCurrentTraceContext.newBuilder(); } + @Bean + @ConditionalOnMissingBean + ReporterMetrics sleuthReporterMetrics() { + return new InMemoryReporterMetrics(); + } + @Bean @ConditionalOnMissingBean Reporter noOpSpanReporter() { diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/ZipkinAutoConfiguration.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/ZipkinAutoConfiguration.java index b5eef2af2b..89f1983553 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/ZipkinAutoConfiguration.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/ZipkinAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2018 the original author or authors. + * Copyright 2013-2019 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. @@ -19,14 +19,13 @@ import java.util.concurrent.TimeUnit; import zipkin2.Span; -import zipkin2.codec.BytesEncoder; import zipkin2.reporter.AsyncReporter; -import zipkin2.reporter.InMemoryReporterMetrics; import zipkin2.reporter.Reporter; import zipkin2.reporter.ReporterMetrics; import zipkin2.reporter.Sender; 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.ConditionalOnClass; @@ -55,6 +54,7 @@ * {@link DefaultZipkinRestTemplateCustomizer} adds the GZip compression. * * @author Spencer Gibb + * @author Tim Ysewyn * @since 1.0.0 * @see SamplerAutoConfiguration * @see ZipkinRestTemplateCustomizer @@ -62,33 +62,33 @@ */ @Configuration @EnableConfigurationProperties(ZipkinProperties.class) -@ConditionalOnProperty(value = "spring.zipkin.enabled", matchIfMissing = true) +@ConditionalOnProperty(value = { "spring.sleuth.enabled", + "spring.zipkin.enabled" }, matchIfMissing = true) @AutoConfigureBefore(TraceAutoConfiguration.class) @AutoConfigureAfter(name = "org.springframework.cloud.autoconfigure.RefreshAutoConfiguration") @Import({ ZipkinSenderConfigurationImportSelector.class, SamplerAutoConfiguration.class }) public class ZipkinAutoConfiguration { /** - * Accepts a sender so you can plug-in any standard one. Returns a Reporter so you can - * also replace with a standard one. + * Zipkin reporter bean name. Name of the bean matters for supporting multiple tracing + * systems. */ - @Bean - @ConditionalOnMissingBean + public static final String REPORTER_BEAN_NAME = "zipkinReporter"; + + /** + * Zipkin sender bean name. Name of the bean matters for supporting multiple tracing + * systems. + */ + public static final String SENDER_BEAN_NAME = "zipkinSender"; + + @Bean(REPORTER_BEAN_NAME) + @ConditionalOnMissingBean(name = REPORTER_BEAN_NAME) public Reporter reporter(ReporterMetrics reporterMetrics, - ZipkinProperties zipkin, Sender sender, BytesEncoder spanBytesEncoder) { - return AsyncReporter.builder(sender).queuedMaxSpans(1000) // historical - // constraint. Note: - // AsyncReporter - // supports memory - // bounds + ZipkinProperties zipkin, @Qualifier(SENDER_BEAN_NAME) Sender sender) { + // historical constraint. Note: AsyncReporter supports memory bounds + return AsyncReporter.builder(sender).queuedMaxSpans(1000) .messageTimeout(zipkin.getMessageTimeout(), TimeUnit.SECONDS) - .metrics(reporterMetrics).build(spanBytesEncoder); - } - - @Bean - @ConditionalOnMissingBean - public BytesEncoder spanBytesEncoder(ZipkinProperties zipkinProperties) { - return zipkinProperties.getEncoder(); + .metrics(reporterMetrics).build(zipkin.getEncoder()); } @Bean @@ -98,12 +98,6 @@ public ZipkinRestTemplateCustomizer zipkinRestTemplateCustomizer( return new DefaultZipkinRestTemplateCustomizer(zipkinProperties); } - @Bean - @ConditionalOnMissingBean - ReporterMetrics sleuthReporterMetrics() { - return new InMemoryReporterMetrics(); - } - @Configuration @ConditionalOnMissingBean(EndpointLocator.class) @ConditionalOnProperty(value = "spring.zipkin.locator.discovery.enabled", havingValue = "false", matchIfMissing = true) diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/ZipkinBackwardsCompatibilityAutoConfiguration.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/ZipkinBackwardsCompatibilityAutoConfiguration.java new file mode 100644 index 0000000000..ce6d3aec39 --- /dev/null +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/ZipkinBackwardsCompatibilityAutoConfiguration.java @@ -0,0 +1,145 @@ +/* + * Copyright 2013-2019 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 + * + * http://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.cloud.sleuth.zipkin2; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import zipkin2.Span; +import zipkin2.codec.BytesEncoder; +import zipkin2.reporter.AsyncReporter; +import zipkin2.reporter.InMemoryReporterMetrics; +import zipkin2.reporter.Reporter; +import zipkin2.reporter.ReporterMetrics; +import zipkin2.reporter.Sender; + +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ConfigurationCondition; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.util.Assert; + +/** + * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration + * Auto-configuration} that will provide backwards compatibility to be able to support + * multiple tracing systems on the classpath. + * + * Needs to be auto-configured before {@link ZipkinAutoConfiguration} in order to create a + * {@link Reporter span reporter} if needed. + * + * @author Tim Ysewyn + * @since 2.1.0 + * @see ZipkinAutoConfiguration + * @deprecated + */ +@Configuration +@ConditionalOnProperty(value = { "spring.sleuth.enabled" }, matchIfMissing = true) +@AutoConfigureAfter({ ZipkinAutoConfiguration.class }) +@Deprecated +class ZipkinBackwardsCompatibilityAutoConfiguration { + + /** + * Reporter that is depending on a {@link Sender} bean which is created in another + * auto-configuration than {@link ZipkinAutoConfiguration}. + */ + @Bean + @Conditional(BackwardsCompatibilityCondition.class) + @Deprecated + Reporter reporter(ReporterMetrics reporterMetrics, ZipkinProperties zipkin, + BytesEncoder spanBytesEncoder, DefaultListableBeanFactory beanFactory) { + List beanNames = new ArrayList<>( + Arrays.asList(beanFactory.getBeanNamesForType(Sender.class))); + beanNames.remove(ZipkinAutoConfiguration.SENDER_BEAN_NAME); + Sender sender = (Sender) beanFactory.getBean(beanNames.get(0)); + // historical constraint. Note: AsyncReporter supports memory bounds + return AsyncReporter.builder(sender).queuedMaxSpans(1000) + .messageTimeout(zipkin.getMessageTimeout(), TimeUnit.SECONDS) + .metrics(reporterMetrics).build(spanBytesEncoder); + } + + /** + * Only used for creating a reporter bean with the method above + * @deprecated + */ + @Bean + @ConditionalOnMissingBean + @Deprecated + BytesEncoder spanBytesEncoder(ZipkinProperties zipkinProperties) { + return zipkinProperties.getEncoder(); + } + + /** + * Deprecated because this is moved to {@link TraceAutoConfiguration}. Left for + * backwards compatibility reasons. + * @deprecated + */ + @Bean + @ConditionalOnMissingBean + @Deprecated + ReporterMetrics zipkinReporterMetrics() { + return new InMemoryReporterMetrics(); + } + + static class BackwardsCompatibilityCondition extends SpringBootCondition + implements ConfigurationCondition { + + @Override + public ConfigurationPhase getConfigurationPhase() { + return ConfigurationPhase.REGISTER_BEAN; + } + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + Assert.isInstanceOf(DefaultListableBeanFactory.class, + context.getBeanFactory()); + DefaultListableBeanFactory listableBeanFactory = (DefaultListableBeanFactory) context + .getBeanFactory(); + int foundSenders = listableBeanFactory + .getBeanNamesForType(Sender.class).length; + + // Previously we supported 1 Sender bean at a time + // which could be overridden by another auto-configuration. + // Now we support both the overridden bean and our default zipkinSender bean. + if (foundSenders < 2) { + return ConditionOutcome.noMatch( + "We don't support backwards compatibility for more than 2 Sender beans"); + } + int foundReporters = listableBeanFactory + .getBeanNamesForType(Reporter.class).length; + // Check if we need to provide a Reporter bean for the overridden Sender bean + if (foundReporters == foundSenders) { + return ConditionOutcome.noMatch( + "Both tracing systems already define their own Reporter bean"); + } + return ConditionOutcome.match(); + } + + } + +} diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/sender/ZipkinKafkaSenderConfiguration.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/sender/ZipkinKafkaSenderConfiguration.java index 0bb3116c8d..ad7b9717e9 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/sender/ZipkinKafkaSenderConfiguration.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/sender/ZipkinKafkaSenderConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2018 the original author or authors. + * Copyright 2013-2019 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. @@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.kafka.KafkaProperties; +import org.springframework.cloud.sleuth.zipkin2.ZipkinAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @@ -34,7 +35,7 @@ @Configuration @ConditionalOnClass(ByteArraySerializer.class) @ConditionalOnBean(KafkaProperties.class) -@ConditionalOnMissingBean(Sender.class) +@ConditionalOnMissingBean(name = ZipkinAutoConfiguration.SENDER_BEAN_NAME) @Conditional(ZipkinSenderCondition.class) @ConditionalOnProperty(value = "spring.zipkin.sender.type", havingValue = "kafka") class ZipkinKafkaSenderConfiguration { @@ -42,7 +43,7 @@ class ZipkinKafkaSenderConfiguration { @Value("${spring.zipkin.kafka.topic:zipkin}") private String topic; - @Bean + @Bean(ZipkinAutoConfiguration.SENDER_BEAN_NAME) Sender kafkaSender(KafkaProperties config) { Map properties = config.buildProducerProperties(); properties.put("key.serializer", ByteArraySerializer.class.getName()); diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/sender/ZipkinRabbitSenderConfiguration.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/sender/ZipkinRabbitSenderConfiguration.java index 3ce3bb491d..2b7da99ab5 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/sender/ZipkinRabbitSenderConfiguration.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/sender/ZipkinRabbitSenderConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2018 the original author or authors. + * Copyright 2013-2019 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. @@ -21,6 +21,7 @@ import org.springframework.boot.autoconfigure.amqp.RabbitProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.sleuth.zipkin2.ZipkinAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @@ -29,14 +30,14 @@ @Configuration @ConditionalOnBean(CachingConnectionFactory.class) -@ConditionalOnMissingBean(Sender.class) +@ConditionalOnMissingBean(name = ZipkinAutoConfiguration.SENDER_BEAN_NAME) @Conditional(ZipkinSenderCondition.class) class ZipkinRabbitSenderConfiguration { @Value("${spring.zipkin.rabbitmq.queue:zipkin}") private String queue; - @Bean + @Bean(ZipkinAutoConfiguration.SENDER_BEAN_NAME) Sender rabbitSender(CachingConnectionFactory connectionFactory, RabbitProperties config) { return RabbitMQSender.newBuilder() diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/sender/ZipkinRestTemplateSenderConfiguration.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/sender/ZipkinRestTemplateSenderConfiguration.java index c634228bc2..6c9619459f 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/sender/ZipkinRestTemplateSenderConfiguration.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/sender/ZipkinRestTemplateSenderConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2018 the original author or authors. + * Copyright 2013-2019 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. @@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; +import org.springframework.cloud.sleuth.zipkin2.ZipkinAutoConfiguration; import org.springframework.cloud.sleuth.zipkin2.ZipkinLoadBalancer; import org.springframework.cloud.sleuth.zipkin2.ZipkinProperties; import org.springframework.cloud.sleuth.zipkin2.ZipkinRestTemplateCustomizer; @@ -42,7 +43,7 @@ import zipkin2.reporter.Sender; @Configuration -@ConditionalOnMissingBean(Sender.class) +@ConditionalOnMissingBean(name = ZipkinAutoConfiguration.SENDER_BEAN_NAME) @Conditional(ZipkinSenderCondition.class) @EnableConfigurationProperties(ZipkinSenderProperties.class) class ZipkinRestTemplateSenderConfiguration { @@ -50,8 +51,7 @@ class ZipkinRestTemplateSenderConfiguration { @Autowired ZipkinUrlExtractor extractor; - @Bean - @ConditionalOnMissingBean + @Bean(ZipkinAutoConfiguration.SENDER_BEAN_NAME) public Sender restTemplateSender(ZipkinProperties zipkin, ZipkinRestTemplateCustomizer zipkinRestTemplateCustomizer) { RestTemplate restTemplate = new ZipkinRestTemplateWrapper(zipkin, this.extractor); diff --git a/spring-cloud-sleuth-zipkin/src/main/resources/META-INF/spring.factories b/spring-cloud-sleuth-zipkin/src/main/resources/META-INF/spring.factories index 7a0e23d33f..d9fa3ceb7a 100644 --- a/spring-cloud-sleuth-zipkin/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-sleuth-zipkin/src/main/resources/META-INF/spring.factories @@ -1,3 +1,4 @@ # Auto Configuration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -org.springframework.cloud.sleuth.zipkin2.ZipkinAutoConfiguration \ No newline at end of file +org.springframework.cloud.sleuth.zipkin2.ZipkinAutoConfiguration,\ + org.springframework.cloud.sleuth.zipkin2.ZipkinBackwardsCompatibilityAutoConfiguration \ No newline at end of file diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/ZipkinAutoConfigurationTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/ZipkinAutoConfigurationTests.java index 0d30d2a27f..a7f01e1160 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/ZipkinAutoConfigurationTests.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/ZipkinAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2018 the original author or authors. + * Copyright 2013-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.cloud.sleuth.zipkin2; +import java.util.List; + import brave.Span; import brave.Tracing; import brave.handler.FinishedSpanHandler; @@ -38,6 +40,10 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.mock.env.MockEnvironment; +import zipkin2.Call; +import zipkin2.codec.Encoding; +import zipkin2.reporter.AsyncReporter; +import zipkin2.reporter.Reporter; import zipkin2.reporter.Sender; import zipkin2.reporter.amqp.RabbitMQSender; import zipkin2.reporter.kafka11.KafkaSender; @@ -73,7 +79,7 @@ public void defaultsToV2Endpoint() throws Exception { this.server.url("/").toString()); this.context.register(ZipkinAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, TraceAutoConfiguration.class, - Config.class); + Config.class, ZipkinBackwardsCompatibilityAutoConfiguration.class); this.context.refresh(); Span span = this.context.getBean(Tracing.class).tracer().nextSpan().name("foo") .tag("foo", "bar").start(); @@ -100,7 +106,7 @@ public void encoderDirectsEndpoint() throws Exception { environment().setProperty("spring.zipkin.encoder", "JSON_V1"); this.context.register(ZipkinAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, TraceAutoConfiguration.class, - Config.class); + Config.class, ZipkinBackwardsCompatibilityAutoConfiguration.class); this.context.refresh(); Span span = this.context.getBean(Tracing.class).tracer().nextSpan().name("foo") .tag("foo", "bar").start(); @@ -118,8 +124,11 @@ public void encoderDirectsEndpoint() throws Exception { public void overrideRabbitMQQueue() throws Exception { this.context = new AnnotationConfigApplicationContext(); environment().setProperty("spring.zipkin.rabbitmq.queue", "zipkin2"); + environment().setProperty("spring.zipkin.sender.type", "rabbit"); this.context.register(PropertyPlaceholderAutoConfiguration.class, - RabbitAutoConfiguration.class, ZipkinAutoConfiguration.class); + RabbitAutoConfiguration.class, ZipkinAutoConfiguration.class, + TraceAutoConfiguration.class, + ZipkinBackwardsCompatibilityAutoConfiguration.class); this.context.refresh(); then(this.context.getBean(Sender.class)).isInstanceOf(RabbitMQSender.class); @@ -133,7 +142,9 @@ public void overrideKafkaTopic() throws Exception { environment().setProperty("spring.zipkin.kafka.topic", "zipkin2"); environment().setProperty("spring.zipkin.sender.type", "kafka"); this.context.register(PropertyPlaceholderAutoConfiguration.class, - KafkaAutoConfiguration.class, ZipkinAutoConfiguration.class); + KafkaAutoConfiguration.class, ZipkinAutoConfiguration.class, + TraceAutoConfiguration.class, + ZipkinBackwardsCompatibilityAutoConfiguration.class); this.context.refresh(); then(this.context.getBean(Sender.class)).isInstanceOf(KafkaSender.class); @@ -147,7 +158,8 @@ public void canOverrideBySender() throws Exception { environment().setProperty("spring.zipkin.sender.type", "web"); this.context.register(PropertyPlaceholderAutoConfiguration.class, RabbitAutoConfiguration.class, KafkaAutoConfiguration.class, - ZipkinAutoConfiguration.class); + ZipkinAutoConfiguration.class, TraceAutoConfiguration.class, + ZipkinBackwardsCompatibilityAutoConfiguration.class); this.context.refresh(); then(this.context.getBean(Sender.class).getClass().getName()) @@ -162,7 +174,8 @@ public void canOverrideBySenderAndIsCaseInsensitive() throws Exception { environment().setProperty("spring.zipkin.sender.type", "WEB"); this.context.register(PropertyPlaceholderAutoConfiguration.class, RabbitAutoConfiguration.class, KafkaAutoConfiguration.class, - ZipkinAutoConfiguration.class); + ZipkinAutoConfiguration.class, TraceAutoConfiguration.class, + ZipkinBackwardsCompatibilityAutoConfiguration.class); this.context.refresh(); then(this.context.getBean(Sender.class).getClass().getName()) @@ -176,7 +189,8 @@ public void rabbitWinsWhenKafkaPresent() throws Exception { this.context = new AnnotationConfigApplicationContext(); this.context.register(PropertyPlaceholderAutoConfiguration.class, RabbitAutoConfiguration.class, KafkaAutoConfiguration.class, - ZipkinAutoConfiguration.class); + ZipkinAutoConfiguration.class, TraceAutoConfiguration.class, + ZipkinBackwardsCompatibilityAutoConfiguration.class); this.context.refresh(); then(this.context.getBean(Sender.class)).isInstanceOf(RabbitMQSender.class); @@ -184,6 +198,90 @@ public void rabbitWinsWhenKafkaPresent() throws Exception { this.context.close(); } + @Test + public void supportsMultipleReporters() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + environment().setProperty("spring.zipkin.base-url", + this.server.url("/").toString()); + this.context.register(ZipkinAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, TraceAutoConfiguration.class, + Config.class, MultipleReportersConfig.class, + ZipkinBackwardsCompatibilityAutoConfiguration.class); + this.context.refresh(); + + then(this.context.getBeansOfType(Sender.class)).hasSize(2); + then(this.context.getBeansOfType(Sender.class)) + .containsKeys(ZipkinAutoConfiguration.SENDER_BEAN_NAME, "otherSender"); + + then(this.context.getBeansOfType(Reporter.class)).hasSize(2); + then(this.context.getBeansOfType(Reporter.class)).containsKeys( + ZipkinAutoConfiguration.REPORTER_BEAN_NAME, "otherReporter"); + + Span span = this.context.getBean(Tracing.class).tracer().nextSpan().name("foo") + .tag("foo", "bar").start(); + + span.finish(); + + Awaitility.await().untilAsserted( + () -> then(this.server.getRequestCount()).isGreaterThan(0)); + RecordedRequest request = this.server.takeRequest(); + then(request.getPath()).isEqualTo("/api/v2/spans"); + then(request.getBody().readUtf8()).contains("localEndpoint"); + + MultipleReportersConfig.OtherSender sender = this.context + .getBean(MultipleReportersConfig.OtherSender.class); + Awaitility.await().untilAsserted(() -> then(sender.isSpanSent()).isTrue()); + } + + @Test + public void supportsMultipleReportersWithBackwardsCompatibilty() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + environment().setProperty("spring.zipkin.base-url", + this.server.url("/").toString()); + this.context.register(ZipkinAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, TraceAutoConfiguration.class, + Config.class, BackwardsCompatibilityConfig.class, + ZipkinBackwardsCompatibilityAutoConfiguration.class); + this.context.refresh(); + + then(this.context.getBeansOfType(Sender.class)).hasSize(2); + then(this.context.getBeansOfType(Sender.class)) + .containsKeys(ZipkinAutoConfiguration.SENDER_BEAN_NAME, "rabbitSender"); + + then(this.context.getBeansOfType(Reporter.class)).hasSize(2); + then(this.context.getBeansOfType(Reporter.class)) + .containsKeys(ZipkinAutoConfiguration.REPORTER_BEAN_NAME, "reporter"); + } + + @Test + public void shouldOverrideDefaultBeans() { + this.context = new AnnotationConfigApplicationContext(); + this.context.register(ZipkinAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, TraceAutoConfiguration.class, + Config.class, MyConfig.class, + ZipkinBackwardsCompatibilityAutoConfiguration.class); + this.context.refresh(); + + then(this.context.getBeansOfType(Sender.class)).hasSize(1); + then(this.context.getBeansOfType(Sender.class)) + .containsKeys(ZipkinAutoConfiguration.SENDER_BEAN_NAME); + + then(this.context.getBeansOfType(Reporter.class)).hasSize(1); + then(this.context.getBeansOfType(Reporter.class)) + .containsKeys(ZipkinAutoConfiguration.REPORTER_BEAN_NAME); + + Span span = this.context.getBean(Tracing.class).tracer().nextSpan().name("foo") + .tag("foo", "bar").start(); + + span.finish(); + + Awaitility.await() + .untilAsserted(() -> then(this.server.getRequestCount()).isEqualTo(0)); + + MyConfig.MySender sender = this.context.getBean(MyConfig.MySender.class); + Awaitility.await().untilAsserted(() -> then(sender.isSpanSent()).isTrue()); + } + @Configuration protected static class Config { @@ -221,4 +319,110 @@ public boolean handle(TraceContext traceContext, MutableSpan span) { } + @Configuration + protected static class MultipleReportersConfig { + + @Bean + Reporter otherReporter() { + return AsyncReporter.create(otherSender()); + } + + @Bean + OtherSender otherSender() { + return new OtherSender(); + } + + static class OtherSender extends Sender { + + private boolean spanSent = false; + + boolean isSpanSent() { + return this.spanSent; + } + + @Override + public Encoding encoding() { + return Encoding.JSON; + } + + @Override + public int messageMaxBytes() { + return Integer.MAX_VALUE; + } + + @Override + public int messageSizeInBytes(List encodedSpans) { + return encoding().listSizeInBytes(encodedSpans); + } + + @Override + public Call sendSpans(List encodedSpans) { + this.spanSent = true; + return Call.create(null); + } + + } + + } + + @Configuration + protected static class BackwardsCompatibilityConfig { + + @Bean + Sender rabbitSender() { + return RabbitMQSender.create("localhost"); + } + + } + + // tag::override_default_beans[] + + @Configuration + protected static class MyConfig { + + @Bean(ZipkinAutoConfiguration.REPORTER_BEAN_NAME) + Reporter myReporter() { + return AsyncReporter.create(mySender()); + } + + @Bean(ZipkinAutoConfiguration.SENDER_BEAN_NAME) + MySender mySender() { + return new MySender(); + } + + static class MySender extends Sender { + + private boolean spanSent = false; + + boolean isSpanSent() { + return this.spanSent; + } + + @Override + public Encoding encoding() { + return Encoding.JSON; + } + + @Override + public int messageMaxBytes() { + return Integer.MAX_VALUE; + } + + @Override + public int messageSizeInBytes(List encodedSpans) { + return encoding().listSizeInBytes(encodedSpans); + } + + @Override + public Call sendSpans(List encodedSpans) { + this.spanSent = true; + return Call.create(null); + } + + } + + } + + // end::override_default_beans[] + }