diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 2f5f92eb4..dd7ab7202 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -1158,7 +1158,7 @@ Decorating Spring Integration Executor Channel with `TraceableExecutorService` w === Zuul -We're registering Zuul filters to propagate the tracing information (the request header is enriched with tracing data). +We're instrumenting the Zuul Ribbon integration by enriching the Ribbon requests with tracing information. To disable Zuul support set the `spring.sleuth.zuul.enabled` property to `false`. == Running examples diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/AbstractTraceZuulFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/AbstractTraceZuulFilter.java deleted file mode 100644 index 0b2c9aec7..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/AbstractTraceZuulFilter.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.zuul; - -import javax.servlet.http.HttpServletResponse; - -import brave.Tracer; -import brave.http.HttpClientHandler; -import brave.http.HttpTracing; -import brave.propagation.Propagation; -import brave.propagation.TraceContext; - -import com.netflix.zuul.ZuulFilter; -import com.netflix.zuul.context.RequestContext; - -/** - * The pre and post filters use the same handler logic - * - * @author Marcin Grzejszczak - * @since 2.0.0 - */ -abstract class AbstractTraceZuulFilter extends ZuulFilter { - - static final String ZUUL_CURRENT_SPAN = - AbstractTraceZuulFilter.class.getName() + ".CURRENT_SPAN"; - - static final Propagation.Setter SETTER = new Propagation.Setter() { - @Override public void put(RequestContext carrier, String key, String value) { - carrier.getZuulRequestHeaders().put(key, value); - } - - @Override public String toString() { - return "RequestContext::getZuulRequestHeaders::put"; - } - }; - - final Tracer tracer; - HttpClientHandler handler; - TraceContext.Injector injector; - - AbstractTraceZuulFilter(HttpTracing httpTracing) { - this.tracer = httpTracing.tracing().tracer(); - this.handler = HttpClientHandler - .create(httpTracing, new AbstractTraceZuulFilter.HttpAdapter()); - this.injector = httpTracing.tracing().propagation().injector(SETTER); - } - - static final class HttpAdapter - extends brave.http.HttpClientAdapter { - - @Override public String method(RequestContext request) { - return request.getRequest().getMethod(); - } - - @Override public String url(RequestContext request) { - return request.getRequest().getRequestURI(); - } - - @Override public String requestHeader(RequestContext request, String name) { - Object result = request.getZuulRequestHeaders().get(name); - return result != null ? result.toString() : null; - } - - @Override public Integer statusCode(HttpServletResponse response) { - return response.getStatus(); - } - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizer.java deleted file mode 100644 index 2a2e6d430..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizer.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2013-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.zuul; - -import brave.http.HttpClientAdapter; -import brave.http.HttpTracing; -import brave.propagation.Propagation; -import org.apache.http.Header; -import org.apache.http.client.methods.RequestBuilder; - -/** - * Customization of a Ribbon request for Apache HttpClient - * - * @author Marcin Grzejszczak - * @since 1.1.0 - */ -class ApacheHttpClientRibbonRequestCustomizer extends - SpanInjectingRibbonRequestCustomizer { - - static final Propagation.Setter SETTER = new Propagation.Setter() { - @Override public void put(RequestBuilder carrier, String key, String value) { - if (carrier.getFirstHeader(key) != null) { - return; - } - carrier.addHeader(key, value); - } - - @Override public String toString() { - return "RequestBuilder::addHeader"; - } - }; - - ApacheHttpClientRibbonRequestCustomizer(HttpTracing tracer) { - super(tracer); - } - - @Override - public boolean accepts(Class aClass) { - return aClass == RequestBuilder.class; - } - - @Override - protected HttpClientAdapter handlerClientAdapter() { - return new HttpClientAdapter() { - @Override public String method(RequestBuilder request) { - return request.getMethod(); - } - - @Override public String url(RequestBuilder request) { - return request.getUri().toString(); - } - - @Override public String requestHeader(RequestBuilder request, String name) { - Header header = request.getFirstHeader(name); - if (header == null) { - return null; - } - return header.getValue(); - } - - @Override public Integer statusCode(RequestBuilder response) { - throw new UnsupportedOperationException("response not supported"); - } - }; - } - - @Override protected Propagation.Setter setter() { - return SETTER; - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/HttpAdapter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/HttpAdapter.java deleted file mode 100644 index 9e41f00ea..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/HttpAdapter.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.springframework.cloud.sleuth.instrument.zuul; - -import javax.servlet.http.HttpServletResponse; - -import com.netflix.zuul.context.RequestContext; - -final class HttpAdapter - extends brave.http.HttpClientAdapter { - - @Override public String method(RequestContext request) { - return request.getRequest().getMethod(); - } - - @Override public String url(RequestContext request) { - return request.getRequest().getRequestURI(); - } - - @Override public String requestHeader(RequestContext request, String name) { - Object result = request.getZuulRequestHeaders().get(name); - return result != null ? result.toString() : null; - } - - @Override public Integer statusCode(HttpServletResponse response) { - return response.getStatus(); - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizer.java deleted file mode 100644 index 6bc3f40ab..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizer.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2013-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.zuul; - -import brave.http.HttpClientAdapter; -import brave.http.HttpTracing; -import brave.propagation.Propagation; -import okhttp3.Request; - -/** - * Customization of a Ribbon request for OkHttp - * - * @author Marcin Grzejszczak - * @since 1.1.0 - */ -class OkHttpClientRibbonRequestCustomizer extends - SpanInjectingRibbonRequestCustomizer { - - static final Propagation.Setter SETTER = - new Propagation.Setter() { - @Override public void put(Request.Builder carrier, String key, String value) { - if (carrier.build().header(key) != null) { - return; - } - carrier.addHeader(key, value); - } - - @Override public String toString() { - return "RequestBuilder::addHeader"; - } - }; - - OkHttpClientRibbonRequestCustomizer(HttpTracing tracer) { - super(tracer); - } - - @Override - public boolean accepts(Class aClass) { - return aClass == Request.Builder.class; - } - - @Override - protected HttpClientAdapter handlerClientAdapter() { - return new HttpClientAdapter() { - @Override public String method(Request.Builder request) { - return request.build().method(); - } - - @Override public String url(Request.Builder request) { - return request.build().url().uri().toString(); - } - - @Override public String requestHeader(Request.Builder request, String name) { - return request.build().header(name); - } - - @Override public Integer statusCode(Request.Builder response) { - throw new UnsupportedOperationException("response not supported"); - } - }; - } - - @Override protected Propagation.Setter setter() { - return SETTER; - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizer.java deleted file mode 100644 index 7912a07ca..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizer.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2013-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.zuul; - -import brave.http.HttpClientAdapter; -import brave.http.HttpTracing; -import brave.propagation.Propagation; - -import com.netflix.client.http.HttpRequest; - -/** - * Customization of a Ribbon request for Netflix HttpClient - * - * @author Marcin Grzejszczak - * @since 1.1.0 - */ -class RestClientRibbonRequestCustomizer extends - SpanInjectingRibbonRequestCustomizer { - - static final Propagation.Setter SETTER = - new Propagation.Setter() { - @Override public void put(HttpRequest.Builder carrier, String key, String value) { - if (carrier.build().getHttpHeaders().containsHeader(key)) { - return; - } - carrier.header(key, value); - } - - @Override public String toString() { - return "RequestBuilder::addHeader"; - } - }; - - RestClientRibbonRequestCustomizer(HttpTracing tracer) { - super(tracer); - } - - @Override - public boolean accepts(Class aClass) { - return aClass == HttpRequest.Builder.class; - } - - @Override - protected HttpClientAdapter handlerClientAdapter() { - return new HttpClientAdapter() { - @Override public String method(HttpRequest.Builder request) { - return request.build().getVerb().verb(); - } - - @Override public String url(HttpRequest.Builder request) { - return request.build().getUri().toString(); - } - - @Override - public String requestHeader(HttpRequest.Builder request, String name) { - return request.build().getHttpHeaders().getFirstValue(name); - } - - @Override public Integer statusCode(HttpRequest.Builder response) { - throw new UnsupportedOperationException("response not supported"); - } - }; - } - - @Override protected Propagation.Setter setter() { - return SETTER; - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/SpanInjectingRibbonRequestCustomizer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/SpanInjectingRibbonRequestCustomizer.java deleted file mode 100644 index 5b6162cd6..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/SpanInjectingRibbonRequestCustomizer.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2013-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.zuul; - -import brave.Span; -import brave.Tracer; -import brave.http.HttpClientHandler; -import brave.http.HttpTracing; -import brave.propagation.Propagation; -import brave.propagation.TraceContext; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer; - -/** - * Abstraction over customization of Ribbon Requests. All clients will inject the span - * into their respective context. The only difference is how those contexts set the headers. - * - * @author Marcin Grzejszczak - * @since 1.1.0 - */ -abstract class SpanInjectingRibbonRequestCustomizer implements RibbonRequestCustomizer { - - private static final Log log = LogFactory.getLog(SpanInjectingRibbonRequestCustomizer.class); - - private final Tracer tracer; - HttpClientHandler handler; - TraceContext.Injector injector; - - SpanInjectingRibbonRequestCustomizer(HttpTracing httpTracing) { - this.tracer = httpTracing.tracing().tracer(); - this.handler = HttpClientHandler - .create(httpTracing, handlerClientAdapter()); - this.injector = httpTracing.tracing().propagation().injector(setter()); - } - - @Override - public void customize(T context) { - Span span = getCurrentSpan(); - if (span == null) { - this.handler.handleSend(this.injector, context); - return; - } - Span childSpan = this.handler.handleSend(this.injector, context, span); - try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(childSpan)) { - if (log.isDebugEnabled()) { - log.debug("Span in the RibbonRequestCustomizer is" + span); - } - } finally { - childSpan.finish(); - } - } - - protected abstract brave.http.HttpClientAdapter handlerClientAdapter(); - - protected abstract Propagation.Setter setter(); - - Span getCurrentSpan() { - return this.tracer.currentSpan(); - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java deleted file mode 100644 index f3cd9c534..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.zuul; - -import javax.servlet.http.HttpServletResponse; - -import brave.Span; -import brave.Tracer; -import brave.Tracing; -import brave.http.HttpTracing; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import com.netflix.zuul.ZuulFilter; -import com.netflix.zuul.context.RequestContext; - -/**8 - * A post request {@link ZuulFilter} that publishes an event upon start of the filtering - * - * @author Dave Syer - * @since 1.0.0 - */ -public class TracePostZuulFilter extends AbstractTraceZuulFilter { - - private static final Log log = LogFactory.getLog(TracePostZuulFilter.class); - - public static ZuulFilter create(Tracing tracing) { - return new TracePostZuulFilter(HttpTracing.create(tracing)); - } - - public static ZuulFilter create(HttpTracing httpTracing) { - return new TracePostZuulFilter(httpTracing); - } - - TracePostZuulFilter(HttpTracing httpTracing) { - super(httpTracing); - } - - @Override - public boolean shouldFilter() { - return getCurrentSpan() != null; - } - - @Override - public Object run() { - Span span = getCurrentSpan(); - try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { - if (log.isDebugEnabled()) { - log.debug("Closing current client span " + span); - } - HttpServletResponse response = RequestContext.getCurrentContext() - .getResponse(); - this.handler.handleReceive(response, null, span); - } finally { - if (span != null) { - span.finish(); - } - } - return null; - } - - private Span getCurrentSpan() { - RequestContext ctx = RequestContext.getCurrentContext(); - if (ctx == null || ctx.getRequest() == null) { - return null; - } - return (Span) ctx.getRequest().getAttribute(ZUUL_CURRENT_SPAN); - } - - @Override - public String filterType() { - return "post"; - } - - @Override - public int filterOrder() { - return 0; - } - -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java deleted file mode 100644 index 30fdd8158..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.zuul; - -import brave.Span; -import brave.Tracer; -import brave.Tracing; -import brave.http.HttpTracing; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.instrument.web.TraceFilter; -import org.springframework.cloud.sleuth.instrument.web.TraceRequestAttributes; - -import com.netflix.zuul.ExecutionStatus; -import com.netflix.zuul.ZuulFilter; -import com.netflix.zuul.ZuulFilterResult; -import com.netflix.zuul.context.RequestContext; - -/** - * A pre request {@link ZuulFilter} that sets tracing related headers on the request - * from the current span. We're doing so to ensure tracing propagates to the next hop. - * - * @author Dave Syer - * @since 1.0.0 - */ -public class TracePreZuulFilter extends AbstractTraceZuulFilter { - - private static final Log log = LogFactory.getLog(TracePreZuulFilter.class); - private static final String TRACE_REQUEST_ATTR = TraceFilter.class.getName() + ".TRACE"; - private static final String TRACE_CLOSE_SPAN_REQUEST_ATTR = - TraceFilter.class.getName() + ".CLOSE_SPAN"; - - public static ZuulFilter create(Tracing tracing, ErrorParser errorParser) { - return new TracePreZuulFilter(HttpTracing.create(tracing), errorParser); - } - - public static ZuulFilter create(HttpTracing httpTracing, ErrorParser errorParser) { - return new TracePreZuulFilter(httpTracing, errorParser); - } - - private final ErrorParser errorParser; - - TracePreZuulFilter(HttpTracing httpTracing, ErrorParser errorParser) { - super(httpTracing); - this.errorParser = errorParser; - } - - @Override public ZuulFilterResult runFilter() { - RequestContext ctx = RequestContext.getCurrentContext(); - Span span = this.handler.handleSend(this.injector, ctx); - ZuulFilterResult result = null; - try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { - markRequestAsHandled(ctx, span); - if (log.isDebugEnabled()) { - log.debug("New Zuul Span is " + span + ""); - } - result = super.runFilter(); - return result; - } - finally { - if (result != null && ExecutionStatus.SUCCESS != result.getStatus()) { - if (log.isDebugEnabled()) { - log.debug( - "The result of Zuul filter execution was not successful thus " - + "will close the current span " + span); - } - this.errorParser.parseErrorTags(span, result.getException()); - span.finish(); - } - } - } - - // TraceFilter will not create the "fallback" span - private void markRequestAsHandled(RequestContext ctx, Span span) { - ctx.getRequest() - .setAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR, "true"); - ctx.getRequest().setAttribute(TraceRequestAttributes.ERROR_HANDLED_SPAN_REQUEST_ATTR, - "true"); - ctx.getRequest().setAttribute(TRACE_REQUEST_ATTR, span); - ctx.getRequest().setAttribute(TRACE_CLOSE_SPAN_REQUEST_ATTR, true); - ctx.getRequest().setAttribute(ZUUL_CURRENT_SPAN, span); - } - - @Override public String filterType() { - return "pre"; - } - - @Override public int filterOrder() { - return 0; - } - - @Override public boolean shouldFilter() { - return true; - } - - @Override public Object run() { - return null; - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactory.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactory.java index 2b755e9fb..7b1980526 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactory.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactory.java @@ -16,11 +16,23 @@ package org.springframework.cloud.sleuth.instrument.zuul; +import java.io.IOException; +import java.util.Collections; +import java.util.concurrent.Future; + import brave.Span; +import brave.Tracer; +import brave.http.HttpClientHandler; import brave.http.HttpTracing; +import brave.propagation.Propagation; +import brave.propagation.TraceContext; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.cloud.netflix.ribbon.support.RibbonCommandContext; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommand; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; +import org.springframework.http.client.ClientHttpResponse; +import rx.Observable; /** * Propagates traces downstream via http headers that contain trace metadata. @@ -31,25 +43,77 @@ import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory */ class TraceRibbonCommandFactory implements RibbonCommandFactory { - private final RibbonCommandFactory delegate; - private final HttpTracing tracing; + static final Propagation.Setter SETTER = new Propagation.Setter() { + @Override public void put(RibbonCommandContext carrier, String key, String value) { + carrier.getHeaders().put(key, Collections.singletonList(value)); + } - public TraceRibbonCommandFactory(RibbonCommandFactory delegate, - HttpTracing tracing) { + @Override public String toString() { + return "RibbonCommandContext::headers::put"; + } + }; + + private static final Log log = LogFactory.getLog(TraceRibbonCommandFactory.class); + + final HttpTracing tracing; + final Tracer tracer; + final RibbonCommandFactory delegate; + HttpClientHandler handler; + TraceContext.Injector injector; + + TraceRibbonCommandFactory(RibbonCommandFactory delegate, HttpTracing httpTracing) { + this.tracing = httpTracing; this.delegate = delegate; - this.tracing = tracing; + this.tracer = httpTracing.tracing().tracer(); + this.handler = HttpClientHandler + .create(httpTracing, new TraceRibbonCommandFactory.HttpAdapter()); + this.injector = httpTracing.tracing().propagation().injector(SETTER); } @Override - public RibbonCommand create(RibbonCommandContext context) { - RibbonCommand ribbonCommand = this.delegate.create(context); - Span span = this.tracing.tracing().tracer().currentSpan(); - this.tracing.clientParser().request(new TraceRibbonCommandFactory.HttpAdapter(), context, span); - return ribbonCommand; + public RibbonCommand create(final RibbonCommandContext context) { + final RibbonCommand ribbonCommand = this.delegate.create(context); + Span span = this.tracer.currentSpan(); + if (log.isDebugEnabled()) { + log.debug("Will set contents of the span " + this.tracer.currentSpan() + " in the ribbon command"); + } + return new RibbonCommand() { + @Override public ClientHttpResponse execute() { + Span span = TraceRibbonCommandFactory.this.handler.handleSend(TraceRibbonCommandFactory.this.injector, context); + ClientHttpResponse response = null; + Throwable error = null; + try (Tracer.SpanInScope ws = TraceRibbonCommandFactory.this.tracer.withSpanInScope(span)) { + return response = ribbonCommand.execute(); + } catch (RuntimeException | Error e) { + error = e; + throw e; + } finally { + TraceRibbonCommandFactory.this.handler.handleReceive(response, error, span); + } + } + + // currently only .execute() is used in Zuul + @Override public Future queue() { + parseRequest(context, span); + return ribbonCommand.queue(); + } + + // currently only .execute() is used in Zuul + @Override public Observable observe() { + parseRequest(context, span); + return ribbonCommand.observe(); + } + }; + + } + + private void parseRequest(RibbonCommandContext context, Span span) { + TraceRibbonCommandFactory.this.tracing.clientParser() + .request(new TraceRibbonCommandFactory.HttpAdapter(), context, span); } static final class HttpAdapter - extends brave.http.HttpClientAdapter { + extends brave.http.HttpClientAdapter { @Override public String method(RibbonCommandContext request) { return request.getMethod(); @@ -64,8 +128,12 @@ class TraceRibbonCommandFactory implements RibbonCommandFactory { return result != null ? result.toString() : null; } - @Override public Integer statusCode(RibbonCommand response) { - throw new UnsupportedOperationException("RibbonCommand doesn't support status code"); + @Override public Integer statusCode(ClientHttpResponse response) { + try { + return response.getRawStatusCode(); + } catch (IOException e) { + return null; + } } } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessor.java index c4799b578..5bca66ad6 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessor.java @@ -28,7 +28,7 @@ import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory * * @author Marcin Grzejszczak * - * @since 1.1.0 + * @since 2.0.0 */ final class TraceRibbonCommandFactoryBeanPostProcessor implements BeanPostProcessor { @@ -42,15 +42,16 @@ final class TraceRibbonCommandFactoryBeanPostProcessor implements BeanPostProces @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof RibbonCommandFactory) { - return new TraceRibbonCommandFactory((RibbonCommandFactory) bean, tracing()); - } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof RibbonCommandFactory + && !(bean instanceof TraceRibbonCommandFactory)) { + return new TraceRibbonCommandFactory((RibbonCommandFactory) bean, tracing()); + } return bean; } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java index a72bbeadd..ec53eb2a4 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java @@ -16,24 +16,18 @@ package org.springframework.cloud.sleuth.instrument.zuul; import brave.http.HttpTracing; -import okhttp3.Request; -import org.apache.http.client.methods.RequestBuilder; +import com.netflix.zuul.ZuulFilter; import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.cloud.sleuth.ErrorParser; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommand; import org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration; -import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import com.netflix.client.http.HttpRequest; -import com.netflix.zuul.ZuulFilter; - /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} * enables span information propagation when using Zuul. @@ -49,40 +43,12 @@ import com.netflix.zuul.ZuulFilter; @AutoConfigureAfter(TraceWebServletAutoConfiguration.class) public class TraceZuulAutoConfiguration { - @Bean - @ConditionalOnMissingBean - public ZuulFilter tracePreZuulFilter(HttpTracing tracer, - ErrorParser errorParser) { - return TracePreZuulFilter.create(tracer, errorParser); - } - - @Bean - @ConditionalOnMissingBean - public ZuulFilter tracePostZuulFilter(HttpTracing tracer) { - return TracePostZuulFilter.create(tracer); - } - - @Bean - public TraceRibbonCommandFactoryBeanPostProcessor traceRibbonCommandFactoryBeanPostProcessor(BeanFactory beanFactory) { - return new TraceRibbonCommandFactoryBeanPostProcessor(beanFactory); - } - - @Bean - @ConditionalOnClass(name = "com.netflix.client.http.HttpRequest.Builder") - public RibbonRequestCustomizer restClientRibbonRequestCustomizer(HttpTracing tracer) { - return new RestClientRibbonRequestCustomizer(tracer); - } - - @Bean - @ConditionalOnClass(name = "org.apache.http.client.methods.RequestBuilder") - public RibbonRequestCustomizer apacheHttpRibbonRequestCustomizer(HttpTracing tracer) { - return new ApacheHttpClientRibbonRequestCustomizer(tracer); - } - - @Bean - @ConditionalOnClass(name = "okhttp3.Request.Builder") - public RibbonRequestCustomizer okHttpRibbonRequestCustomizer(HttpTracing tracer) { - return new OkHttpClientRibbonRequestCustomizer(tracer); + @ConditionalOnClass(RibbonCommand.class) + static class RibbonConfig { + @Bean + public TraceRibbonCommandFactoryBeanPostProcessor traceRibbonCommandFactoryBeanPostProcessor(BeanFactory beanFactory) { + return new TraceRibbonCommandFactoryBeanPostProcessor(beanFactory); + } } @Bean diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java index e16b0e686..baf328ae0 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java @@ -23,8 +23,8 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.cloud.sleuth.instrument.web.TraceHandlerInterceptor; import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping; +import org.springframework.cloud.sleuth.instrument.web.TraceHandlerInterceptor; /** * Bean post processor that wraps {@link ZuulHandlerMapping} in its @@ -46,6 +46,12 @@ class TraceZuulHandlerMappingBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { if (bean instanceof ZuulHandlerMapping) { if (log.isDebugEnabled()) { log.debug("Attaching trace interceptor to bean [" + beanName + "] of type [" + bean.getClass().getSimpleName() + "]"); @@ -56,10 +62,4 @@ class TraceZuulHandlerMappingBeanPostProcessor implements BeanPostProcessor { } return bean; } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) - throws BeansException { - return bean; - } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizerTests.java deleted file mode 100644 index 1d081b9cb..000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizerTests.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2013-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.zuul; - -import brave.Tracing; -import brave.http.HttpTracing; -import brave.propagation.CurrentTraceContext; -import brave.sampler.Sampler; -import org.apache.http.Header; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.client.methods.RequestBuilder; -import org.junit.Test; -import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; -import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; -import org.springframework.cloud.sleuth.util.SpanUtil; - -import static org.assertj.core.api.BDDAssertions.then; - -/** - * @author Marcin Grzejszczak - */ -public class ApacheHttpClientRibbonRequestCustomizerTests { - - private static final String SAMPLED_NAME = "X-B3-Sampled"; - private static final String TRACE_ID_NAME = "X-B3-TraceId"; - private static final String SPAN_ID_NAME = "X-B3-SpanId"; - - ArrayListSpanReporter reporter = new ArrayListSpanReporter(); - Tracing tracing = Tracing.newBuilder() - .currentTraceContext(CurrentTraceContext.Default.create()) - .spanReporter(this.reporter) - .build(); - TraceKeys traceKeys = new TraceKeys(); - HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing) - .clientParser(SleuthHttpParserAccessor.getClient(this.traceKeys)) - .serverParser(SleuthHttpParserAccessor.getServer(this.traceKeys, new ExceptionMessageErrorParser())) - .build(); - brave.Span span = this.tracing.tracer().nextSpan().name("name").start(); - ApacheHttpClientRibbonRequestCustomizer customizer = - new ApacheHttpClientRibbonRequestCustomizer(this.httpTracing) { - @Override brave.Span getCurrentSpan() { - return span; - } - }; - - @Test - public void should_accept_customizer_when_apache_http_client_is_passed() throws Exception { - then(this.customizer.accepts(String.class)).isFalse(); - then(this.customizer.accepts(RequestBuilder.class)).isTrue(); - } - - @Test - public void should_set_not_sampled_on_the_context_when_there_is_no_span() throws Exception { - this.span = null; - Tracing tracing = Tracing.newBuilder() - .currentTraceContext(CurrentTraceContext.Default.create()) - .spanReporter(this.reporter) - .sampler(Sampler.NEVER_SAMPLE) - .build(); - TraceKeys traceKeys = new TraceKeys(); - HttpTracing httpTracing = HttpTracing.newBuilder(tracing) - .clientParser(SleuthHttpParserAccessor.getClient(traceKeys)) - .serverParser(SleuthHttpParserAccessor.getServer(traceKeys, new ExceptionMessageErrorParser())) - .build(); - RequestBuilder requestBuilder = RequestBuilder.create("GET").setUri("http://foo"); - - new ApacheHttpClientRibbonRequestCustomizer(httpTracing) { - @Override brave.Span getCurrentSpan() { - return span; - } - }.customize(requestBuilder); - - HttpUriRequest request = requestBuilder.build(); - Header header = request.getFirstHeader(SAMPLED_NAME); - then(header.getName()).isEqualTo(SAMPLED_NAME); - then(header.getValue()).isEqualTo("0"); - } - - @Test - public void should_set_tracing_headers_on_the_context_when_there_is_a_span() throws Exception { - RequestBuilder requestBuilder = RequestBuilder.create("GET").setUri("http://foo"); - - this.customizer.customize(requestBuilder); - - HttpUriRequest request = requestBuilder.build(); - thenThereIsAHeaderWithNameAndValue(request, SPAN_ID_NAME, SpanUtil.idToHex(this.span.context().spanId())); - thenThereIsAHeaderWithNameAndValue(request, TRACE_ID_NAME, this.span.context().traceIdString()); - } - - @Test - public void should_not_set_duplicate_tracing_headers_on_the_context_when_there_is_a_span() throws Exception { - RequestBuilder requestBuilder = RequestBuilder.create("GET").setUri("http://foo"); - - this.customizer.customize(requestBuilder); - this.customizer.customize(requestBuilder); - - HttpUriRequest request = requestBuilder.build(); - thenThereIsAHeaderWithNameAndValue(request, SPAN_ID_NAME, SpanUtil.idToHex(this.span.context().spanId())); - thenThereIsAHeaderWithNameAndValue(request, TRACE_ID_NAME, this.span.context().traceIdString()); - } - - public void thenThereIsAHeaderWithNameAndValue(HttpUriRequest request, String name, String value) { - then(request.getHeaders(name)).hasSize(1); - Header header = request.getFirstHeader(name); - then(header.getName()).isEqualTo(name); - then(header.getValue()).isEqualTo(value); - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizerTests.java deleted file mode 100644 index 99db830fb..000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizerTests.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2013-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.zuul; - -import brave.Tracing; -import brave.http.HttpTracing; -import brave.propagation.CurrentTraceContext; -import brave.sampler.Sampler; -import okhttp3.Request; -import org.junit.Test; -import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; -import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; -import org.springframework.cloud.sleuth.util.SpanUtil; - -import static org.assertj.core.api.BDDAssertions.then; - -/** - * @author Marcin Grzejszczak - */ -public class OkHttpClientRibbonRequestCustomizerTests { - - private static final String SAMPLED_NAME = "X-B3-Sampled"; - private static final String TRACE_ID_NAME = "X-B3-TraceId"; - private static final String SPAN_ID_NAME = "X-B3-SpanId"; - - ArrayListSpanReporter reporter = new ArrayListSpanReporter(); - Tracing tracing = Tracing.newBuilder() - .currentTraceContext(CurrentTraceContext.Default.create()) - .spanReporter(this.reporter) - .build(); - TraceKeys traceKeys = new TraceKeys(); - HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing) - .clientParser(SleuthHttpParserAccessor.getClient(this.traceKeys)) - .serverParser(SleuthHttpParserAccessor.getServer(this.traceKeys, new ExceptionMessageErrorParser())) - .build(); - brave.Span span = this.tracing.tracer().nextSpan().name("name").start(); - OkHttpClientRibbonRequestCustomizer customizer = - new OkHttpClientRibbonRequestCustomizer(this.httpTracing) { - @Override brave.Span getCurrentSpan() { - return span; - } - }; - - @Test - public void should_accept_customizer_when_apache_http_client_is_passed() throws Exception { - then(this.customizer.accepts(String.class)).isFalse(); - then(this.customizer.accepts(Request.Builder.class)).isTrue(); - } - - @Test - public void should_set_not_sampled_on_the_context_when_there_is_no_span() throws Exception { - this.span = null; - Tracing tracing = Tracing.newBuilder() - .currentTraceContext(CurrentTraceContext.Default.create()) - .spanReporter(this.reporter) - .sampler(Sampler.NEVER_SAMPLE) - .build(); - TraceKeys traceKeys = new TraceKeys(); - HttpTracing httpTracing = HttpTracing.newBuilder(tracing) - .clientParser(SleuthHttpParserAccessor.getClient(traceKeys)) - .serverParser(SleuthHttpParserAccessor.getServer(traceKeys, new ExceptionMessageErrorParser())) - .build(); - Request.Builder requestBuilder = requestBuilder(); - - new OkHttpClientRibbonRequestCustomizer(httpTracing) { - @Override brave.Span getCurrentSpan() { - return span; - } - }.customize(requestBuilder); - - this.customizer.customize(requestBuilder); - - Request request = requestBuilder.build(); - then(request.header(SAMPLED_NAME)).isEqualTo("0"); - } - - @Test - public void should_set_tracing_headers_on_the_context_when_there_is_a_span() throws Exception { - Request.Builder requestBuilder = requestBuilder(); - - this.customizer.customize(requestBuilder); - - Request request = requestBuilder.build(); - - thenThereIsAHeaderWithNameAndValue(request, SPAN_ID_NAME, SpanUtil.idToHex(this.span.context().spanId())); - thenThereIsAHeaderWithNameAndValue(request, TRACE_ID_NAME, this.span.context().traceIdString()); - } - - @Test - public void should_not_set_duplicate_tracing_headers_on_the_context_when_there_is_a_span() throws Exception { - Request.Builder requestBuilder = requestBuilder(); - - this.customizer.customize(requestBuilder); - this.customizer.customize(requestBuilder); - - Request request = requestBuilder.build(); - thenThereIsAHeaderWithNameAndValue(request, SPAN_ID_NAME, SpanUtil.idToHex(this.span.context().spanId())); - thenThereIsAHeaderWithNameAndValue(request, TRACE_ID_NAME, this.span.context().traceIdString()); - } - - private void thenThereIsAHeaderWithNameAndValue(Request request, String name, String value) { - then(request.headers(name)).hasSize(1); - then(request.header(name)).isEqualTo(value); - } - - private Request.Builder requestBuilder() { - return new Request.Builder().get().url("http://localhost:8080/"); - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizerTests.java deleted file mode 100644 index 9bd8a163f..000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizerTests.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2013-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.zuul; - -import java.util.stream.Collectors; - -import brave.Tracing; -import brave.http.HttpTracing; -import brave.propagation.CurrentTraceContext; -import brave.sampler.Sampler; -import org.junit.Test; -import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; -import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; -import org.springframework.cloud.sleuth.util.SpanUtil; - -import com.netflix.client.http.HttpRequest; - -import static org.assertj.core.api.BDDAssertions.then; - -/** - * @author Marcin Grzejszczak - */ -public class RestClientRibbonRequestCustomizerTests { - - private static final String SAMPLED_NAME = "X-B3-Sampled"; - private static final String TRACE_ID_NAME = "X-B3-TraceId"; - private static final String SPAN_ID_NAME = "X-B3-SpanId"; - - ArrayListSpanReporter reporter = new ArrayListSpanReporter(); - Tracing tracing = Tracing.newBuilder() - .currentTraceContext(CurrentTraceContext.Default.create()) - .spanReporter(this.reporter) - .build(); - TraceKeys traceKeys = new TraceKeys(); - HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing) - .clientParser(SleuthHttpParserAccessor.getClient(this.traceKeys)) - .serverParser(SleuthHttpParserAccessor.getServer(this.traceKeys, new ExceptionMessageErrorParser())) - .build(); - brave.Span span = this.tracing.tracer().nextSpan().name("name").start(); - RestClientRibbonRequestCustomizer customizer = - new RestClientRibbonRequestCustomizer(this.httpTracing) { - @Override brave.Span getCurrentSpan() { - return span; - } - }; - - @Test - public void should_accept_customizer_when_apache_http_client_is_passed() throws Exception { - then(this.customizer.accepts(String.class)).isFalse(); - then(this.customizer.accepts(HttpRequest.Builder.class)).isTrue(); - } - - @Test - public void should_set_not_sampled_on_the_context_when_there_is_no_span() throws Exception { - this.span = null; - Tracing tracing = Tracing.newBuilder() - .currentTraceContext(CurrentTraceContext.Default.create()) - .spanReporter(this.reporter) - .sampler(Sampler.NEVER_SAMPLE) - .build(); - TraceKeys traceKeys = new TraceKeys(); - HttpTracing httpTracing = HttpTracing.newBuilder(tracing) - .clientParser(SleuthHttpParserAccessor.getClient(traceKeys)) - .serverParser(SleuthHttpParserAccessor.getServer(traceKeys, new ExceptionMessageErrorParser())) - .build(); - HttpRequest.Builder requestBuilder = requestBuilder(); - - new RestClientRibbonRequestCustomizer(httpTracing) { - @Override brave.Span getCurrentSpan() { - return span; - } - }.customize(requestBuilder); - - this.customizer.customize(requestBuilder); - - HttpRequest request = requestBuilder.build(); - then(request.getHttpHeaders().getFirstValue(SAMPLED_NAME)).isEqualTo("0"); - } - - @Test - public void should_set_tracing_headers_on_the_context_when_there_is_a_span() throws Exception { - HttpRequest.Builder requestBuilder = requestBuilder(); - - this.customizer.customize(requestBuilder); - - HttpRequest request = requestBuilder.build(); - thenThereIsAHeaderWithNameAndValue(request, SPAN_ID_NAME, SpanUtil.idToHex(this.span.context().spanId())); - thenThereIsAHeaderWithNameAndValue(request, TRACE_ID_NAME, this.span.context().traceIdString()); - } - - @Test - public void should_not_set_duplicate_tracing_headers_on_the_context_when_there_is_a_span() throws Exception { - HttpRequest.Builder requestBuilder = requestBuilder(); - - this.customizer.customize(requestBuilder); - this.customizer.customize(requestBuilder); - - HttpRequest request = requestBuilder.build(); - thenThereIsAHeaderWithNameAndValue(request, SPAN_ID_NAME, SpanUtil.idToHex(this.span.context().spanId())); - thenThereIsAHeaderWithNameAndValue(request, TRACE_ID_NAME, this.span.context().traceIdString()); - } - - private void thenThereIsAHeaderWithNameAndValue(HttpRequest request, String name, String value) { - then(request.getHttpHeaders().getAllHeaders() - .stream().filter(stringStringEntry -> stringStringEntry.getKey().equals(name)).collect( - Collectors.toList())).hasSize(1); - then(request.getHttpHeaders().getFirstValue(name)).isEqualTo(value); - } - - private HttpRequest.Builder requestBuilder() { - return new HttpRequest.Builder().verb(HttpRequest.Verb.GET).uri("http://localhost:8080/"); - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilterTests.java deleted file mode 100644 index 89f00432b..000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilterTests.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.zuul; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.List; - -import brave.Span; -import brave.Tracer; -import brave.Tracing; -import brave.http.HttpTracing; -import brave.propagation.CurrentTraceContext; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.BDDMockito; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; -import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; -import org.springframework.cloud.netflix.zuul.metrics.EmptyTracerFactory; - -import com.netflix.zuul.context.RequestContext; -import com.netflix.zuul.monitoring.TracerFactory; - -import static org.assertj.core.api.BDDAssertions.then; - -/** - * @author Dave Syer - * - */ -@RunWith(MockitoJUnitRunner.class) -public class TracePostZuulFilterTests { - - @Mock HttpServletRequest httpServletRequest; - @Mock HttpServletResponse httpServletResponse; - - ArrayListSpanReporter reporter = new ArrayListSpanReporter(); - Tracing tracing = Tracing.newBuilder() - .currentTraceContext(CurrentTraceContext.Default.create()) - .spanReporter(this.reporter) - .build(); - TraceKeys traceKeys = new TraceKeys(); - HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing) - .clientParser(SleuthHttpParserAccessor.getClient(this.traceKeys)) - .serverParser(SleuthHttpParserAccessor.getServer(this.traceKeys, new ExceptionMessageErrorParser())) - .build(); - private TracePostZuulFilter filter = new TracePostZuulFilter(this.httpTracing); - RequestContext requestContext = new RequestContext(); - - @After - public void clean() { - RequestContext.getCurrentContext().unset(); - this.httpTracing.tracing().close(); - RequestContext.testSetCurrentContext(null); - } - - @Before - public void setup() { - BDDMockito.given(this.httpServletResponse.getStatus()).willReturn(200); - this.requestContext.setRequest(this.httpServletRequest); - this.requestContext.setResponse(this.httpServletResponse); - RequestContext.testSetCurrentContext(this.requestContext); - TracerFactory.initialize(new EmptyTracerFactory()); - } - - @Test - public void filterPublishesEventAndClosesSpan() throws Exception { - Span span = this.tracing.tracer().nextSpan().name("http:start").start(); - BDDMockito.given(this.httpServletRequest - .getAttribute(TracePostZuulFilter.ZUUL_CURRENT_SPAN)).willReturn(span); - BDDMockito.given(this.httpServletResponse.getStatus()).willReturn(456); - - try (Tracer.SpanInScope ws = this.tracing.tracer().withSpanInScope(span)) { - this.filter.runFilter(); - } finally { - span.finish(); - } - - List spans = this.reporter.getSpans(); - then(spans).hasSize(1); - // initial span - then(spans.get(0).tags()) - .containsEntry("http.status_code", "456"); - then(spans.get(0).name()).isEqualTo("http:start"); - then(this.tracing.tracer().currentSpan()).isNull(); - } -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilterTests.java deleted file mode 100644 index 06211266a..000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilterTests.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.zuul; - -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import javax.servlet.http.HttpServletRequest; - -import brave.Span; -import brave.Tracer; -import brave.Tracing; -import brave.http.HttpTracing; -import brave.propagation.CurrentTraceContext; -import brave.sampler.Sampler; -import com.netflix.zuul.context.RequestContext; -import com.netflix.zuul.monitoring.MonitoringHelper; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.BDDMockito; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; -import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; - -import static org.assertj.core.api.BDDAssertions.then; - -/** - * @author Dave Syer - * - */ -@RunWith(MockitoJUnitRunner.class) -public class TracePreZuulFilterTests { - static final String TRACE_ID_NAME = "X-B3-TraceId"; - static final String SAMPLED_NAME = "X-B3-Sampled"; - - @Mock HttpServletRequest httpServletRequest; - - ArrayListSpanReporter reporter = new ArrayListSpanReporter(); - Tracing tracing = Tracing.newBuilder() - .currentTraceContext(CurrentTraceContext.Default.create()) - .spanReporter(this.reporter) - .build(); - Tracer tracer = this.tracing.tracer(); - TraceKeys traceKeys = new TraceKeys(); - HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing) - .clientParser(SleuthHttpParserAccessor.getClient(this.traceKeys)) - .serverParser(SleuthHttpParserAccessor.getServer(this.traceKeys, new ExceptionMessageErrorParser())) - .build(); - ErrorParser errorParser = new ExceptionMessageErrorParser(); - - private TracePreZuulFilter filter = new TracePreZuulFilter(this.httpTracing, this.errorParser); - - @After - public void clean() { - RequestContext.getCurrentContext().unset(); - this.tracing.close(); - RequestContext.testSetCurrentContext(null); - } - - @Before - public void setup() { - MonitoringHelper.initMocks(); - RequestContext requestContext = new RequestContext(); - BDDMockito.given(this.httpServletRequest.getRequestURI()).willReturn("http://foo.bar"); - BDDMockito.given(this.httpServletRequest.getMethod()).willReturn("GET"); - requestContext.setRequest(this.httpServletRequest); - RequestContext.testSetCurrentContext(requestContext); - } - - @Test - public void filterAddsHeaders() throws Exception { - Span span = this.tracer.nextSpan().name("http:start").start(); - - try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { - this.filter.runFilter(); - } finally { - span.finish(); - } - - RequestContext ctx = RequestContext.getCurrentContext(); - then(ctx.getZuulRequestHeaders().get(TRACE_ID_NAME)) - .isNotNull(); - then(ctx.getZuulRequestHeaders().get(SAMPLED_NAME)) - .isEqualTo("1"); - then(this.tracer.currentSpan()).isNull(); - } - - @Test - public void notSampledIfNotExportable() throws Exception { - Tracing tracing = Tracing.newBuilder() - .sampler(Sampler.NEVER_SAMPLE) - .currentTraceContext(CurrentTraceContext.Default.create()) - .spanReporter(this.reporter) - .build(); - HttpTracing httpTracing = HttpTracing.create(tracing); - this.filter = new TracePreZuulFilter(httpTracing, this.errorParser); - - Span span = tracing.tracer().nextSpan().name("http:start").start(); - - try (Tracer.SpanInScope ws = tracing.tracer().withSpanInScope(span)) { - this.filter.runFilter(); - } finally { - span.finish(); - } - - RequestContext ctx = RequestContext.getCurrentContext(); - then(ctx.getZuulRequestHeaders().get(TRACE_ID_NAME)) - .isNotNull(); - then(ctx.getZuulRequestHeaders().get(SAMPLED_NAME)) - .isEqualTo("0"); - then(this.tracer.currentSpan()).isNull(); - } - - @Test - public void shouldCloseSpanWhenExceptionIsThrown() throws Exception { - Span startedSpan = this.tracer.nextSpan().name("http:start").start(); - final AtomicReference span = new AtomicReference<>(); - - try (Tracer.SpanInScope ws = tracing.tracer().withSpanInScope(startedSpan)) { - new TracePreZuulFilter(this.httpTracing, this.errorParser) { - @Override - public Object run() { - super.run(); - span.set( - TracePreZuulFilterTests.this.tracer.currentSpan()); - throw new RuntimeException("foo"); - } - }.runFilter(); - } finally { - startedSpan.finish(); - } - - then(startedSpan).isNotEqualTo(span.get()); - List spans = this.reporter.getSpans(); - then(spans).hasSize(2); - // initial span - then(spans.get(0).tags()) - .containsEntry("http.method", "GET") - .containsEntry("error", "foo"); - // span from zuul - then(spans.get(1).name()).isEqualTo("http:start"); - then(this.tracer.currentSpan()).isNull(); - } - - @Test - public void shouldNotCloseSpanWhenNoExceptionIsThrown() throws Exception { - Span startedSpan = this.tracer.nextSpan().name("http:start").start(); - final AtomicReference span = new AtomicReference<>(); - - try (Tracer.SpanInScope ws = tracing.tracer().withSpanInScope(startedSpan)) { - new TracePreZuulFilter(this.httpTracing, this.errorParser) { - @Override - public Object run() { - span.set( - TracePreZuulFilterTests.this.tracer.currentSpan()); - return super.run(); - } - }.runFilter(); - } finally { - startedSpan.finish(); - } - - then(startedSpan).isNotEqualTo(span.get()); - then(this.tracer.currentSpan()).isNull(); - then(this.reporter.getSpans()).isNotEmpty(); - } - -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessorTests.java index 0e11424d4..c1f723690 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessorTests.java @@ -16,13 +16,22 @@ package org.springframework.cloud.sleuth.instrument.zuul; +import brave.Tracing; +import brave.http.HttpTracing; +import brave.propagation.CurrentTraceContext; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.BDDMockito; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.beans.factory.BeanFactory; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; +import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import static org.assertj.core.api.BDDAssertions.then; @@ -32,18 +41,34 @@ import static org.assertj.core.api.BDDAssertions.then; @RunWith(MockitoJUnitRunner.class) public class TraceRibbonCommandFactoryBeanPostProcessorTests { + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + TraceKeys traceKeys = new TraceKeys(); + HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing) + .clientParser(SleuthHttpParserAccessor.getClient(this.traceKeys)) + .serverParser(SleuthHttpParserAccessor.getServer(this.traceKeys, new ExceptionMessageErrorParser())) + .build(); + @Mock RibbonCommandFactory ribbonCommandFactory; @Mock BeanFactory beanFactory; @InjectMocks TraceRibbonCommandFactoryBeanPostProcessor postProcessor; @Test public void should_return_a_bean_as_it_is_if_its_not_a_ribbon_command_Factory() { - then(this.postProcessor.postProcessBeforeInitialization("", "name")).isEqualTo(""); + then(this.postProcessor.postProcessAfterInitialization("", "name")).isEqualTo(""); } @Test public void should_wrap_ribbon_command_factory_in_a_trace_representation() { - then(this.postProcessor.postProcessBeforeInitialization(ribbonCommandFactory, "name")).isInstanceOf( + then(this.postProcessor.postProcessAfterInitialization(ribbonCommandFactory, "name")).isInstanceOf( TraceRibbonCommandFactory.class); } + + @Before + public void setup() { + BDDMockito.given(this.beanFactory.getBean(HttpTracing.class)).willReturn(this.httpTracing); + } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryTest.java index 32d176a81..96245e235 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryTest.java @@ -23,23 +23,24 @@ import brave.Tracer; import brave.Tracing; import brave.http.HttpTracing; import brave.propagation.CurrentTraceContext; +import com.netflix.zuul.context.RequestContext; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.BDDMockito; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.cloud.netflix.ribbon.support.RibbonCommandContext; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommand; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; -import org.springframework.cloud.netflix.ribbon.support.RibbonCommandContext; -import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; import org.springframework.http.HttpHeaders; import org.springframework.util.LinkedMultiValueMap; -import com.netflix.zuul.context.RequestContext; - import static org.assertj.core.api.BDDAssertions.then; /** @@ -48,7 +49,6 @@ import static org.assertj.core.api.BDDAssertions.then; @RunWith(MockitoJUnitRunner.class) public class TraceRibbonCommandFactoryTest { - ArrayListSpanReporter reporter = new ArrayListSpanReporter(); Tracing tracing = Tracing.newBuilder() .currentTraceContext(CurrentTraceContext.Default.create()) @@ -60,6 +60,7 @@ public class TraceRibbonCommandFactoryTest { .serverParser(SleuthHttpParserAccessor.getServer(this.traceKeys, new ExceptionMessageErrorParser())) .build(); @Mock RibbonCommandFactory ribbonCommandFactory; + @Mock RibbonCommand ribbonCommand; TraceRibbonCommandFactory traceRibbonCommandFactory; Span span = this.tracing.tracer().nextSpan().name("name"); @@ -68,6 +69,9 @@ public class TraceRibbonCommandFactoryTest { public void setup() { this.traceRibbonCommandFactory = new TraceRibbonCommandFactory( this.ribbonCommandFactory, this.httpTracing); + BDDMockito.given(this.ribbonCommandFactory + .create(BDDMockito.any(RibbonCommandContext.class))) + .willReturn(this.ribbonCommand); } @After @@ -79,16 +83,21 @@ public class TraceRibbonCommandFactoryTest { @Test public void should_attach_trace_headers_to_the_span() throws Exception { try (Tracer.SpanInScope ws = this.tracing.tracer().withSpanInScope(this.span)) { - this.traceRibbonCommandFactory.create(ribbonCommandContext()); + RibbonCommand ribbonCommand = this.traceRibbonCommandFactory + .create(ribbonCommandContext()); + ribbonCommand.execute(); } finally { this.span.finish(); } - then(this.reporter.getSpans()).hasSize(1); + then(this.reporter.getSpans()).hasSize(2); + // RPC zipkin2.Span span = this.reporter.getSpans().get(0); then(span.tags()) .containsEntry("http.method", "GET") .containsEntry("http.url", "http://localhost:1234/foo"); + zipkin2.Span main = this.reporter.getSpans().get(1); + then(main.name()).isEqualTo("name"); } private RibbonCommandContext ribbonCommandContext() {