diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 190b2fc9d..a4ef51530 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -33,7 +33,7 @@ 1.8 1.8 2.3.0.BUILD-SNAPSHOT - 5.9.3 + 5.9.5 3.14.6 diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java index 7f095b779..fa013b7c8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java @@ -30,6 +30,7 @@ import reactor.core.Scannable; import reactor.core.publisher.Operators; import reactor.util.context.Context; +import org.springframework.cloud.sleuth.internal.LazyBean; import org.springframework.context.ConfigurableApplicationContext; /** @@ -70,8 +71,8 @@ public abstract class ReactorSleuth { // keep a reference outside the lambda so that any caching will be visible to // all publishers - LazyBean lazyCurrentTraceContext = new LazyBean<>( - springContext, CurrentTraceContext.class); + LazyBean lazyCurrentTraceContext = LazyBean + .create(springContext, CurrentTraceContext.class); return Operators.liftPublisher((p, sub) -> { // We don't scope scalar results as they happen in an instant. This prevents diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ScopePassingSpanSubscriber.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ScopePassingSpanSubscriber.java index 2e24dc278..a1b587052 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ScopePassingSpanSubscriber.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ScopePassingSpanSubscriber.java @@ -54,7 +54,9 @@ final class ScopePassingSpanSubscriber implements SpanSubscription, Scanna this.subscriber = subscriber; this.currentTraceContext = currentTraceContext; this.parent = parent; - this.context = parent != null ? ctx.put(TraceContext.class, parent) : ctx; + this.context = parent != null + && !parent.equals(ctx.getOrDefault(TraceContext.class, null)) + ? ctx.put(TraceContext.class, parent) : ctx; if (log.isTraceEnabled()) { log.trace("Parent span [" + parent + "], context [" + this.context + "]"); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpClientBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpClientBeanPostProcessor.java index dcf21694e..a358784da 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpClientBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpClientBeanPostProcessor.java @@ -16,70 +16,94 @@ package org.springframework.cloud.sleuth.instrument.web.client; -import java.util.List; +import java.net.InetSocketAddress; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.function.BiFunction; import brave.Span; -import brave.Tracer; import brave.http.HttpClientHandler; import brave.http.HttpTracing; +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.TraceContext; import io.netty.bootstrap.Bootstrap; import reactor.core.publisher.Mono; import reactor.netty.Connection; import reactor.netty.http.client.HttpClient; import reactor.netty.http.client.HttpClientRequest; import reactor.netty.http.client.HttpClientResponse; +import reactor.util.context.Context; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.cloud.sleuth.internal.LazyBean; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.lang.Nullable; class HttpClientBeanPostProcessor implements BeanPostProcessor { - private final BeanFactory beanFactory; + final ConfigurableApplicationContext springContext; - HttpClientBeanPostProcessor(BeanFactory beanFactory) { - this.beanFactory = beanFactory; + HttpClientBeanPostProcessor(ConfigurableApplicationContext springContext) { + this.springContext = springContext; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + LazyBean httpTracing = LazyBean.create(this.springContext, + HttpTracing.class); if (bean instanceof HttpClient) { - return ((HttpClient) bean).mapConnect(new TracingMapConnect(this.beanFactory)) - .doOnRequest(TracingDoOnRequest.create(this.beanFactory)) - .doOnRequestError(TracingDoOnErrorRequest.create(this.beanFactory)) - .doOnResponse(TracingDoOnResponse.create(this.beanFactory)) - .doOnResponseError(TracingDoOnErrorResponse.create(this.beanFactory)); + // This adds handlers to manage the span lifecycle. All require explicit + // propagation of the current span as a reactor context property. + // This done in mapConnect, added last so that it is setup first. + // https://projectreactor.io/docs/core/release/reference/#_simple_context_examples + return ((HttpClient) bean) + .doOnResponseError(new TracingDoOnErrorResponse(httpTracing)) + .doOnResponse(new TracingDoOnResponse(httpTracing)) + .doOnRequestError(new TracingDoOnErrorRequest(httpTracing)) + .doOnRequest(new TracingDoOnRequest(httpTracing)) + .mapConnect(new TracingMapConnect(httpTracing)); } return bean; } + /** current client span, cleared on completion. */ + private static final class CurrentClientSpan extends AtomicReference { + + } + private static class TracingMapConnect implements BiFunction, Bootstrap, Mono> { - private final BeanFactory beanFactory; + final LazyBean httpTracing; - private Tracer tracer; + CurrentTraceContext currentTraceContext; - TracingMapConnect(BeanFactory beanFactory) { - this.beanFactory = beanFactory; + TracingMapConnect(LazyBean httpTracing) { + this.httpTracing = httpTracing; } @Override public Mono apply(Mono mono, Bootstrap bootstrap) { - return mono.subscriberContext(context -> context.put(AtomicReference.class, - new AtomicReference<>(tracer().currentSpan()))); + return mono.subscriberContext(context -> { + TraceContext invocationContext = currentTraceContext().get(); + if (invocationContext != null) { + // Read in this processor and also in ScopePassingSpanSubscriber + context = context.put(TraceContext.class, invocationContext); + } + return context.put(CurrentClientSpan.class, new CurrentClientSpan()); + }); } - private Tracer tracer() { - if (this.tracer == null) { - this.tracer = this.beanFactory.getBean(Tracer.class); + CurrentTraceContext currentTraceContext() { + if (this.currentTraceContext == null) { + this.currentTraceContext = this.httpTracing.get().tracing() + .currentTraceContext(); } - return this.tracer; + return this.currentTraceContext; } } @@ -87,59 +111,64 @@ class HttpClientBeanPostProcessor implements BeanPostProcessor { private static class TracingDoOnRequest implements BiConsumer { - final BeanFactory beanFactory; - - HttpTracing httpTracing; - - List propagationKeys; + final LazyBean httpTracing; HttpClientHandler handler; - TracingDoOnRequest(BeanFactory beanFactory) { - this.beanFactory = beanFactory; + TracingDoOnRequest(LazyBean httpTracing) { + this.httpTracing = httpTracing; } - static TracingDoOnRequest create(BeanFactory beanFactory) { - return new TracingDoOnRequest(beanFactory); - } - - private HttpTracing httpTracing() { - if (this.httpTracing == null) { - this.httpTracing = this.beanFactory.getBean(HttpTracing.class); - } - return this.httpTracing; - } - - private List propagationKeys() { - if (this.propagationKeys == null) { - this.propagationKeys = httpTracing().tracing().propagation().keys(); - } - return this.propagationKeys; - } - - private HttpClientHandler handler() { + HttpClientHandler handler() { if (this.handler == null) { - this.handler = HttpClientHandler.create(httpTracing()); + this.handler = HttpClientHandler.create(httpTracing.get()); } return this.handler; } + CurrentTraceContext currentTraceContext() { + return httpTracing.get().tracing().currentTraceContext(); + } + @Override public void accept(HttpClientRequest req, Connection connection) { - // request already instrumented - // TODO: consider another, cheaper way, like flagging a context - // property. If not, comment why. - for (String key : propagationKeys()) { - if (req.requestHeaders().contains(key)) { - return; - } + CurrentClientSpan ref = req.currentContext() + .getOrDefault(CurrentClientSpan.class, null); + if (ref == null) { // Somehow TracingMapConnect was not invoked.. skip out + return; } - AtomicReference reference = req.currentContext() - .getOrDefault(AtomicReference.class, new AtomicReference<>()); + + // This might be re-entrant on auto-redirect or connection retry: + // See reactor/reactor-netty#1000 for follow-ups. + Span clientSpan = ref.getAndSet(null); + if (clientSpan != null) { + // Retry from a connect fail wouldn't have parsed the request, leading to + // an empty span with no data if we finished it. An auto-redirect would + // have parsed the request, but we have no idea which status code it + // finished with. Since we can't see the preceding request state, we + // abandon its span in favor of the next. + clientSpan.abandon(); + } + + // Start a new client span with the appropriate parent + TraceContext parent = req.currentContext().getOrDefault(TraceContext.class, + null); WrappedHttpClientRequest request = new WrappedHttpClientRequest(req); - Span span = reference.get() == null ? handler().handleSend(request) - : handler().handleSend(request, reference.get()); - reference.set(span); + + // Simplify after openzipkin/brave#1082 + try (Scope ws = currentTraceContext().maybeScope(parent)) { + clientSpan = handler().handleSend(request); + parseConnectionAddress(connection, clientSpan); + ref.set(clientSpan); + } + } + + static void parseConnectionAddress(Connection connection, Span span) { + if (span.isNoop()) { + return; + } + InetSocketAddress socketAddress = connection.address(); + span.remoteIpAndPort(socketAddress.getHostString(), socketAddress.getPort()); } } @@ -147,17 +176,13 @@ class HttpClientBeanPostProcessor implements BeanPostProcessor { private static class TracingDoOnResponse extends AbstractTracingDoOnHandler implements BiConsumer { - TracingDoOnResponse(BeanFactory beanFactory) { - super(beanFactory); - } - - static TracingDoOnResponse create(BeanFactory beanFactory) { - return new TracingDoOnResponse(beanFactory); + TracingDoOnResponse(LazyBean httpTracing) { + super(httpTracing); } @Override - public void accept(HttpClientResponse httpClientResponse, Connection connection) { - handle(httpClientResponse, null); + public void accept(HttpClientResponse response, Connection connection) { + handle(response.currentContext(), response, null); } } @@ -165,17 +190,13 @@ class HttpClientBeanPostProcessor implements BeanPostProcessor { private static class TracingDoOnErrorRequest extends AbstractTracingDoOnHandler implements BiConsumer { - TracingDoOnErrorRequest(BeanFactory beanFactory) { - super(beanFactory); - } - - static TracingDoOnErrorRequest create(BeanFactory beanFactory) { - return new TracingDoOnErrorRequest(beanFactory); + TracingDoOnErrorRequest(LazyBean httpTracing) { + super(httpTracing); } @Override - public void accept(HttpClientRequest request, Throwable throwable) { - handle(null, throwable); + public void accept(HttpClientRequest req, Throwable error) { + handle(req.currentContext(), null, error); } } @@ -183,59 +204,48 @@ class HttpClientBeanPostProcessor implements BeanPostProcessor { private static class TracingDoOnErrorResponse extends AbstractTracingDoOnHandler implements BiConsumer { - TracingDoOnErrorResponse(BeanFactory beanFactory) { - super(beanFactory); - } - - static TracingDoOnErrorResponse create(BeanFactory beanFactory) { - return new TracingDoOnErrorResponse(beanFactory); + TracingDoOnErrorResponse(LazyBean httpTracing) { + super(httpTracing); } @Override - public void accept(HttpClientResponse httpClientResponse, Throwable throwable) { - handle(httpClientResponse, throwable); + public void accept(HttpClientResponse response, Throwable error) { + handle(response.currentContext(), response, error); } } private static abstract class AbstractTracingDoOnHandler { - final BeanFactory beanFactory; - - HttpTracing httpTracing; + final LazyBean httpTracing; HttpClientHandler handler; - AbstractTracingDoOnHandler(BeanFactory beanFactory) { - this.beanFactory = beanFactory; + AbstractTracingDoOnHandler(LazyBean httpTracing) { + this.httpTracing = httpTracing; } - private HttpTracing httpTracing() { - if (this.httpTracing == null) { - this.httpTracing = this.beanFactory.getBean(HttpTracing.class); - } - return this.httpTracing; - } - - private HttpClientHandler handler() { + HttpClientHandler handler() { if (this.handler == null) { - this.handler = HttpClientHandler.create(httpTracing()); + this.handler = HttpClientHandler.create(httpTracing.get()); } return this.handler; } - protected void handle(HttpClientResponse httpClientResponse, - Throwable throwable) { - if (httpClientResponse == null) { + void handle(Context context, @Nullable HttpClientResponse resp, + @Nullable Throwable error) { + CurrentClientSpan ref = context.getOrDefault(CurrentClientSpan.class, null); + if (ref == null) { // Somehow TracingMapConnect was not invoked.. skip out return; } - AtomicReference reference = httpClientResponse.currentContext() - .getOrDefault(AtomicReference.class, null); - if (reference == null || reference.get() == null) { - return; + + Span clientSpan = ref.getAndSet(null); + if (clientSpan == null) { + return; // Unexpected. In the handle method, without a span to finish! } - handler().handleReceive(new WrappedHttpClientResponse(httpClientResponse), - throwable, (Span) reference.get()); + WrappedHttpClientResponse response = resp != null + ? new WrappedHttpClientResponse(resp) : null; + handler().handleReceive(response, error, clientSpan); } } @@ -260,12 +270,12 @@ class HttpClientBeanPostProcessor implements BeanPostProcessor { @Override public String path() { - return delegate.path(); + return "/" + delegate.path(); // TODO: reactor/reactor-netty#999 } @Override public String url() { - return delegate.uri(); + return delegate.resourceUrl(); } @Override @@ -288,6 +298,11 @@ class HttpClientBeanPostProcessor implements BeanPostProcessor { this.delegate = delegate; } + @Override + public String method() { + return delegate.method().name(); + } + @Override public Object unwrap() { return delegate; diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java index ef596dddf..bb59752b7 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java @@ -162,8 +162,8 @@ public class TraceWebClientAutoConfiguration { @Bean static HttpClientBeanPostProcessor httpClientBeanPostProcessor( - BeanFactory beanFactory) { - return new HttpClientBeanPostProcessor(beanFactory); + ConfigurableApplicationContext springContext) { + return new HttpClientBeanPostProcessor(springContext); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientBeanPostProcessor.java index 558f384cf..e50f31fc0 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientBeanPostProcessor.java @@ -18,6 +18,7 @@ package org.springframework.cloud.sleuth.instrument.web.client; import java.util.Collections; import java.util.List; +import java.util.concurrent.CancellationException; import java.util.function.Consumer; import java.util.function.Function; @@ -131,9 +132,14 @@ final class TraceExchangeFilterFunction implements ExchangeFilterFunction { } }; - private static final String CLIENT_SPAN_KEY = "sleuth.webclient.clientSpan"; + static final String CLIENT_SPAN_KEY = "sleuth.webclient.clientSpan"; - private static final String CANCELLED_SUBSCRIPTION_ERROR = "CANCELLED"; + static final Exception CANCELLED_ERROR = new CancellationException("CANCELLED") { + @Override + public Throwable fillInStackTrace() { + return this; // stack trace doesn't add value here + } + }; final ConfigurableApplicationContext springContext; @@ -170,6 +176,8 @@ final class TraceExchangeFilterFunction implements ExchangeFilterFunction { } MonoWebClientTrace trace = new MonoWebClientTrace(next, wrapper.buildRequest(), this, span); + // TODO: investigate why this commit leaks a scope: + // 8f5bcdabd7af23df443e771432eb85597f3b3076 tracer().withSpanInScope(parentSpan); return trace; } @@ -356,13 +364,14 @@ final class TraceExchangeFilterFunction implements ExchangeFilterFunction { return this.context; } - void handleReceive(Span clientSpan, ClientResponse clientResponse, - Throwable throwable) { + void handleReceive(Span clientSpan, @Nullable ClientResponse res, + @Nullable Throwable error) { if (log.isTraceEnabled()) { log.trace("Handling receive"); } - this.handler.handleReceive(new HttpClientResponse(clientResponse), - throwable, clientSpan); + HttpClientResponse response = res != null ? new HttpClientResponse(res) + : null; + this.handler.handleReceive(response, error, clientSpan); if (log.isTraceEnabled()) { log.trace("Closed scope"); } @@ -374,32 +383,31 @@ final class TraceExchangeFilterFunction implements ExchangeFilterFunction { + this.span + "]"); } - this.span.tag("error", CANCELLED_SUBSCRIPTION_ERROR); - handleReceive(this.span, null, null); + handleReceive(this.span, null, CANCELLED_ERROR); } void terminateSpan(@Nullable ClientResponse clientResponse, - @Nullable Throwable throwable) { + @Nullable Throwable error) { if (clientResponse == null) { if (log.isDebugEnabled()) { log.debug("No response was returned. Will close the span [" + this.span + "]"); } - handleReceive(this.span, clientResponse, throwable); + handleReceive(this.span, null, error); return; } int statusCode = clientResponse.rawStatusCode(); - boolean error = statusCode >= 400; - if (error) { + boolean isHttpError = statusCode >= 400; + if (isHttpError) { if (log.isDebugEnabled()) { log.debug( "Non positive status code was returned from the call. Will close the span [" + this.span + "]"); } - throwable = new RestClientException( + error = new RestClientException( "Status code of the response is [" + statusCode + "]"); } - handleReceive(this.span, clientResponse, throwable); + handleReceive(this.span, clientResponse, error); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignClient.java index 54cdc172a..58b15a604 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignClient.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignClient.java @@ -99,9 +99,13 @@ final class TracingFeignClient implements Client { Throwable error = null; try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { Response res = this.delegate.execute(request.build(), options); - if (res != null) { // possibly null on bad implementation or mocks + if (res != null) { response = new HttpClientResponse(res); } + else { // possibly null on bad implementation or mocks + response = new HttpClientResponse( + Response.builder().request(req).build()); + } return res; } catch (IOException | RuntimeException | Error e) { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/LazyBean.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/internal/LazyBean.java similarity index 83% rename from spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/LazyBean.java rename to spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/internal/LazyBean.java index 364d18648..8a55e0b31 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/LazyBean.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/internal/LazyBean.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.sleuth.instrument.reactor; +package org.springframework.cloud.sleuth.internal; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -25,8 +25,16 @@ import org.springframework.lang.Nullable; /** * Avoids calling the expensive {@link ConfigurableApplicationContext#getBean(Class)} many * times or throwing an exception. + * + *

+ * Note: This is an internal class to sleuth and must not be used by external code. */ -final class LazyBean { +public final class LazyBean { + + public static LazyBean create(ConfigurableApplicationContext springContext, + Class requiredType) { + return new LazyBean<>(springContext, requiredType); + } // spring-jcl uses commons-logging, so do we. private static final Log log = LogFactory.getLog(LazyBean.class); @@ -47,7 +55,7 @@ final class LazyBean { * @return the bean value or null if there was an exception getting it. */ @Nullable - T get() { + public T get() { if (this.value != null) { return this.value; } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/ReactorNettyHttpClientSpringBootTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/ReactorNettyHttpClientSpringBootTests.java new file mode 100644 index 000000000..efecdd11b --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/ReactorNettyHttpClientSpringBootTests.java @@ -0,0 +1,206 @@ +/* + * 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 + * + * 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.cloud.sleuth.instrument.web.client; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import brave.propagation.B3SinglePropagation; +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.Propagation; +import brave.propagation.TraceContext; +import brave.sampler.Sampler; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.netty.DisposableServer; +import reactor.netty.http.client.HttpClient; +import reactor.netty.http.client.HttpClientResponse; +import reactor.netty.http.client.PrematureCloseException; +import reactor.netty.http.server.HttpServer; +import zipkin2.Span; +import zipkin2.reporter.Reporter; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * This tests {@link HttpClient} instrumentation performed by + * {@link HttpClientBeanPostProcessor}, as wired by auto-configuration. + * + *

+ * Note: {@link HttpClient} can be an implementation of {@link WebClient}, so + * care should be taken to also test that integration. For example, it would be easy to + * create duplicate client spans for the same request. + */ +@SpringBootTest(classes = ReactorNettyHttpClientSpringBootTests.TestConfiguration.class, + webEnvironment = SpringBootTest.WebEnvironment.NONE) +@RunWith(SpringRunner.class) +public class ReactorNettyHttpClientSpringBootTests { + + DisposableServer disposableServer; + + @Autowired + HttpClient httpClient; + + @Autowired + BlockingQueue spans; + + @Autowired + CurrentTraceContext currentTraceContext; + + TraceContext context = TraceContext.newBuilder().traceId(1).spanId(1).sampled(true) + .build(); + + @After + public void tearDown() { + if (disposableServer != null) { + disposableServer.disposeNow(); + } + this.spans.clear(); + } + + @Test + public void shouldRecordRemoteEndpoint() throws Exception { + disposableServer = HttpServer.create().port(0) + .handle((in, out) -> out.sendString(Flux.just("foo"))).bindNow(); + + HttpClientResponse response = httpClient.port(disposableServer.port()).get() + .uri("/").response().block(); + + assertThat(response.status()).isEqualTo(HttpResponseStatus.OK); + + Span clientSpan = takeClientSpan(); + + assertThat(clientSpan.remoteEndpoint()).satisfiesAnyOf( + ep -> assertThat(ep.ipv4()).isNotNull(), + ep -> assertThat(ep.ipv6()).isNotNull()); + assertThat(clientSpan.remoteEndpoint().portAsInt()).isNotZero(); + } + + @Test + public void shouldUseInvocationContext() throws Exception { + disposableServer = HttpServer.create().port(0) + // this reads the trace context header, b3, returning it in the response + .handle((in, out) -> out + .sendString(Flux.just(in.requestHeaders().get("b3")))) + .bindNow(); + + String b3SingleHeaderReadByServer; + try (Scope ws = currentTraceContext.newScope(context)) { + b3SingleHeaderReadByServer = httpClient.port(disposableServer.port()).get() + .uri("/").responseContent().aggregate().asString().block(); + } + + Span clientSpan = takeClientSpan(); + + assertThat(b3SingleHeaderReadByServer).isEqualTo(context.traceIdString() + "-" + + clientSpan.id() + "-1-" + context.spanIdString()); + } + + @Test + public void shouldSendTraceContextToServer_rootSpan() throws Exception { + disposableServer = HttpServer.create().port(0) + // this reads the trace context header, b3, returning it in the response + .handle((in, out) -> out + .sendString(Flux.just(in.requestHeaders().get("b3")))) + .bindNow(); + + Mono request = httpClient.port(disposableServer.port()).get().uri("/") + .responseContent().aggregate().asString(); + + String b3SingleHeaderReadByServer = request.block(); + + Span clientSpan = takeClientSpan(); + + assertThat(b3SingleHeaderReadByServer) + .isEqualTo(clientSpan.traceId() + "-" + clientSpan.id() + "-1"); + } + + @Test + public void shouldTagOnRequestError() throws InterruptedException { + disposableServer = HttpServer.create().port(0).handle((req, resp) -> { + throw new RuntimeException("test"); + }).bindNow(); + + Mono request = httpClient.port(disposableServer.port()).get().uri("/") + .responseContent().aggregate().asString(); + + assertThatThrownBy(request::block) + .hasCauseInstanceOf(PrematureCloseException.class); + + Span clientSpan = takeClientSpan(); + + assertThat(clientSpan.tags()).containsKey("error"); + } + + /** Call this to block until a span was reported */ + Span takeClientSpan() throws InterruptedException { + Span result = spans.poll(1, TimeUnit.SECONDS); + assertThat(result).withFailMessage("Span was not reported").isNotNull(); + assertThat(result.kind()).isEqualTo(Span.Kind.CLIENT); + return result; + } + + @Configuration + @EnableAutoConfiguration + static class TestConfiguration { + + @Bean + Propagation.Factory propagationFactory() { + return B3SinglePropagation.FACTORY; + } + + @Bean + Sampler sampler() { + return Sampler.ALWAYS_SAMPLE; + } + + /** + * Use a blocking queue as it is simpler than wrapping everything in awaitility + */ + @Bean + BlockingQueue spans() { + return new LinkedBlockingQueue<>(); + } + + @Bean + Reporter spanReporter(BlockingQueue spans) { + return spans::add; + } + + @Bean + HttpClient reactorHttpClient() { + return HttpClient.create(); + } + + } + +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignClientTests.java index d195b7bd0..53dc877cd 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignClientTests.java @@ -17,8 +17,9 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; import java.io.IOException; -import java.nio.charset.Charset; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import brave.Span; import brave.Tracer; @@ -36,9 +37,7 @@ import org.mockito.BDDMockito; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.beans.factory.BeanFactory; import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; -import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import static org.assertj.core.api.BDDAssertions.then; @@ -48,15 +47,16 @@ import static org.assertj.core.api.BDDAssertions.then; @RunWith(MockitoJUnitRunner.class) public class TracingFeignClientTests { - ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Request request = Request.create("GET", "https://foo", new HashMap<>(), null, null); - @Mock - BeanFactory beanFactory; + Request.Options options = new Request.Options(); + + List spans = new ArrayList<>(); Tracing tracing = Tracing.newBuilder() .currentTraceContext(ThreadLocalCurrentTraceContext.newBuilder() .addScopeDecorator(StrictScopeDecorator.create()).build()) - .spanReporter(this.reporter).build(); + .spanReporter(spans::add).build(); Tracer tracer = this.tracing.tracer(); @@ -78,17 +78,13 @@ public class TracingFeignClientTests { Span span = this.tracer.nextSpan().name("foo"); try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { - this.traceFeignClient - .execute( - Request.create("GET", "https://foo", new HashMap<>(), - "".getBytes(), Charset.defaultCharset()), - new Request.Options()); + this.traceFeignClient.execute(this.request, this.options); } finally { span.finish(); } - then(this.reporter.getSpans().get(0)).extracting("kind.ordinal") + then(spans.get(0)).extracting("kind.ordinal") .isEqualTo(Span.Kind.CLIENT.ordinal()); } @@ -99,11 +95,7 @@ public class TracingFeignClientTests { .willThrow(new RuntimeException("exception has occurred")); try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { - this.traceFeignClient - .execute( - Request.create("GET", "https://foo", new HashMap<>(), - "".getBytes(), Charset.defaultCharset()), - new Request.Options()); + this.traceFeignClient.execute(this.request, this.options); BDDAssertions.fail("Exception should have been thrown"); } catch (Exception e) { @@ -112,21 +104,17 @@ public class TracingFeignClientTests { span.finish(); } - then(this.reporter.getSpans().get(0)).extracting("kind.ordinal") + then(this.spans.get(0)).extracting("kind.ordinal") .isEqualTo(Span.Kind.CLIENT.ordinal()); - then(this.reporter.getSpans().get(0).tags()).containsEntry("error", - "exception has occurred"); + then(this.spans.get(0).tags()).containsEntry("error", "exception has occurred"); } @Test public void should_shorten_the_span_name() throws IOException { - this.traceFeignClient - .execute( - Request.create("GET", "https://foo/" + bigName(), new HashMap<>(), - "".getBytes(), Charset.defaultCharset()), - new Request.Options()); + this.traceFeignClient.execute(Request.create("GET", "https://foo/" + bigName(), + new HashMap<>(), null, null), this.options); - then(this.reporter.getSpans().get(0).name()).hasSize(50); + then(this.spans.get(0).name()).hasSize(50); } private String bigName() { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java index b335b2443..f25ed70b5 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java @@ -36,6 +36,7 @@ import brave.propagation.TraceContextOrSamplingFlags; import brave.sampler.Sampler; import junitparams.JUnitParamsRunner; import junitparams.Parameters; +import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; @@ -51,8 +52,8 @@ import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import reactor.netty.http.client.HttpClient; -import reactor.netty.http.client.HttpClientResponse; +import org.reactivestreams.Subscription; +import reactor.core.publisher.BaseSubscriber; import zipkin2.Annotation; import zipkin2.reporter.Reporter; @@ -112,8 +113,7 @@ public class WebClientTests { static final String SAMPLED_NAME = "X-B3-Sampled"; static final String PARENT_ID_NAME = "X-B3-ParentSpanId"; - private static final org.apache.commons.logging.Log log = LogFactory - .getLog(WebClientTests.class); + private static final Log log = LogFactory.getLog(WebClientTests.class); @Rule public final SpringMethodRule springMethodRule = new SpringMethodRule(); @@ -134,9 +134,6 @@ public class WebClientTests { @Autowired HttpClientBuilder httpClientBuilder; // #845 - @Autowired - HttpClient nettyHttpClient; - @Autowired HttpAsyncClientBuilder httpAsyncClientBuilder; // #845 @@ -276,30 +273,6 @@ public class WebClientTests { then(this.reporter.getSpans()).isNotEmpty(); } - @Test - @SuppressWarnings("unchecked") - public void shouldAttachTraceIdWhenCallingAnotherServiceForNettyHttpClient() - throws Exception { - Span span = this.tracer.nextSpan().name("foo").start(); - - try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { - HttpClientResponse response = this.nettyHttpClient.get() - .uri("http://localhost:" + this.port).response().block(); - - then(response).isNotNull(); - } - - Awaitility.await().untilAsserted(() -> { - then(this.tracer.currentSpan()).isNull(); - System.out.println("Collected span " + this.reporter.getSpans()); - then(this.reporter.getSpans()).isNotEmpty() - .extracting("traceId", String.class) - // we can have some bizarre spans popping up - .contains(span.context().traceIdString()); - then(this.reporter.getSpans()).extracting("kind.name").contains("CLIENT"); - }); - } - @Test @SuppressWarnings("unchecked") public void shouldAttachTraceIdWhenCallingAnotherServiceForHttpClient() @@ -378,7 +351,7 @@ public class WebClientTests { @Test @SuppressWarnings("unchecked") - public void shouldWorkWhenCustomStatusCodeIsReturned() throws InterruptedException { + public void shouldWorkWhenCustomStatusCodeIsReturned() { Span span = this.tracer.nextSpan().name("foo").start(); try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { @@ -397,6 +370,21 @@ public class WebClientTests { .contains("CLIENT"); } + @Test + public void shouldTagOnCancel() { + this.webClient.get().uri("http://localhost:" + this.port + "/doNotSkip") + .retrieve().bodyToMono(String.class) + .subscribe(new BaseSubscriber() { + @Override + protected void hookOnSubscribe(Subscription subscription) { + cancel(); + } + }); + + then(this.reporter.getSpans()).isNotEmpty(); + then(this.reporter.getSpans().get(0).tags()).containsEntry("error", "CANCELLED"); + } + @Test public void shouldRespectSkipPattern() { this.webClient.get().uri("http://localhost:" + this.port + "/skip").retrieve() @@ -599,11 +587,6 @@ public class WebClientTests { return new MyRestTemplateCustomizer(); } - @Bean - HttpClient reactorHttpClient() { - return HttpClient.create(); - } - } static class MyRestTemplateCustomizer implements RestTemplateCustomizer { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/reactor/LazyBeanTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/internal/LazyBeanTests.java similarity index 95% rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/reactor/LazyBeanTests.java rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/internal/LazyBeanTests.java index 3f9694ba6..db03fb410 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/reactor/LazyBeanTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/internal/LazyBeanTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.sleuth.instrument.reactor; +package org.springframework.cloud.sleuth.internal; import brave.propagation.CurrentTraceContext; import org.junit.Test; diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 7b8985958..905e78763 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -31,7 +31,7 @@ spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 5.9.3 + 5.9.5 0.35.1 3.4.1 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 deleted file mode 100644 index e58326656..000000000 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/ZipkinBackwardsCompatibilityAutoConfiguration.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * 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 - * - * 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.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.AutoConfigureBefore; -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 left for backward compatibility - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnProperty(value = { "spring.sleuth.enabled", "spring.zipkin.enabled" }, - matchIfMissing = true) -@AutoConfigureBefore(ZipkinAutoConfiguration.class) -@Deprecated -public class ZipkinBackwardsCompatibilityAutoConfiguration { - - /** - * Reporter that is depending on a {@link Sender} bean which is created in another - * auto-configuration than {@link ZipkinAutoConfiguration}. - * @param reporterMetrics metrics - * @param zipkin zipkin properties - * @param spanBytesEncoder encoder - * @param beanFactory Spring's Bean Factory - * @return span reporter - * @deprecated left for backwards compatibility - */ - @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. - * @param zipkinProperties zipkin properties - * @return bytes encoder - * @deprecated left for backwards compatibility - */ - @Bean - @ConditionalOnMissingBean - @Deprecated - BytesEncoder spanBytesEncoder(ZipkinProperties zipkinProperties) { - return zipkinProperties.getEncoder(); - } - - /** - * Deprecated because this is moved to {@link TraceAutoConfiguration}. Left for - * backwards compatibility reasons. - * @return reporter metrics - * @deprecated left for backwards compatibility - */ - @Bean - @ConditionalOnMissingBean - @Deprecated - ReporterMetrics zipkinReporterMetrics() { - return new InMemoryReporterMetrics(); - } - - /** - * Old approach: - one sender - one reporter - * - * This auto configuration verifies if we have the old approach. In which case we - * define the missing beans. - * - * In case of having 0 or more than 1 sender and there is a reporter, we don't need to - * use the backward compatibility bean setup. - */ - 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. - // Since this config is adapting the old config we're searching for exactly 1 - // `Sender` bean before `ZipkinAutoConfiguration` kicks in. - if (foundSenders != 1) { - return ConditionOutcome.noMatch( - "None or multiple Sender beans found - no reason to apply backwards compatibility"); - } - int foundReporters = listableBeanFactory - .getBeanNamesForType(Reporter.class).length; - // Check if we need to provide a Reporter bean for the overridden Sender bean - if (foundReporters > 0) { - return ConditionOutcome.noMatch( - "The old config setup already defines its own Reporter bean"); - } - return ConditionOutcome.match(); - } - - } - -} 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 d36fcbaca..f2608fbe3 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,4 +1,3 @@ # Auto Configuration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -org.springframework.cloud.sleuth.zipkin2.ZipkinAutoConfiguration,\ -org.springframework.cloud.sleuth.zipkin2.ZipkinBackwardsCompatibilityAutoConfiguration +org.springframework.cloud.sleuth.zipkin2.ZipkinAutoConfiguration diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/ZipkinBackwardsCompatibilityAutoConfigurationTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/ZipkinBackwardsCompatibilityAutoConfigurationTests.java deleted file mode 100644 index 934c9ff9b..000000000 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/ZipkinBackwardsCompatibilityAutoConfigurationTests.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 - * - * 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.cloud.sleuth.zipkin2; - -import org.junit.Test; -import zipkin2.codec.BytesEncoder; -import zipkin2.reporter.Reporter; -import zipkin2.reporter.ReporterMetrics; - -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Tim Ysewyn - */ -public class ZipkinBackwardsCompatibilityAutoConfigurationTests { - - private ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of( - ZipkinBackwardsCompatibilityAutoConfiguration.class, - ZipkinAutoConfiguration.class, TraceAutoConfiguration.class)); - - @Test - public void shouldLoadBeans() { - this.contextRunner.run(context -> { - assertThat(context.getBean(ZipkinProperties.class)).isNotNull(); - assertThat(context.getBean(Reporter.class)).isNotNull(); - assertThat(context.getBean(BytesEncoder.class)).isNotNull(); - assertThat(context.getBean(ReporterMetrics.class)).isNotNull(); - }); - } - - @Test - public void shouldNotLoadBackwardsCompatibilityConfigWhenZipkinDisabled() { - this.contextRunner.withPropertyValues("spring.zipkin.enabled=false") - .run(context -> { - assertThat(context.getBeansOfType(ZipkinProperties.class)).isEmpty(); - assertThat(context.getBeansOfType(BytesEncoder.class)).isEmpty(); - assertThat(context.getBean(ReporterMetrics.class)).isNotNull(); // TraceAutoConfiguration - assertThat(context.getBean(Reporter.class)).isNotNull(); // noOpSpanReporter - }); - } - - @Test - public void shouldNotLoadBackwardsCompatibilityConfigWhenSleuthDisabled() { - this.contextRunner.withPropertyValues("spring.sleuth.enabled=false") - .run(context -> { - assertThat(context.getBeansOfType(ZipkinProperties.class)).isEmpty(); - assertThat(context.getBeansOfType(BytesEncoder.class)).isEmpty(); - assertThat(context.getBeansOfType(ReporterMetrics.class)).isEmpty(); - assertThat(context.getBeansOfType(Reporter.class)).isEmpty(); - }); - } - -}