Updated HTTP client side span with HTTP tags

After this change when sending an HTTP request, the client side span will have all the necessary HTTP related tags.

Fixes #290
This commit is contained in:
Marcin Grzejszczak
2016-05-31 09:31:55 +02:00
parent 723b19ee78
commit d30efca703
29 changed files with 462 additions and 186 deletions

View File

@@ -0,0 +1,81 @@
package org.springframework.cloud.sleuth.instrument.web;
import java.util.Collection;
import java.util.Map;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.util.StringUtils;
/**
* Injects HTTP related keys to the current span.
*
* @author Marcin Grzejszczak
*
* @since 1.0.1
*/
public class HttpTraceKeysInjector {
private final Tracer tracer;
private final TraceKeys traceKeys;
public HttpTraceKeysInjector(Tracer tracer, TraceKeys traceKeys) {
this.tracer = tracer;
this.traceKeys = traceKeys;
}
/**
* Adds tags from the HTTP request to the current Span
*/
public void addRequestTags(String url, String host, String path, String method) {
this.tracer.addTag(this.traceKeys.getHttp().getUrl(), url);
this.tracer.addTag(this.traceKeys.getHttp().getHost(), host);
this.tracer.addTag(this.traceKeys.getHttp().getPath(), path);
this.tracer.addTag(this.traceKeys.getHttp().getMethod(), method);
}
/**
* Adds tags from the HTTP request to the given Span
*/
public void addRequestTags(Span span, String url, String host, String path, String method) {
tagSpan(span, this.traceKeys.getHttp().getUrl(), url);
tagSpan(span, this.traceKeys.getHttp().getHost(), host);
tagSpan(span, this.traceKeys.getHttp().getPath(), path);
tagSpan(span, this.traceKeys.getHttp().getMethod(), method);
}
/**
* Adds tags from the HTTP request together with headers to the current Span
*/
public void addRequestTags(String url, String host, String path, String method,
Map<String, ? extends Collection<String>> headers) {
addRequestTags(url, host, path, method);
addRequestTagsFromHeaders(headers);
}
/**
* Add a tag to the given, exportable Span
*/
public void tagSpan(Span span, String key, String value) {
if (span != null && span.isExportable()) {
span.tag(key, value);
}
}
private void addRequestTagsFromHeaders(Map<String, ? extends Collection<String>> headers) {
for (String name : this.traceKeys.getHttp().getHeaders()) {
for (Map.Entry<String, ? extends Collection<String>> entry : headers.entrySet()) {
addTagForEntry(name, entry.getValue());
}
}
}
private void addTagForEntry(String name, Collection<String> list) {
String key = this.traceKeys.getHttp().getPrefix() + name.toLowerCase();
String value = list.size() == 1 ? list.iterator().next()
: StringUtils.collectionToDelimitedString(list, ",", "'", "'");
this.tracer.addTag(key, value);
}
}

View File

@@ -15,17 +15,16 @@
*/
package org.springframework.cloud.sleuth.instrument.web;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.regex.Pattern;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanExtractor;
import org.springframework.cloud.sleuth.SpanInjector;
@@ -79,24 +78,29 @@ public class TraceFilter extends OncePerRequestFilter {
private final SpanReporter spanReporter;
private final SpanExtractor<HttpServletRequest> spanExtractor;
private final SpanInjector<HttpServletResponse> spanInjector;
private final HttpTraceKeysInjector httpTraceKeysInjector;
private UrlPathHelper urlPathHelper = new UrlPathHelper();
public TraceFilter(Tracer tracer, TraceKeys traceKeys, SpanReporter spanReporter,
SpanExtractor<HttpServletRequest> spanExtractor, SpanInjector<HttpServletResponse> spanInjector) {
SpanExtractor<HttpServletRequest> spanExtractor,
SpanInjector<HttpServletResponse> spanInjector,
HttpTraceKeysInjector httpTraceKeysInjector) {
this(tracer, traceKeys, Pattern.compile(DEFAULT_SKIP_PATTERN), spanReporter,
spanExtractor, spanInjector);
spanExtractor, spanInjector, httpTraceKeysInjector);
}
public TraceFilter(Tracer tracer, TraceKeys traceKeys, Pattern skipPattern,
SpanReporter spanReporter, SpanExtractor<HttpServletRequest> spanExtractor,
SpanInjector<HttpServletResponse> spanInjector) {
SpanInjector<HttpServletResponse> spanInjector,
HttpTraceKeysInjector httpTraceKeysInjector) {
this.tracer = tracer;
this.traceKeys = traceKeys;
this.skipPattern = skipPattern;
this.spanReporter = spanReporter;
this.spanExtractor = spanExtractor;
this.spanInjector = spanInjector;
this.httpTraceKeysInjector = httpTraceKeysInjector;
}
@Override
@@ -115,10 +119,9 @@ public class TraceFilter extends OncePerRequestFilter {
spanFromRequest = createSpan(request, skip, spanFromRequest, name);
Throwable exception = null;
try {
addRequestTags(request);
this.spanInjector.inject(spanFromRequest, response);
// Add headers before filter chain in case one of the filters flushes the
// response...
this.spanInjector.inject(spanFromRequest, response);
filterChain.doFilter(request, response);
}
catch (Throwable e) {
@@ -149,6 +152,12 @@ public class TraceFilter extends OncePerRequestFilter {
}
}
private void addRequestTagsForParentSpan(HttpServletRequest request, Span spanFromRequest) {
if (spanFromRequest.getName().contains("parent")) {
addRequestTags(spanFromRequest, request);
}
}
/**
* Creates a span and appends it as the current request's attribute
*/
@@ -157,9 +166,9 @@ public class TraceFilter extends OncePerRequestFilter {
if (spanFromRequest != null) {
return spanFromRequest;
}
Span parent = this.spanExtractor
.joinTrace(request);
Span parent = this.spanExtractor.joinTrace(request);
if (parent != null) {
addRequestTagsForParentSpan(request, parent);
spanFromRequest = this.tracer.createSpan(name, parent);
if (parent.isRemote()) {
parent.logEvent(Span.SERVER_RECV);
@@ -180,12 +189,10 @@ public class TraceFilter extends OncePerRequestFilter {
}
/** Override to add annotations not defined in {@link TraceKeys}. */
protected void addRequestTags(HttpServletRequest request) {
protected void addRequestTags(Span span, HttpServletRequest request) {
String uri = this.urlPathHelper.getPathWithinApplication(request);
this.tracer.addTag(this.traceKeys.getHttp().getUrl(), getFullUrl(request));
this.tracer.addTag(this.traceKeys.getHttp().getHost(), request.getServerName());
this.tracer.addTag(this.traceKeys.getHttp().getPath(), uri);
this.tracer.addTag(this.traceKeys.getHttp().getMethod(), request.getMethod());
this.httpTraceKeysInjector.addRequestTags(span, getFullUrl(request),
request.getServerName(), uri, request.getMethod());
for (String name : this.traceKeys.getHttp().getHeaders()) {
Enumeration<String> values = request.getHeaders(name);
if (values.hasMoreElements()) {
@@ -193,7 +200,7 @@ public class TraceFilter extends OncePerRequestFilter {
ArrayList<String> list = Collections.list(values);
String value = list.size() == 1 ? list.get(0)
: StringUtils.collectionToDelimitedString(list, ",", "'", "'");
this.tracer.addTag(key, value);
this.httpTraceKeysInjector.tagSpan(span, key, value);
}
}
}

View File

@@ -76,9 +76,10 @@ public class TraceWebAutoConfiguration {
public TraceFilter traceFilter(Tracer tracer, TraceKeys traceKeys,
SkipPatternProvider skipPatternProvider, SpanReporter spanReporter,
SpanExtractor<HttpServletRequest> spanExtractor,
SpanInjector<HttpServletResponse> spanInjector) {
SpanInjector<HttpServletResponse> spanInjector,
HttpTraceKeysInjector httpTraceKeysInjector) {
return new TraceFilter(tracer, traceKeys, skipPatternProvider.skipPattern(),
spanReporter, spanExtractor, spanInjector);
spanReporter, spanExtractor, spanInjector, httpTraceKeysInjector);
}
@Bean

View File

@@ -21,25 +21,26 @@ import java.net.URI;
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.http.HttpRequest;
/**
* Abstraction over classes that interact with Http requests. Allows you
* to enrich the request headers with trace related information.
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
abstract class AbstractTraceHttpRequestInterceptor {
protected final Tracer tracer;
protected final SpanInjector<HttpRequest> spanInjector;
protected final HttpTraceKeysInjector keysInjector;
protected AbstractTraceHttpRequestInterceptor(Tracer tracer,
SpanInjector<HttpRequest> spanInjector) {
SpanInjector<HttpRequest> spanInjector, HttpTraceKeysInjector keysInjector) {
this.tracer = tracer;
this.spanInjector = spanInjector;
this.keysInjector = keysInjector;
}
/**
@@ -51,6 +52,7 @@ abstract class AbstractTraceHttpRequestInterceptor {
String spanName = uriScheme(uri) + ":" + uri.getPath();
Span newSpan = this.tracer.createSpan(spanName);
this.spanInjector.inject(newSpan, request);
addRequestTags(request);
newSpan.logEvent(Span.CLIENT_SEND);
}
@@ -58,6 +60,17 @@ abstract class AbstractTraceHttpRequestInterceptor {
return uri.getScheme() == null ? "http" : uri.getScheme();
}
/**
* Adds HTTP tags to the client side span
*/
protected void addRequestTags(HttpRequest request) {
this.keysInjector.addRequestTags(request.getURI().toString(),
request.getURI().getHost(),
request.getURI().getPath(),
request.getMethod().name(),
request.getHeaders());
}
/**
* Close the current span and log the client received event
*/

View File

@@ -21,6 +21,7 @@ import java.net.URI;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
import org.springframework.core.task.AsyncListenableTaskExecutor;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
@@ -54,9 +55,11 @@ public class TraceAsyncClientHttpRequestFactoryWrapper extends AbstractTraceHttp
*
* @see org.springframework.web.client.AsyncRestTemplate#AsyncRestTemplate(AsyncClientHttpRequestFactory)
*/
public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer, SpanInjector<HttpRequest> spanInjector,
AsyncClientHttpRequestFactory asyncDelegate) {
super(tracer, spanInjector);
public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer,
SpanInjector<HttpRequest> spanInjector,
AsyncClientHttpRequestFactory asyncDelegate,
HttpTraceKeysInjector httpTraceKeysInjector) {
super(tracer, spanInjector, httpTraceKeysInjector);
this.asyncDelegate = asyncDelegate;
this.syncDelegate = asyncDelegate instanceof ClientHttpRequestFactory ?
(ClientHttpRequestFactory) asyncDelegate : defaultClientHttpRequestFactory();
@@ -66,16 +69,20 @@ public class TraceAsyncClientHttpRequestFactoryWrapper extends AbstractTraceHttp
* Default implementation that creates a {@link SimpleClientHttpRequestFactory} that
* has a wrapped task executor via the {@link TraceAsyncListenableTaskExecutor}
*/
public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer, SpanInjector<HttpRequest> spanInjector) {
super(tracer, spanInjector);
public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer,
SpanInjector<HttpRequest> spanInjector, HttpTraceKeysInjector httpTraceKeysInjector) {
super(tracer, spanInjector, httpTraceKeysInjector);
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = defaultClientHttpRequestFactory();
this.asyncDelegate = simpleClientHttpRequestFactory;
this.syncDelegate = simpleClientHttpRequestFactory;
}
public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer, SpanInjector<HttpRequest> spanInjector,
AsyncClientHttpRequestFactory asyncDelegate, ClientHttpRequestFactory syncDelegate) {
super(tracer, spanInjector);
public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer,
SpanInjector<HttpRequest> spanInjector,
AsyncClientHttpRequestFactory asyncDelegate,
ClientHttpRequestFactory syncDelegate,
HttpTraceKeysInjector httpTraceKeysInjector) {
super(tracer, spanInjector, httpTraceKeysInjector);
this.asyncDelegate = asyncDelegate;
this.syncDelegate = syncDelegate;
}
@@ -97,6 +104,7 @@ public class TraceAsyncClientHttpRequestFactoryWrapper extends AbstractTraceHttp
throws IOException {
AsyncClientHttpRequest request = this.asyncDelegate
.createAsyncRequest(uri, httpMethod);
addRequestTags(request);
publishStartEvent(request);
return request;
}
@@ -105,6 +113,7 @@ public class TraceAsyncClientHttpRequestFactoryWrapper extends AbstractTraceHttp
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod)
throws IOException {
ClientHttpRequest request = this.syncDelegate.createRequest(uri, httpMethod);
addRequestTags(request);
publishStartEvent(request);
return request;
}

View File

@@ -20,6 +20,7 @@ import java.io.IOException;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
@@ -38,8 +39,9 @@ import org.springframework.http.client.ClientHttpResponse;
public class TraceRestTemplateInterceptor extends AbstractTraceHttpRequestInterceptor
implements ClientHttpRequestInterceptor {
public TraceRestTemplateInterceptor(Tracer tracer, SpanInjector<HttpRequest> spanInjector) {
super(tracer, spanInjector);
public TraceRestTemplateInterceptor(Tracer tracer, SpanInjector<HttpRequest> spanInjector,
HttpTraceKeysInjector httpTraceKeysInjector) {
super(tracer, spanInjector, httpTraceKeysInjector);
}
@Override

View File

@@ -26,6 +26,7 @@ import org.springframework.cloud.sleuth.SpanAccessor;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@@ -53,6 +54,7 @@ import org.springframework.web.client.AsyncRestTemplate;
public class TraceWebAsyncClientAutoConfiguration {
@Autowired Tracer tracer;
@Autowired HttpTraceKeysInjector httpTraceKeysInjector;
@Autowired SpanInjector<HttpRequest> spanInjector;
@Autowired(required = false) ClientHttpRequestFactory clientHttpRequestFactory;
@Autowired(required = false) AsyncClientHttpRequestFactory asyncClientHttpRequestFactory;
@@ -71,7 +73,7 @@ public class TraceWebAsyncClientAutoConfiguration {
(AsyncClientHttpRequestFactory) clientFactory : defaultClientHttpRequestFactory(this.tracer);
}
return new TraceAsyncClientHttpRequestFactoryWrapper(this.tracer, this.spanInjector,
asyncClientFactory, clientFactory);
asyncClientFactory, clientFactory, this.httpTraceKeysInjector);
}
private SimpleClientHttpRequestFactory defaultClientHttpRequestFactory(Tracer tracer) {

View File

@@ -16,12 +16,11 @@
package org.springframework.cloud.sleuth.instrument.web.client;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -29,8 +28,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpRequest;
@@ -55,8 +56,9 @@ public class TraceWebClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public TraceRestTemplateInterceptor traceRestTemplateInterceptor(Tracer tracer,
SpanInjector<HttpRequest> spanInjector) {
return new TraceRestTemplateInterceptor(tracer, spanInjector);
SpanInjector<HttpRequest> spanInjector,
HttpTraceKeysInjector httpTraceKeysInjector) {
return new TraceRestTemplateInterceptor(tracer, spanInjector, httpTraceKeysInjector);
}
@Bean
@@ -64,6 +66,12 @@ public class TraceWebClientAutoConfiguration {
return new HttpRequestInjector();
}
@Bean
@ConditionalOnMissingBean
public HttpTraceKeysInjector httpTraceKeysInjector(Tracer tracer, TraceKeys traceKeys) {
return new HttpTraceKeysInjector(tracer, traceKeys);
}
@Configuration
protected static class TraceInterceptorConfiguration {

View File

@@ -17,6 +17,7 @@
package org.springframework.cloud.sleuth.instrument.web.client.feign;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
import feign.Feign;
import feign.hystrix.HystrixFeign;
@@ -34,9 +35,9 @@ final class SleuthFeignBuilder {
private SleuthFeignBuilder() {}
static Feign.Builder builder(Tracer tracer) {
static Feign.Builder builder(Tracer tracer, HttpTraceKeysInjector keysInjector) {
return HystrixFeign.builder()
.client(new TraceFeignClient(tracer))
.client(new TraceFeignClient(tracer, keysInjector))
.retryer(new TraceFeignRetryer(tracer))
.decoder(new TraceFeignDecoder(tracer))
.errorDecoder(new TraceFeignErrorDecoder(tracer));

View File

@@ -17,9 +17,11 @@
package org.springframework.cloud.sleuth.instrument.web.client.feign;
import java.io.IOException;
import java.net.URI;
import java.util.Objects;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
import feign.Client;
import feign.Request;
@@ -37,21 +39,25 @@ import feign.RetryableException;
final class TraceFeignClient extends FeignEventPublisher implements Client {
private final Client delegate;
private final HttpTraceKeysInjector keysInjector;
TraceFeignClient(Tracer tracer) {
TraceFeignClient(Tracer tracer, HttpTraceKeysInjector keysInjector) {
super(tracer);
this.delegate = new Client.Default(null, null);
this.keysInjector = keysInjector;
}
TraceFeignClient(Tracer tracer, Client delegate) {
TraceFeignClient(Tracer tracer, Client delegate, HttpTraceKeysInjector keysInjector) {
super(tracer);
this.delegate = delegate;
this.keysInjector = keysInjector;
}
@Override
public Response execute(Request request, Request.Options options) throws IOException {
Response response = null;
Response response;
try {
addRequestTags(request);
response = this.delegate.execute(request, options);
}
catch (RetryableException | IOException e) {
@@ -69,4 +75,13 @@ final class TraceFeignClient extends FeignEventPublisher implements Client {
}
return response;
}
/**
* Adds HTTP tags to the client side span
*/
private void addRequestTags(Request request) {
URI uri = URI.create(request.url());
this.keysInjector.addRequestTags(uri.toString(), uri.getHost(), uri.getPath(),
request.method(), request.headers());
}
}

View File

@@ -16,14 +16,13 @@
package org.springframework.cloud.sleuth.instrument.web.client.feign;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import com.netflix.hystrix.HystrixCommand;
import feign.Client;
import feign.Feign;
import feign.FeignException;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.Response;
import feign.codec.Decoder;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -39,18 +38,21 @@ import org.springframework.cloud.netflix.feign.FeignContext;
import org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder;
import org.springframework.cloud.netflix.feign.support.SpringDecoder;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.hystrix.SleuthHystrixAutoConfiguration;
import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import feign.Client;
import feign.Feign;
import feign.FeignException;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.Response;
import feign.codec.Decoder;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
@@ -74,8 +76,8 @@ public class TraceFeignClientAutoConfiguration {
@Scope("prototype")
@ConditionalOnClass(HystrixCommand.class)
@ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = true)
Feign.Builder feignHystrixBuilder(Tracer tracer, TraceKeys traceKeys) {
return SleuthFeignBuilder.builder(tracer);
Feign.Builder feignHystrixBuilder(Tracer tracer, HttpTraceKeysInjector keysInjector) {
return SleuthFeignBuilder.builder(tracer, keysInjector);
}
@Configuration
@@ -86,7 +88,6 @@ public class TraceFeignClientAutoConfiguration {
FeignBeanPostProcessor feignBeanPostProcessor(TraceFeignObjectWrapper traceFeignObjectWrapper) {
return new FeignBeanPostProcessor(traceFeignObjectWrapper);
}
}
@Bean
@@ -123,7 +124,7 @@ public class TraceFeignClientAutoConfiguration {
* an existing one if a retry takes place.
*/
@Bean
public RequestInterceptor traceIdRequestInterceptor(Tracer tracer) {
RequestInterceptor traceIdRequestInterceptor(Tracer tracer) {
return new TraceFeignRequestInterceptor(tracer, feignRequestTemplateInjector());
}

View File

@@ -1,11 +1,13 @@
package org.springframework.cloud.sleuth.instrument.web.client.feign;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
import feign.Client;
import feign.Retryer;
import feign.codec.Decoder;
import feign.codec.ErrorDecoder;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.sleuth.Tracer;
/**
* Class that wraps Feign related classes into their Trace representative
@@ -17,6 +19,7 @@ final class TraceFeignObjectWrapper {
private final BeanFactory beanFactory;
private Tracer tracer;
private HttpTraceKeysInjector keysInjector;
TraceFeignObjectWrapper(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
@@ -28,7 +31,7 @@ final class TraceFeignObjectWrapper {
} else if (bean instanceof Retryer && !(bean instanceof TraceFeignRetryer)) {
return new TraceFeignRetryer(getTracer(), (Retryer) bean);
} else if (bean instanceof Client && !(bean instanceof TraceFeignClient)) {
return new TraceFeignClient(getTracer(), (Client) bean);
return new TraceFeignClient(getTracer(), (Client) bean, getHttpTraceKeysInjector());
} else if (bean instanceof ErrorDecoder && !(bean instanceof TraceFeignErrorDecoder)) {
return new TraceFeignErrorDecoder(getTracer(), (ErrorDecoder) bean);
}
@@ -36,9 +39,16 @@ final class TraceFeignObjectWrapper {
}
private Tracer getTracer() {
if (this.tracer==null) {
if (this.tracer == null) {
this.tracer = this.beanFactory.getBean(Tracer.class);
}
return this.tracer;
}
private HttpTraceKeysInjector getHttpTraceKeysInjector() {
if (this.keysInjector == null) {
this.keysInjector = this.beanFactory.getBean(HttpTraceKeysInjector.class);
}
return this.keysInjector;
}
}

View File

@@ -88,12 +88,7 @@ public class SpanAssert extends AbstractAssert<SpanAssert, Span> {
public SpanAssert hasATag(String tagKey, String tagValue) {
isNotNull();
if (!this.actual.tags().containsKey(tagKey)) {
String message = String.format("Expected span to have the tag with key <%s>. "
+ "Found tags are <%s>", tagKey, this.actual.tags());
log.error(message);
failWithMessage(message);
}
assertThatTagIsPresent(tagKey);
String foundTagValue = this.actual.tags().get(tagKey);
if (!foundTagValue.equals(tagValue)) {
String message = String.format("Expected span to have the tag with key <%s> and value <%s>. "
@@ -104,6 +99,28 @@ public class SpanAssert extends AbstractAssert<SpanAssert, Span> {
return this;
}
public SpanAssert matchesATag(String tagKey, String tagRegex) {
isNotNull();
assertThatTagIsPresent(tagKey);
String foundTagValue = this.actual.tags().get(tagKey);
if (!foundTagValue.matches(tagRegex)) {
String message = String.format("Expected span to have the tag with key <%s> and match a regex <%s>. "
+ "Found value for that tag is <%s>", tagKey, tagRegex, foundTagValue);
log.error(message);
failWithMessage(message);
}
return this;
}
private void assertThatTagIsPresent(String tagKey) {
if (!this.actual.tags().containsKey(tagKey)) {
String message = String.format("Expected span to have the tag with key <%s>. "
+ "Found tags are <%s>", tagKey, this.actual.tags());
log.error(message);
failWithMessage(message);
}
}
public SpanAssert hasLoggedAnEvent(String event) {
isNotNull();
if (!this.actual.logs().stream().map(org.springframework.cloud.sleuth.Log::getEvent)

View File

@@ -25,6 +25,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.messaging.TraceChannelInterceptorTests.App;
@@ -195,5 +196,10 @@ public class TraceChannelInterceptorTests implements MessageHandler {
return new MessagingTemplate(channel());
}
@Bean
Sampler alwaysSampler() {
return new AlwaysSampler();
}
}
}

View File

@@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.messaging.TraceContextPropagationChannelInterceptorTests.App;
@@ -98,5 +99,9 @@ public class TraceContextPropagationChannelInterceptorTests {
return new QueueChannel();
}
@Bean
Sampler testSampler() {
return new AlwaysSampler();
}
}
}

View File

@@ -16,14 +16,15 @@
package org.springframework.cloud.sleuth.instrument.messaging.websocket;
import static org.assertj.core.api.BDDAssertions.then;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.instrument.messaging.TraceChannelInterceptor;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -32,6 +33,8 @@ import org.springframework.web.socket.config.annotation.DelegatingWebSocketMessa
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import static org.assertj.core.api.BDDAssertions.then;
/**
* @author Marcin Grzejszczak
*/
@@ -67,5 +70,9 @@ public class TraceWebSocketAutoConfigurationTest {
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/hello").withSockJS();
}
@Bean Sampler testSampler() {
return new AlwaysSampler();
}
}
}

View File

@@ -1,12 +1,5 @@
package org.springframework.cloud.sleuth.instrument.web;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
@@ -16,8 +9,10 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
@@ -37,6 +32,13 @@ import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.async.WebAsyncTask;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringApplicationConfiguration(classes = {
RestTemplateTraceAspectIntegrationTests.Config.class })
@RunWith(SpringJUnit4ClassRunner.class)
@@ -119,6 +121,11 @@ public class RestTemplateTraceAspectIntegrationTests {
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
Sampler alwaysSampler() {
return new AlwaysSampler();
}
}
@RestController

View File

@@ -59,7 +59,8 @@ public class TraceFilterAlwaysSamplerIntegrationTests extends AbstractMvcIntegra
@Override
protected void configureMockMvcBuilder(DefaultMockMvcBuilder mockMvcBuilder) {
mockMvcBuilder.addFilters(new TraceFilter(this.tracer, this.traceKeys,
new NoOpSpanReporter(), this.spanExtractor, this.spanInjector));
new NoOpSpanReporter(), this.spanExtractor, this.spanInjector,
this.httpTraceKeysInjector));
}
private MvcResult whenSentPingWithTraceIdAndNotSampling(Long traceId)

View File

@@ -16,18 +16,14 @@
package org.springframework.cloud.sleuth.instrument.web;
import static org.assertj.core.api.BDDAssertions.then;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
@@ -35,9 +31,11 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanExtractor;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.cloud.sleuth.trace.TestSpanContextHolder;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
@@ -53,19 +51,18 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.BDDAssertions.then;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(TraceFilterCustomExtractorTests.Config.class)
@WebIntegrationTest(randomPort = true)
@DirtiesContext
public class TraceFilterCustomExtractorTests {
@Autowired
Random random;
@Autowired
RestTemplate restTemplate;
@Autowired
Config config;
@Autowired
CustomRestController customRestController;
@Autowired Random random;
@Autowired RestTemplate restTemplate;
@Autowired Config config;
@Autowired CustomRestController customRestController;
@Test
@SuppressWarnings("unchecked")
@@ -126,6 +123,11 @@ public class TraceFilterCustomExtractorTests {
CustomRestController customRestController() {
return new CustomRestController();
}
@Bean
Sampler alwaysSampler() {
return new AlwaysSampler();
}
}
// tag::extractor[]

View File

@@ -16,10 +16,12 @@ import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration;
import org.springframework.cloud.sleuth.instrument.web.common.AbstractMvcIntegrationTest;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
@@ -165,5 +167,10 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest {
return managementServerProperties;
}
}
@Bean
Sampler alwaysSampler() {
return new AlwaysSampler();
}
}
}

View File

@@ -50,6 +50,7 @@ public class TraceFilterMockChainIntegrationTests {
new Random(), new DefaultSpanNamer(),
new NoOpSpanLogger(), new NoOpSpanReporter());
private TraceKeys traceKeys = new TraceKeys();
private HttpTraceKeysInjector keysInjector = new HttpTraceKeysInjector(this.tracer, this.traceKeys);
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@@ -73,7 +74,7 @@ public class TraceFilterMockChainIntegrationTests {
public void startsNewTrace() throws Exception {
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, new NoOpSpanReporter(),
new HttpServletRequestExtractor(new Random(), Pattern.compile(TraceFilter.DEFAULT_SKIP_PATTERN)),
new HttpServletResponseInjector());
new HttpServletResponseInjector(), keysInjector);
filter.doFilter(this.request, this.response, this.filterChain);
assertNull(TestSpanContextHolder.getCurrentSpan());
}
@@ -85,7 +86,7 @@ public class TraceFilterMockChainIntegrationTests {
.header(Span.TRACE_ID_NAME, generator.nextLong()).buildRequest(new MockServletContext());
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, new NoOpSpanReporter(),
new HttpServletRequestExtractor(new Random(), Pattern.compile(TraceFilter.DEFAULT_SKIP_PATTERN)),
new HttpServletResponseInjector());
new HttpServletResponseInjector(), keysInjector);
filter.doFilter(this.request, this.response, this.filterChain);
assertNull(TestSpanContextHolder.getCurrentSpan());
}

View File

@@ -16,11 +16,11 @@
package org.springframework.cloud.sleuth.instrument.web;
import java.util.Random;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Optional;
import java.util.Random;
import java.util.regex.Pattern;
import org.junit.Before;
import org.junit.Test;
@@ -38,6 +38,7 @@ import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.cloud.sleuth.sampler.NeverSampler;
import org.springframework.cloud.sleuth.trace.DefaultTracer;
import org.springframework.cloud.sleuth.trace.TestSpanContextHolder;
import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockFilterChain;
@@ -46,8 +47,8 @@ import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.assertThat;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.entry;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -60,13 +61,14 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
public class TraceFilterTests {
@Mock SpanLogger spanLogger;
@Mock SpanReporter spanReporter;
ArrayListSpanAccumulator spanReporter = new ArrayListSpanAccumulator();
SpanExtractor<HttpServletRequest> spanExtractor = new HttpServletRequestExtractor(new Random(), Pattern
.compile(TraceFilter.DEFAULT_SKIP_PATTERN));
SpanInjector<HttpServletResponse> spanInjector = new HttpServletResponseInjector();
private Tracer tracer;
private TraceKeys traceKeys = new TraceKeys();
private HttpTraceKeysInjector httpTraceKeysInjector;
private Span span;
@@ -90,6 +92,7 @@ public class TraceFilterTests {
this.response = new MockHttpServletResponse();
this.response.setContentType(MediaType.APPLICATION_JSON_VALUE);
this.filterChain = new MockFilterChain();
this.httpTraceKeysInjector = new HttpTraceKeysInjector(this.tracer, this.traceKeys);
}
public MockHttpServletRequestBuilder builder() {
@@ -101,7 +104,7 @@ public class TraceFilterTests {
public void notTraced() throws Exception {
this.sampler = NeverSampler.INSTANCE;
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter,
this.spanExtractor, this.spanInjector);
this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector);
this.request = get("/favicon.ico").accept(MediaType.ALL)
.buildRequest(new MockServletContext());
@@ -115,9 +118,11 @@ public class TraceFilterTests {
@Test
public void startsNewTrace() throws Exception {
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter,
this.spanExtractor, this.spanInjector);
this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector);
filter.doFilter(this.request, this.response, this.filterChain);
verifyHttpTags();
verifyCurrentSpanStatusCode(HttpStatus.OK);
assertNull(TestSpanContextHolder.getCurrentSpan());
}
@@ -129,15 +134,27 @@ public class TraceFilterTests {
.header(Span.PARENT_ID_NAME, Span.idToHex(3L))
.buildRequest(new MockServletContext());
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter,
this.spanExtractor, this.spanInjector);
this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector);
filter.doFilter(this.request, this.response, this.filterChain);
// this creates a child span which is why we'd expect the parents to include 1L)
assertThat(this.span.getParents()).containsOnly(1L);
assertThat(parentSpan())
.hasATag("http.url", "http://localhost/?foo=bar")
.hasATag("http.host", "localhost")
.hasATag("http.path", "/")
.hasATag("http.method", "GET");
assertNull(TestSpanContextHolder.getCurrentSpan());
}
private Span parentSpan() {
Optional<Span> parent = this.spanReporter.getSpans().stream()
.filter(span -> span.getName().contains("parent")).findFirst();
assertThat(parent.isPresent()).isTrue();
return parent.get();
}
@Test
public void continuesSpanInRequestAttr() throws Exception {
Span span = this.tracer.createSpan("http:foo");
@@ -146,11 +163,9 @@ public class TraceFilterTests {
TestSpanContextHolder.removeCurrentSpan();
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter,
this.spanExtractor, this.spanInjector);
this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector);
filter.doFilter(this.request, this.response, this.filterChain);
verifyHttpTags();
assertNull(TestSpanContextHolder.getCurrentSpan());
}
@@ -160,10 +175,10 @@ public class TraceFilterTests {
.header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext());
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter,
this.spanExtractor, this.spanInjector);
this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector);
filter.doFilter(this.request, this.response, this.filterChain);
verifyHttpTags();
verifyParentSpanHttpTags();
assertNull(TestSpanContextHolder.getCurrentSpan());
}
@@ -175,11 +190,11 @@ public class TraceFilterTests {
this.traceKeys.getHttp().getHeaders().add("x-foo");
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter,
this.spanExtractor, this.spanInjector);
this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector);
this.request.addHeader("X-Foo", "bar");
filter.doFilter(this.request, this.response, this.filterChain);
assertThat(this.span.tags()).contains(entry("http.x-foo", "bar"));
assertThat(parentSpan().tags()).contains(entry("http.x-foo", "bar"));
assertNull(TestSpanContextHolder.getCurrentSpan());
}
@@ -189,7 +204,7 @@ public class TraceFilterTests {
this.request = builder().header(Span.SPAN_ID_NAME, 10L)
.header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext());
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, spanIsStoppedVeryfingReporter(),
this.spanExtractor, this.spanInjector);
this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector);
filter.doFilter(this.request, this.response, this.filterChain);
}
@@ -205,20 +220,22 @@ public class TraceFilterTests {
this.traceKeys.getHttp().getHeaders().add("x-foo");
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter,
this.spanExtractor, this.spanInjector);
this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector);
this.request.addHeader("X-Foo", "bar");
this.request.addHeader("X-Foo", "spam");
filter.doFilter(this.request, this.response, this.filterChain);
assertThat(this.span.tags()).contains(entry("http.x-foo", "'bar','spam'"));
assertThat(parentSpan().tags()).contains(entry("http.x-foo", "'bar','spam'"));
assertNull(TestSpanContextHolder.getCurrentSpan());
}
@Test
public void catchesException() throws Exception {
this.request = builder().header(Span.SPAN_ID_NAME, 10L)
.header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext());
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, this.spanReporter,
this.spanExtractor, this.spanInjector);
this.spanExtractor, this.spanInjector, this.httpTraceKeysInjector);
this.filterChain = new MockFilterChain() {
@Override
public void doFilter(javax.servlet.ServletRequest request,
@@ -233,24 +250,28 @@ public class TraceFilterTests {
catch (RuntimeException e) {
assertEquals("Planned", e.getMessage());
}
verifyHttpTags(HttpStatus.INTERNAL_SERVER_ERROR);
verifyParentSpanHttpTags(HttpStatus.INTERNAL_SERVER_ERROR);
assertNull(TestSpanContextHolder.getCurrentSpan());
}
public void verifyHttpTags() {
verifyHttpTags(HttpStatus.OK);
public void verifyParentSpanHttpTags() {
verifyParentSpanHttpTags(HttpStatus.OK);
}
/**
* Shows the expansion of {@link import
* org.springframework.cloud.sleuth.instrument.TraceKeys}.
*/
public void verifyHttpTags(HttpStatus status) {
assertThat(this.span.tags()).contains(entry("http.host", "localhost"),
public void verifyParentSpanHttpTags(HttpStatus status) {
assertThat(parentSpan().tags()).contains(entry("http.host", "localhost"),
entry("http.url", "http://localhost/?foo=bar"), entry("http.path", "/"),
entry("http.method", "GET"));
verifyCurrentSpanStatusCode(status);
}
private void verifyCurrentSpanStatusCode(HttpStatus status) {
// Status is only interesting in non-success case. Omitting it saves at least
// 20bytes per span.
if (status.is2xxSuccessful()) {

View File

@@ -29,7 +29,9 @@ import org.junit.Test;
import org.springframework.cloud.sleuth.DefaultSpanNamer;
import org.springframework.cloud.sleuth.NoOpSpanReporter;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.assertions.SleuthAssertions;
import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
import org.springframework.cloud.sleuth.log.NoOpSpanLogger;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.cloud.sleuth.trace.DefaultTracer;
@@ -59,7 +61,8 @@ public class TraceRestTemplateInterceptorIntegrationTests {
this.tracer = new DefaultTracer(new AlwaysSampler(), new Random(),
new DefaultSpanNamer(), new NoOpSpanLogger(), new NoOpSpanReporter());
this.template.setInterceptors(Arrays.<ClientHttpRequestInterceptor>asList(
new TraceRestTemplateInterceptor(this.tracer, new HttpRequestInjector())));
new TraceRestTemplateInterceptor(this.tracer, new HttpRequestInjector(),
new HttpTraceKeysInjector(this.tracer, new TraceKeys()))));
TestSpanContextHolder.removeCurrentSpan();
}
@@ -68,7 +71,7 @@ public class TraceRestTemplateInterceptorIntegrationTests {
TestSpanContextHolder.removeCurrentSpan();
}
// issue #198
// Issue #198
@Test
public void spanRemovedFromThreadUponException() throws IOException {
this.mockWebServer.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START));

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.sleuth.instrument.web.client;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
@@ -26,12 +27,14 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cloud.sleuth.DefaultSpanNamer;
import org.springframework.cloud.sleuth.NoOpSpanReporter;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
import org.springframework.cloud.sleuth.log.NoOpSpanLogger;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.cloud.sleuth.trace.DefaultTracer;
import org.springframework.cloud.sleuth.trace.TestSpanContextHolder;
import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.test.web.client.MockMvcClientHttpRequestFactory;
@@ -60,12 +63,15 @@ public class TraceRestTemplateInterceptorTests {
private DefaultTracer tracer;
private ArrayListSpanAccumulator spanAccumulator = new ArrayListSpanAccumulator();
@Before
public void setup() {
this.tracer = new DefaultTracer(new AlwaysSampler(), new Random(),
new DefaultSpanNamer(), new NoOpSpanLogger(), new NoOpSpanReporter());
new DefaultSpanNamer(), new NoOpSpanLogger(), this.spanAccumulator);
this.template.setInterceptors(Arrays.<ClientHttpRequestInterceptor>asList(
new TraceRestTemplateInterceptor(this.tracer, new HttpRequestInjector())));
new TraceRestTemplateInterceptor(this.tracer, new HttpRequestInjector(),
new HttpTraceKeysInjector(this.tracer, new TraceKeys()))));
TestSpanContextHolder.removeCurrentSpan();
}
@@ -95,6 +101,21 @@ public class TraceRestTemplateInterceptorTests {
then(Span.hexToId(headers.get(Span.PARENT_ID_NAME))).isEqualTo(2L);
}
// Issue #290
@Test
public void requestHeadersAddedWhenTracing() {
this.tracer.continueSpan(Span.builder().traceId(1L).spanId(2L).parent(3L).build());
this.template.getForEntity("/foo?a=b", Map.class);
List<Span> spans = spanAccumulator.getSpans();
then(spans).isNotEmpty();
then(spans.get(0))
.hasATag("http.url", "/foo?a=b")
.hasATag("http.path", "/foo")
.hasATag("http.method", "GET");
}
@Test
public void notSampledHeaderAddedWhenNotExportable() {
this.tracer.continueSpan(Span.builder().traceId(1L).spanId(2L).exportable(false).build());
@@ -145,6 +166,10 @@ public class TraceRestTemplateInterceptorTests {
return map;
}
@RequestMapping("/foo")
public void foo() {
}
@RequestMapping("/exception")
public Map<String, String> exception() {
throw new RuntimeException("foo");

View File

@@ -16,10 +16,6 @@
package org.springframework.cloud.sleuth.instrument.web.client;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.util.Map;
@@ -37,9 +33,11 @@ import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.assertions.SleuthAssertions;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.cloud.sleuth.trace.TestSpanContextHolder;
import org.springframework.cloud.sleuth.util.ExceptionUtils;
import org.springframework.context.annotation.Bean;
@@ -51,6 +49,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.client.RestTemplate;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {
WebClientDiscoveryExceptionTests.TestConfiguration.class })
@@ -59,13 +61,9 @@ import org.springframework.web.client.RestTemplate;
@DirtiesContext
public class WebClientDiscoveryExceptionTests {
@Autowired
TestFeignInterfaceWithException testFeignInterfaceWithException;
@Autowired
@LoadBalanced
RestTemplate template;
@Autowired
Tracer tracer;
@Autowired TestFeignInterfaceWithException testFeignInterfaceWithException;
@Autowired @LoadBalanced RestTemplate template;
@Autowired Tracer tracer;
@Before
public void open() {
@@ -128,6 +126,11 @@ public class WebClientDiscoveryExceptionTests {
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
Sampler alwaysSampler() {
return new AlwaysSampler();
}
}
@FunctionalInterface

View File

@@ -16,15 +16,14 @@
package org.springframework.cloud.sleuth.instrument.web.client;
import static junitparams.JUnitParamsRunner.$;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -40,9 +39,11 @@ import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.assertions.SleuthAssertions;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.cloud.sleuth.trace.TestSpanContextHolder;
import org.springframework.cloud.sleuth.util.ExceptionUtils;
import org.springframework.context.annotation.Bean;
@@ -54,13 +55,14 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.client.RestTemplate;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import static junitparams.JUnitParamsRunner.$;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
@RunWith(JUnitParamsRunner.class)
@SpringApplicationConfiguration(classes = {
WebClientExceptionTests.TestConfiguration.class })
@@ -73,13 +75,9 @@ public class WebClientExceptionTests {
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
@Autowired
TestFeignInterfaceWithException testFeignInterfaceWithException;
@Autowired
@LoadBalanced
RestTemplate template;
@Autowired
Tracer tracer;
@Autowired TestFeignInterfaceWithException testFeignInterfaceWithException;
@Autowired @LoadBalanced RestTemplate template;
@Autowired Tracer tracer;
@Before
public void open() {
@@ -139,6 +137,11 @@ public class WebClientExceptionTests {
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
Sampler alwaysSampler() {
return new AlwaysSampler();
}
}
@Configuration

View File

@@ -16,17 +16,18 @@
package org.springframework.cloud.sleuth.instrument.web.client;
import static junitparams.JUnitParamsRunner.$;
import static org.assertj.core.api.BDDAssertions.then;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import org.junit.After;
import org.junit.ClassRule;
import org.junit.Rule;
@@ -41,9 +42,11 @@ import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanReporter;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.cloud.sleuth.trace.TestSpanContextHolder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -58,52 +61,52 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import static junitparams.JUnitParamsRunner.$;
import static org.assertj.core.api.BDDAssertions.then;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
@RunWith(JUnitParamsRunner.class)
@SpringApplicationConfiguration(classes = { WebClientTests.TestConfiguration.class })
@WebIntegrationTest(value = { "spring.application.name=fooservice" }, randomPort = true)
public class WebClientTests {
@ClassRule
public static final SpringClassRule SCR = new SpringClassRule();
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
@ClassRule public static final SpringClassRule SCR = new SpringClassRule();
@Rule public final SpringMethodRule springMethodRule = new SpringMethodRule();
@Autowired
TestFeignInterface testFeignInterface;
@Autowired
@LoadBalanced
RestTemplate template;
@Autowired
Listener listener;
@Autowired
Tracer tracer;
@Autowired TestFeignInterface testFeignInterface;
@Autowired @LoadBalanced RestTemplate template;
@Autowired Listener listener;
@Autowired Tracer tracer;
@After
public void close() {
TestSpanContextHolder.removeCurrentSpan();
this.listener.getEvents().clear();
this.listener.getSpans().clear();
}
@Test
@Parameters
@SuppressWarnings("unchecked")
public void shouldCreateANewSpanWhenNoPreviousTracingWasPresent(
public void shouldCreateANewSpanWithClientSideTagsWhenNoPreviousTracingWasPresent(
ResponseEntityProvider provider) {
ResponseEntity<String> response = provider.get(this);
then(getHeader(response, Span.TRACE_ID_NAME)).isNotNull();
then(getHeader(response, Span.SPAN_ID_NAME)).isNotNull();
then(this.listener.getEvents()).isNotEmpty();
then(this.listener.getSpans()).isNotEmpty();
Optional<Span> noTraceSpan = this.listener.getSpans().stream().filter(span ->
"http:/notrace".equals(span.getName()) && !span.tags().isEmpty()).findFirst();
then(noTraceSpan.isPresent()).isTrue();
// TODO: matches cause there is an issue with Feign not providing the full URL at the interceptor level
then(noTraceSpan.get()).matchesATag("http.url", ".*/notrace")
.hasATag("http.path", "/notrace")
.hasATag("http.method", "GET");
}
Object[] parametersForShouldCreateANewSpanWhenNoPreviousTracingWasPresent() {
Object[] parametersForShouldCreateANewSpanWithClientSideTagsWhenNoPreviousTracingWasPresent() {
return $(
(ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(),
(ResponseEntityProvider) (tests) -> tests.template
@@ -123,7 +126,7 @@ public class WebClientTests {
then(response.getBody().get(Span.TRACE_ID_NAME)).isNotNull();
then(response.getBody().get(Span.SAMPLED_NAME)).isEqualTo(Span.SPAN_NOT_SAMPLED);
then(this.listener.getEvents()).isNotEmpty();
then(this.listener.getSpans()).isNotEmpty();
}
Object[] parametersForShouldPropagateNotSamplingHeader() {
@@ -152,7 +155,7 @@ public class WebClientTests {
}
private Span spanWithClientEvents() {
return this.listener.getEvents().stream()
return this.listener.getSpans().stream()
.filter(span -> span.logs().stream()
.filter(log -> log.getEvent().contains(Span.CLIENT_RECV)
|| log.getEvent().contains(Span.CLIENT_SEND))
@@ -241,13 +244,18 @@ public class WebClientTests {
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
Sampler testSampler() {
return new AlwaysSampler();
}
}
@Component
public static class Listener implements SpanReporter {
private List<Span> events = new ArrayList<>();
public List<Span> getEvents() {
public List<Span> getSpans() {
return this.events;
}

View File

@@ -16,12 +16,15 @@
package org.springframework.cloud.sleuth.instrument.web.client.feign;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import feign.codec.Decoder;
import feign.codec.ErrorDecoder;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -37,9 +40,11 @@ import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanReporter;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.cloud.sleuth.util.ExceptionUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -53,9 +58,8 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import feign.codec.Decoder;
import feign.codec.ErrorDecoder;
import static org.assertj.core.api.BDDAssertions.then;
@@ -169,6 +173,10 @@ public class FeignClientServerErrorTests {
return new RestTemplate();
}
@Bean Sampler testSampler() {
return new AlwaysSampler();
}
}
@FeignClient(value = "fooservice")

View File

@@ -9,6 +9,7 @@ import org.springframework.cloud.sleuth.SpanExtractor;
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.util.ExceptionUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.web.WebAppConfiguration;
@@ -36,6 +37,7 @@ public abstract class AbstractMvcIntegrationTest {
@Autowired protected TraceKeys traceKeys;
@Autowired protected SpanExtractor<HttpServletRequest> spanExtractor;
@Autowired protected SpanInjector<HttpServletResponse> spanInjector;
@Autowired protected HttpTraceKeysInjector httpTraceKeysInjector;
@Before
public void setup() {