Added RibbonCommand instrumentation (#318)

with this change RibbonRequestCustomizers are created for
- Netflix HttpClient
- OkHttp client
- Apache HttpClient

those customizers know how to inject span into the given context. Also a wrapper over a RibbonCommandFactory bean gets created that injects http trace keys to the given span

fixes #313
This commit is contained in:
Marcin Grzejszczak
2016-06-30 13:02:31 +02:00
committed by GitHub
parent 61e70deff5
commit 48d46b07bb
16 changed files with 644 additions and 187 deletions

View File

@@ -75,6 +75,16 @@
<artifactId>rxjava</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>

View File

@@ -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<RequestBuilder> {
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);
}
}
}

View File

@@ -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<Request.Builder> {
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);
}
}
}

View File

@@ -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<RequestContext> {

View File

@@ -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<HttpRequest.Builder> {
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);
}
}
}

View File

@@ -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<Builder> {
abstract class SpanInjectingRibbonRequestCustomizer<T> implements RibbonRequestCustomizer<T>,
SpanInjector<T> {
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<Builder> {
? 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);
}

View File

@@ -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;
}
}

View File

@@ -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<HttpRequest.Builder> spanInjector;
private final HttpTraceKeysInjector httpTraceKeysInjector;
public TraceRestClientRibbonCommandFactory(SpringClientFactory clientFactory,
Tracer tracer, SpanInjector<HttpRequest.Builder> 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<HttpRequest.Builder> spanInjector;
private final HttpTraceKeysInjector httpTraceKeysInjector;
@SuppressWarnings("deprecation")
public TraceRestClientRibbonCommand(String commandKey, RestClient restClient,
HttpRequest.Verb verb, String uri, Boolean retryable,
MultiValueMap<String, String> headers,
MultiValueMap<String, String> params, InputStream requestEntity,
Tracer tracer, SpanInjector<HttpRequest.Builder> 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();
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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<HttpRequest.Builder> 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<HttpRequest.Builder> requestBuilderContextSpanInjector() {
return new RequestBuilderContextInjector();
public TraceRibbonCommandFactoryBeanPostProcessor traceRibbonCommandFactoryBeanPostProcessor(BeanFactory beanFactory) {
return new TraceRibbonCommandFactoryBeanPostProcessor(beanFactory);
}
@Bean
@ConditionalOnClass(name = "com.netflix.client.http.HttpRequest.Builder")
public RibbonRequestCustomizer<HttpRequest.Builder> restClientRibbonRequestCustomizer(Tracer tracer) {
return new RestClientRibbonRequestCustomizer(tracer);
}
@Bean
@ConditionalOnClass(name = "org.apache.http.client.methods.RequestBuilder")
public RibbonRequestCustomizer<RequestBuilder> apacheHttpRibbonRequestCustomizer(Tracer tracer) {
return new ApacheHttpClientRibbonRequestCustomizer(tracer);
}
@Bean
@ConditionalOnClass(name = "okhttp3.Request.Builder")
public RibbonRequestCustomizer<Request.Builder> okHttpRibbonRequestCustomizer(Tracer tracer) {
return new OkHttpClientRibbonRequestCustomizer(tracer);
}
}

View File

@@ -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);
}
}

View File

@@ -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/");
}
}

View File

@@ -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/");
}
}

View File

@@ -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);
}
}

View File

@@ -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<HttpRequest.Builder> 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() {