diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 5f54e6c2f..f90085dcb 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -75,6 +75,16 @@ rxjava true + + com.squareup.okhttp3 + okhttp + true + + + org.apache.httpcomponents + httpclient + true + org.aspectj aspectjrt 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 new file mode 100644 index 000000000..0715052d0 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013-2016 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 org.apache.http.client.methods.RequestBuilder; +import org.springframework.cloud.sleuth.Tracer; + +/** + * Customization of a Ribbon request for Apache HttpClient + * + * @author Marcin Grzejszczak + * @since 1.1.0 + */ +class ApacheHttpClientRibbonRequestCustomizer extends SpanInjectingRibbonRequestCustomizer { + + ApacheHttpClientRibbonRequestCustomizer(Tracer tracer) { + super(tracer); + } + + @Override + public boolean accepts(Class aClass) { + return aClass == RequestBuilder.class; + } + + @Override + void setHeader(RequestBuilder builder, String name, String value) { + if (value != null) { + builder.setHeader(name, value); + } + } +} 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 new file mode 100644 index 000000000..01d11adb8 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizer.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013-2016 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 org.springframework.cloud.sleuth.Tracer; + +import okhttp3.Request; + +/** + * Customization of a Ribbon request for OkHttp + * + * @author Marcin Grzejszczak + * @since 1.1.0 + */ +class OkHttpClientRibbonRequestCustomizer extends SpanInjectingRibbonRequestCustomizer { + + OkHttpClientRibbonRequestCustomizer(Tracer tracer) { + super(tracer); + } + + @Override + public boolean accepts(Class aClass) { + return aClass == Request.Builder.class; + } + + @Override + void setHeader(Request.Builder builder, String name, String value) { + if (value != null) { + builder.addHeader(name, value); + } + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java index 20b0c5d3a..ca730870e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java @@ -18,17 +18,16 @@ package org.springframework.cloud.sleuth.instrument.zuul; import java.util.Map; +import com.netflix.zuul.context.RequestContext; + import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanInjector; import org.springframework.util.StringUtils; -import com.netflix.zuul.context.RequestContext; - /** * Span injector that injects tracing info to {@link RequestContext} * * @author Marcin Grzejszczak - * * @since 1.0.0 */ class RequestContextInjector implements SpanInjector { 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 new file mode 100644 index 000000000..d3fca982e --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizer.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013-2016 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 com.netflix.client.http.HttpRequest; + +import org.springframework.cloud.sleuth.Tracer; + +/** + * Customization of a Ribbon request for Netflix HttpClient + * + * @author Marcin Grzejszczak + * @since 1.1.0 + */ +class RestClientRibbonRequestCustomizer extends SpanInjectingRibbonRequestCustomizer { + + RestClientRibbonRequestCustomizer(Tracer tracer) { + super(tracer); + } + + @Override + public boolean accepts(Class aClass) { + return aClass == HttpRequest.Builder.class; + } + + @Override + void setHeader(HttpRequest.Builder builder, String name, String value) { + if (value != null) { + builder.header(name, value); + } + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestBuilderContextInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/SpanInjectingRibbonRequestCustomizer.java similarity index 50% rename from spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestBuilderContextInjector.java rename to spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/SpanInjectingRibbonRequestCustomizer.java index d5bc577a8..db7fbf1ce 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestBuilderContextInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/SpanInjectingRibbonRequestCustomizer.java @@ -16,23 +16,49 @@ package org.springframework.cloud.sleuth.instrument.zuul; +import java.lang.invoke.MethodHandles; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanInjector; - -import com.netflix.client.http.HttpRequest; -import com.netflix.client.http.HttpRequest.Builder; +import org.springframework.cloud.sleuth.Tracer; /** - * Span injector that injects tracing info to {@link Builder} + * 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. + * In order to add a new implementation of the {@link RibbonRequestCustomizer} it's + * necessary only to provide the {@link RibbonRequestCustomizer#accepts(Class)} method + * with the context class name and {@link SpanInjectingRibbonRequestCustomizer#setHeader(Object, String, String)} + * to tell Sleuth how to set a header using the particular library. * * @author Marcin Grzejszczak - * - * @since 1.0.0 + * @since 1.1.0 */ -class RequestBuilderContextInjector implements SpanInjector { +abstract class SpanInjectingRibbonRequestCustomizer implements RibbonRequestCustomizer, + SpanInjector { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private final Tracer tracer; + + SpanInjectingRibbonRequestCustomizer(Tracer tracer) { + this.tracer = tracer; + } @Override - public void inject(Span span, Builder carrier) { + public void customize(T context) { + Span span = getCurrentSpan(); + inject(span, context); + span.logEvent(Span.CLIENT_SEND); + if (log.isDebugEnabled()) { + log.debug("Span in the RibbonRequestCustomizer is" + span); + } + } + + @Override + public void inject(Span span, T carrier) { if (span == null) { setHeader(carrier, Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED); return; @@ -55,9 +81,9 @@ class RequestBuilderContextInjector implements SpanInjector { ? span.getParents().get(0) : null; } - public void setHeader(HttpRequest.Builder builder, String name, String value) { - if (value != null) { - builder.header(name, value); - } + private Span getCurrentSpan() { + return this.tracer.getCurrentSpan(); } + + abstract void setHeader(T builder, String name, String value); } 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 index 2717d5b9a..12bbf8635 100644 --- 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 @@ -19,6 +19,11 @@ package org.springframework.cloud.sleuth.instrument.zuul; import java.lang.invoke.MethodHandles; import java.net.URI; +import com.netflix.zuul.ExecutionStatus; +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.ZuulFilterResult; +import com.netflix.zuul.context.RequestContext; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.sleuth.Span; @@ -26,11 +31,6 @@ import org.springframework.cloud.sleuth.SpanInjector; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; -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. @@ -108,5 +108,4 @@ public class TracePreZuulFilter extends ZuulFilter { public int filterOrder() { return 0; } - } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactory.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactory.java deleted file mode 100644 index e8923de1f..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactory.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2013-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.io.InputStream; -import java.net.URISyntaxException; - -import com.netflix.client.http.HttpRequest; -import com.netflix.niws.client.http.RestClient; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.netflix.ribbon.SpringClientFactory; -import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommand; -import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommandFactory; -import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanInjector; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; -import org.springframework.util.MultiValueMap; - -/** - * Propagates traces downstream via http headers that contain trace metadata. - * - * @author Spencer Gibb - * @since 1.0.0 - */ -public class TraceRestClientRibbonCommandFactory extends RestClientRibbonCommandFactory { - - private static final Log log = LogFactory.getLog(TraceRestClientRibbonCommandFactory.class); - - private final Tracer tracer; - private final SpanInjector spanInjector; - private final HttpTraceKeysInjector httpTraceKeysInjector; - - public TraceRestClientRibbonCommandFactory(SpringClientFactory clientFactory, - Tracer tracer, SpanInjector spanInjector, - HttpTraceKeysInjector httpTraceKeysInjector) { - super(clientFactory); - this.tracer = tracer; - this.spanInjector = spanInjector; - this.httpTraceKeysInjector = httpTraceKeysInjector; - } - - @Override - @SuppressWarnings("deprecation") - public RestClientRibbonCommand create(RibbonCommandContext context) { - RestClient restClient = getClientFactory().getClient(context.getServiceId(), - RestClient.class); - try { - return new TraceRestClientRibbonCommand(context.getServiceId(), restClient, - getVerb(context.getVerb()), context.getUri(), context.getRetryable(), - context.getHeaders(), context.getParams(), context.getRequestEntity(), - this.tracer, this.spanInjector, this.httpTraceKeysInjector); - } - catch (URISyntaxException e) { - log.error("Exception occurred while trying to create the TraceRestClientRibbonCommand", e); - throw new RuntimeException(e); - } - } - - class TraceRestClientRibbonCommand extends RestClientRibbonCommand { - - private final Tracer tracer; - private final SpanInjector spanInjector; - private final HttpTraceKeysInjector httpTraceKeysInjector; - - @SuppressWarnings("deprecation") - public TraceRestClientRibbonCommand(String commandKey, RestClient restClient, - HttpRequest.Verb verb, String uri, Boolean retryable, - MultiValueMap headers, - MultiValueMap params, InputStream requestEntity, - Tracer tracer, SpanInjector spanInjector, - HttpTraceKeysInjector httpTraceKeysInjector) - throws URISyntaxException { - super(commandKey, restClient, verb, uri, retryable, headers, params, - requestEntity); - this.tracer = tracer; - this.spanInjector = spanInjector; - this.httpTraceKeysInjector = httpTraceKeysInjector; - } - - @Override - protected void customizeRequest(HttpRequest.Builder requestBuilder) { - Span span = getCurrentSpan(); - this.spanInjector.inject(span, requestBuilder); - this.httpTraceKeysInjector.addRequestTags(span, getUri(), getVerb().verb()); - span.logEvent(Span.CLIENT_SEND); - if (log.isDebugEnabled()) { - log.debug("Span in RibbonCommandFactory is" + span); - } - } - - private Span getCurrentSpan() { - return this.tracer.getCurrentSpan(); - } - - } -} 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 new file mode 100644 index 000000000..f64e121ae --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactory.java @@ -0,0 +1,53 @@ +/* + * Copyright 2013-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 org.springframework.cloud.netflix.zuul.filters.route.RibbonCommand; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; + +/** + * Propagates traces downstream via http headers that contain trace metadata. + * + * @author Spencer Gibb + * @author Marcin Grzejszczak + * @since 1.1.0 + */ +class TraceRibbonCommandFactory implements RibbonCommandFactory { + + private final RibbonCommandFactory delegate; + private final Tracer tracer; + private final HttpTraceKeysInjector httpTraceKeysInjector; + + public TraceRibbonCommandFactory(RibbonCommandFactory delegate, + Tracer tracer, HttpTraceKeysInjector httpTraceKeysInjector) { + this.delegate = delegate; + this.tracer = tracer; + this.httpTraceKeysInjector = httpTraceKeysInjector; + } + + @Override + public RibbonCommand create(RibbonCommandContext context) { + RibbonCommand ribbonCommand = this.delegate.create(context); + Span span = this.tracer.getCurrentSpan(); + this.httpTraceKeysInjector.addRequestTags(span, context.uri(), context.getMethod()); + return ribbonCommand; + } +} 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 new file mode 100644 index 000000000..a506059d5 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessor.java @@ -0,0 +1,72 @@ +/* + * Copyright 2013-2016 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 org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; + +/** + * Post processor that wraps a {@link org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory} + * in its trace representation. + * + * @author Marcin Grzejszczak + * + * @since 1.1.0 + */ +final class TraceRibbonCommandFactoryBeanPostProcessor implements BeanPostProcessor { + + private final BeanFactory beanFactory; + private Tracer tracer; + private HttpTraceKeysInjector httpTraceKeysInjector; + + TraceRibbonCommandFactoryBeanPostProcessor(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof RibbonCommandFactory) { + return new TraceRibbonCommandFactory((RibbonCommandFactory) bean, getTracer(), getHttpTraceKeysInjector()); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + Tracer getTracer() { + if (this.tracer == null) { + this.tracer = this.beanFactory.getBean(Tracer.class); + } + return this.tracer; + } + + HttpTraceKeysInjector getHttpTraceKeysInjector() { + if (this.httpTraceKeysInjector == null) { + this.httpTraceKeysInjector = this.beanFactory.getBean(HttpTraceKeysInjector.class); + } + return this.httpTraceKeysInjector; + } +} 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 54101331e..52556f521 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 @@ -15,13 +15,19 @@ */ package org.springframework.cloud.sleuth.instrument.zuul; +import com.netflix.client.http.HttpRequest; +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; + +import org.apache.http.client.methods.RequestBuilder; +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.netflix.ribbon.SpringClientFactory; +import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer; import org.springframework.cloud.sleuth.SpanInjector; import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; @@ -30,9 +36,7 @@ import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import com.netflix.client.http.HttpRequest; -import com.netflix.zuul.ZuulFilter; -import com.netflix.zuul.context.RequestContext; +import okhttp3.Request; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} @@ -56,13 +60,6 @@ public class TraceZuulAutoConfiguration { return new TracePreZuulFilter(tracer, spanInjector, httpTraceKeysInjector); } - @Bean - public TraceRestClientRibbonCommandFactory traceRestClientRibbonCommandFactory(SpringClientFactory factory, - Tracer tracer, SpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector) { - return new TraceRestClientRibbonCommandFactory(factory, tracer, spanInjector, - httpTraceKeysInjector); - } - @Bean @ConditionalOnMissingBean public TracePostZuulFilter tracePostZuulFilter(Tracer tracer, TraceKeys traceKeys) { @@ -75,8 +72,26 @@ public class TraceZuulAutoConfiguration { } @Bean - public SpanInjector requestBuilderContextSpanInjector() { - return new RequestBuilderContextInjector(); + public TraceRibbonCommandFactoryBeanPostProcessor traceRibbonCommandFactoryBeanPostProcessor(BeanFactory beanFactory) { + return new TraceRibbonCommandFactoryBeanPostProcessor(beanFactory); + } + + @Bean + @ConditionalOnClass(name = "com.netflix.client.http.HttpRequest.Builder") + public RibbonRequestCustomizer restClientRibbonRequestCustomizer(Tracer tracer) { + return new RestClientRibbonRequestCustomizer(tracer); + } + + @Bean + @ConditionalOnClass(name = "org.apache.http.client.methods.RequestBuilder") + public RibbonRequestCustomizer apacheHttpRibbonRequestCustomizer(Tracer tracer) { + return new ApacheHttpClientRibbonRequestCustomizer(tracer); + } + + @Bean + @ConditionalOnClass(name = "okhttp3.Request.Builder") + public RibbonRequestCustomizer okHttpRibbonRequestCustomizer(Tracer tracer) { + return new OkHttpClientRibbonRequestCustomizer(tracer); } } 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 new file mode 100644 index 000000000..34e6a613e --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizerTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013-2016 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 org.apache.http.Header; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.methods.RequestBuilder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(MockitoJUnitRunner.class) +public class ApacheHttpClientRibbonRequestCustomizerTests { + + @Mock Tracer tracer; + @InjectMocks ApacheHttpClientRibbonRequestCustomizer customizer; + Span span = Span.builder().name("name").spanId(1L).traceId(2L).parent(3L) + .processId("processId").build(); + + @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 { + RequestBuilder requestBuilder = RequestBuilder.create("GET"); + + this.customizer.inject(null, requestBuilder); + + HttpUriRequest request = requestBuilder.build(); + Header header = request.getFirstHeader(Span.SAMPLED_NAME); + then(header.getName()).isEqualTo(Span.SAMPLED_NAME); + then(header.getValue()).isEqualTo(Span.SPAN_NOT_SAMPLED); + } + + @Test + public void should_set_tracing_headers_on_the_context_when_there_is_a_span() throws Exception { + RequestBuilder requestBuilder = RequestBuilder.create("GET"); + + this.customizer.inject(this.span, requestBuilder); + + HttpUriRequest request = requestBuilder.build(); + thenThereIsAHeaderWithNameAndValue(request, Span.SPAN_ID_NAME, "1"); + thenThereIsAHeaderWithNameAndValue(request, Span.TRACE_ID_NAME, "2"); + thenThereIsAHeaderWithNameAndValue(request, Span.PARENT_ID_NAME, "3"); + thenThereIsAHeaderWithNameAndValue(request, Span.PROCESS_ID_NAME, "processId"); + } + + private void thenThereIsAHeaderWithNameAndValue(HttpUriRequest request, String name, String value) { + 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 new file mode 100644 index 000000000..f3e1c0d8c --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizerTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2013-2016 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 org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; + +import okhttp3.Request; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(MockitoJUnitRunner.class) +public class OkHttpClientRibbonRequestCustomizerTests { + + @Mock Tracer tracer; + @InjectMocks OkHttpClientRibbonRequestCustomizer customizer; + Span span = Span.builder().name("name").spanId(1L).traceId(2L).parent(3L) + .processId("processId").build(); + + @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 { + Request.Builder requestBuilder = requestBuilder(); + + this.customizer.inject(null, requestBuilder); + + Request request = requestBuilder.build(); + then(request.header(Span.SAMPLED_NAME)).isEqualTo(Span.SPAN_NOT_SAMPLED); + } + + @Test + public void should_set_tracing_headers_on_the_context_when_there_is_a_span() throws Exception { + Request.Builder requestBuilder = requestBuilder(); + + this.customizer.inject(this.span, requestBuilder); + + Request request = requestBuilder.build(); + thenThereIsAHeaderWithNameAndValue(request, Span.SPAN_ID_NAME, "1"); + thenThereIsAHeaderWithNameAndValue(request, Span.TRACE_ID_NAME, "2"); + thenThereIsAHeaderWithNameAndValue(request, Span.PARENT_ID_NAME, "3"); + thenThereIsAHeaderWithNameAndValue(request, Span.PROCESS_ID_NAME, "processId"); + } + + private void thenThereIsAHeaderWithNameAndValue(Request request, String name, String value) { + 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 new file mode 100644 index 000000000..4fc895e21 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizerTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2013-2016 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 com.netflix.client.http.HttpRequest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(MockitoJUnitRunner.class) +public class RestClientRibbonRequestCustomizerTests { + + @Mock Tracer tracer; + @InjectMocks RestClientRibbonRequestCustomizer customizer; + Span span = Span.builder().name("name").spanId(1L).traceId(2L).parent(3L) + .processId("processId").build(); + + @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 { + HttpRequest.Builder requestBuilder = requestBuilder(); + + this.customizer.inject(null, requestBuilder); + + HttpRequest request = requestBuilder.build(); + then(request.getHttpHeaders().getFirstValue(Span.SAMPLED_NAME)).isEqualTo(Span.SPAN_NOT_SAMPLED); + } + + @Test + public void should_set_tracing_headers_on_the_context_when_there_is_a_span() throws Exception { + HttpRequest.Builder requestBuilder = requestBuilder(); + + this.customizer.inject(this.span, requestBuilder); + + HttpRequest request = requestBuilder.build(); + thenThereIsAHeaderWithNameAndValue(request, Span.SPAN_ID_NAME, "1"); + thenThereIsAHeaderWithNameAndValue(request, Span.TRACE_ID_NAME, "2"); + thenThereIsAHeaderWithNameAndValue(request, Span.PARENT_ID_NAME, "3"); + thenThereIsAHeaderWithNameAndValue(request, Span.PROCESS_ID_NAME, "processId"); + } + + private void thenThereIsAHeaderWithNameAndValue(HttpRequest request, String name, String value) { + 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/TraceRibbonCommandFactoryBeanPostProcessorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessorTests.java new file mode 100644 index 000000000..2e7ef1a4e --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessorTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013-2016 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 org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(MockitoJUnitRunner.class) +public class TraceRibbonCommandFactoryBeanPostProcessorTests { + + @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(""); + } + + @Test + public void should_wrap_ribbon_command_factory_in_a_trace_representation() { + then(this.postProcessor.postProcessBeforeInitialization(ribbonCommandFactory, "name")).isInstanceOf(TraceRibbonCommandFactory.class); + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryTest.java similarity index 52% rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryTest.java index 955172e8f..38da7df84 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryTest.java @@ -16,7 +16,6 @@ package org.springframework.cloud.sleuth.instrument.zuul; -import com.netflix.client.http.HttpRequest; import com.netflix.niws.client.http.RestClient; import com.netflix.zuul.context.RequestContext; @@ -27,43 +26,44 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; -import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommand; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanInjector; +import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; import org.springframework.http.HttpHeaders; import org.springframework.util.LinkedMultiValueMap; -import static org.assertj.core.api.BDDAssertions.then; import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; /** * @author Marcin Grzejszczak */ @RunWith(MockitoJUnitRunner.class) -public class TraceRestClientRibbonCommandFactoryTest { +public class TraceRibbonCommandFactoryTest { @Mock Tracer tracer; @Mock SpringClientFactory springClientFactory; - SpanInjector spanInjector = new RequestBuilderContextInjector(); - @Mock HttpTraceKeysInjector httpTraceKeysInjector; - TraceRestClientRibbonCommandFactory traceRestClientRibbonCommandFactory; + HttpTraceKeysInjector httpTraceKeysInjector; + @Mock RibbonCommandFactory ribbonCommandFactory; + TraceRibbonCommandFactory traceRibbonCommandFactory; + Span span = Span.builder().name("name").spanId(1L).traceId(2L).parent(3L) + .processId("processId").build(); @Before @SuppressWarnings({ "deprecation", "unchecked" }) public void setup() { - this.traceRestClientRibbonCommandFactory = new TraceRestClientRibbonCommandFactory( - this.springClientFactory, this.tracer, this.spanInjector, + this.httpTraceKeysInjector = new HttpTraceKeysInjector(this.tracer, new TraceKeys()); + this.traceRibbonCommandFactory = new TraceRibbonCommandFactory( + this.ribbonCommandFactory, this.tracer, httpTraceKeysInjector); given(this.springClientFactory.getClient(anyString(), any(Class.class))) .willReturn(new RestClient()); - Span span = Span.builder().name("name").spanId(1L).traceId(2L).parent(3L) - .processId("processId").build(); given(this.tracer.getCurrentSpan()).willReturn(span); given(this.tracer.isTracing()).willReturn(true); } @@ -75,34 +75,11 @@ public class TraceRestClientRibbonCommandFactoryTest { } @Test - public void should_wrap_ribbon_command_in_a_sleuth_representation() throws Exception { - RestClientRibbonCommand restClientRibbonCommand = this.traceRestClientRibbonCommandFactory - .create(ribbonCommandContext()); + public void should_attach_trace_headers_to_the_span() throws Exception { + this.traceRibbonCommandFactory.create(ribbonCommandContext()); - then(restClientRibbonCommand).isInstanceOf( - TraceRestClientRibbonCommandFactory.TraceRestClientRibbonCommand.class); - } - - @Test - public void should_attach_trace_headers_to_the_sent_request() throws Exception { - RestClientRibbonCommand restClientRibbonCommand = this.traceRestClientRibbonCommandFactory - .create(ribbonCommandContext()); - TraceRestClientRibbonCommandFactory.TraceRestClientRibbonCommand traceRestClientRibbonCommand = (TraceRestClientRibbonCommandFactory.TraceRestClientRibbonCommand) restClientRibbonCommand; - HttpRequest.Builder builder = new HttpRequest.Builder(); - - traceRestClientRibbonCommand.customizeRequest(builder); - - HttpRequest httpRequest = builder.build(); - then(httpRequest.getHttpHeaders().getFirstValue(Span.SPAN_ID_NAME)) - .isEqualTo("1"); - then(httpRequest.getHttpHeaders().getFirstValue(Span.TRACE_ID_NAME)) - .isEqualTo("2"); - then(httpRequest.getHttpHeaders().getFirstValue(Span.SPAN_NAME_NAME)) - .isEqualTo("name"); - then(httpRequest.getHttpHeaders().getFirstValue(Span.PARENT_ID_NAME)) - .isEqualTo("3"); - then(httpRequest.getHttpHeaders().getFirstValue(Span.PROCESS_ID_NAME)) - .isEqualTo("processId"); + then(this.span).hasATag("http.method", "GET"); + then(this.span).hasATag("http.url", "http://localhost:1234/foo"); } private RibbonCommandContext ribbonCommandContext() {