Merge branch '2.2.x'
This commit is contained in:
@@ -33,7 +33,7 @@
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<spring-boot.version>2.3.0.BUILD-SNAPSHOT</spring-boot.version>
|
||||
<brave.version>5.9.3</brave.version>
|
||||
<brave.version>5.9.5</brave.version>
|
||||
<okhttp.version>3.14.6</okhttp.version>
|
||||
</properties>
|
||||
|
||||
|
||||
@@ -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<CurrentTraceContext> lazyCurrentTraceContext = new LazyBean<>(
|
||||
springContext, CurrentTraceContext.class);
|
||||
LazyBean<CurrentTraceContext> 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
|
||||
|
||||
@@ -54,7 +54,9 @@ final class ScopePassingSpanSubscriber<T> implements SpanSubscription<T>, 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 + "]");
|
||||
}
|
||||
|
||||
@@ -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> 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<Span> {
|
||||
|
||||
}
|
||||
|
||||
private static class TracingMapConnect implements
|
||||
BiFunction<Mono<? extends Connection>, Bootstrap, Mono<? extends Connection>> {
|
||||
|
||||
private final BeanFactory beanFactory;
|
||||
final LazyBean<HttpTracing> httpTracing;
|
||||
|
||||
private Tracer tracer;
|
||||
CurrentTraceContext currentTraceContext;
|
||||
|
||||
TracingMapConnect(BeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
TracingMapConnect(LazyBean<HttpTracing> httpTracing) {
|
||||
this.httpTracing = httpTracing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<? extends Connection> apply(Mono<? extends Connection> 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<HttpClientRequest, Connection> {
|
||||
|
||||
final BeanFactory beanFactory;
|
||||
|
||||
HttpTracing httpTracing;
|
||||
|
||||
List<String> propagationKeys;
|
||||
final LazyBean<HttpTracing> httpTracing;
|
||||
|
||||
HttpClientHandler<brave.http.HttpClientRequest, brave.http.HttpClientResponse> handler;
|
||||
|
||||
TracingDoOnRequest(BeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
TracingDoOnRequest(LazyBean<HttpTracing> 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<String> propagationKeys() {
|
||||
if (this.propagationKeys == null) {
|
||||
this.propagationKeys = httpTracing().tracing().propagation().keys();
|
||||
}
|
||||
return this.propagationKeys;
|
||||
}
|
||||
|
||||
private HttpClientHandler<brave.http.HttpClientRequest, brave.http.HttpClientResponse> handler() {
|
||||
HttpClientHandler<brave.http.HttpClientRequest, brave.http.HttpClientResponse> 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<Span> 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<HttpClientResponse, Connection> {
|
||||
|
||||
TracingDoOnResponse(BeanFactory beanFactory) {
|
||||
super(beanFactory);
|
||||
}
|
||||
|
||||
static TracingDoOnResponse create(BeanFactory beanFactory) {
|
||||
return new TracingDoOnResponse(beanFactory);
|
||||
TracingDoOnResponse(LazyBean<HttpTracing> 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<HttpClientRequest, Throwable> {
|
||||
|
||||
TracingDoOnErrorRequest(BeanFactory beanFactory) {
|
||||
super(beanFactory);
|
||||
}
|
||||
|
||||
static TracingDoOnErrorRequest create(BeanFactory beanFactory) {
|
||||
return new TracingDoOnErrorRequest(beanFactory);
|
||||
TracingDoOnErrorRequest(LazyBean<HttpTracing> 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<HttpClientResponse, Throwable> {
|
||||
|
||||
TracingDoOnErrorResponse(BeanFactory beanFactory) {
|
||||
super(beanFactory);
|
||||
}
|
||||
|
||||
static TracingDoOnErrorResponse create(BeanFactory beanFactory) {
|
||||
return new TracingDoOnErrorResponse(beanFactory);
|
||||
TracingDoOnErrorResponse(LazyBean<HttpTracing> 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> httpTracing;
|
||||
|
||||
HttpClientHandler<brave.http.HttpClientRequest, brave.http.HttpClientResponse> handler;
|
||||
|
||||
AbstractTracingDoOnHandler(BeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
AbstractTracingDoOnHandler(LazyBean<HttpTracing> httpTracing) {
|
||||
this.httpTracing = httpTracing;
|
||||
}
|
||||
|
||||
private HttpTracing httpTracing() {
|
||||
if (this.httpTracing == null) {
|
||||
this.httpTracing = this.beanFactory.getBean(HttpTracing.class);
|
||||
}
|
||||
return this.httpTracing;
|
||||
}
|
||||
|
||||
private HttpClientHandler<brave.http.HttpClientRequest, brave.http.HttpClientResponse> handler() {
|
||||
HttpClientHandler<brave.http.HttpClientRequest, brave.http.HttpClientResponse> 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;
|
||||
|
||||
@@ -162,8 +162,8 @@ public class TraceWebClientAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
static HttpClientBeanPostProcessor httpClientBeanPostProcessor(
|
||||
BeanFactory beanFactory) {
|
||||
return new HttpClientBeanPostProcessor(beanFactory);
|
||||
ConfigurableApplicationContext springContext) {
|
||||
return new HttpClientBeanPostProcessor(springContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <p>
|
||||
* Note: This is an internal class to sleuth and must not be used by external code.
|
||||
*/
|
||||
final class LazyBean<T> {
|
||||
public final class LazyBean<T> {
|
||||
|
||||
public static <T> LazyBean<T> create(ConfigurableApplicationContext springContext,
|
||||
Class<T> 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<T> {
|
||||
* @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;
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>
|
||||
* <em>Note:</em> {@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<Span> 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<String> 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<String> 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<Span> spans() {
|
||||
return new LinkedBlockingQueue<>();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Reporter<zipkin2.Span> spanReporter(BlockingQueue<Span> spans) {
|
||||
return spans::add;
|
||||
}
|
||||
|
||||
@Bean
|
||||
HttpClient reactorHttpClient() {
|
||||
return HttpClient.create();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<zipkin2.Span> 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() {
|
||||
|
||||
@@ -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<String>() {
|
||||
@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 {
|
||||
|
||||
@@ -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;
|
||||
@@ -31,7 +31,7 @@
|
||||
<name>spring-cloud-sleuth-dependencies</name>
|
||||
<description>Spring Cloud Sleuth Dependencies</description>
|
||||
<properties>
|
||||
<brave.version>5.9.3</brave.version>
|
||||
<brave.version>5.9.5</brave.version>
|
||||
<brave.opentracing.version>0.35.1</brave.opentracing.version>
|
||||
<grpc.spring.boot.version>3.4.1</grpc.spring.boot.version>
|
||||
</properties>
|
||||
|
||||
@@ -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<Span> reporter(ReporterMetrics reporterMetrics, ZipkinProperties zipkin,
|
||||
BytesEncoder<Span> spanBytesEncoder, DefaultListableBeanFactory beanFactory) {
|
||||
List<String> 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<Span> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user