From d30efca7036a1cc2323a283baffbff0d27ea46e1 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 31 May 2016 09:31:55 +0200 Subject: [PATCH] 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 --- .../instrument/web/HttpTraceKeysInjector.java | 81 +++++++++++++++++++ .../sleuth/instrument/web/TraceFilter.java | 43 +++++----- .../web/TraceWebAutoConfiguration.java | 5 +- .../AbstractTraceHttpRequestInterceptor.java | 19 ++++- ...eAsyncClientHttpRequestFactoryWrapper.java | 25 ++++-- .../client/TraceRestTemplateInterceptor.java | 6 +- .../TraceWebAsyncClientAutoConfiguration.java | 4 +- .../TraceWebClientAutoConfiguration.java | 16 +++- .../web/client/feign/SleuthFeignBuilder.java | 5 +- .../web/client/feign/TraceFeignClient.java | 21 ++++- .../TraceFeignClientAutoConfiguration.java | 33 ++++---- .../client/feign/TraceFeignObjectWrapper.java | 18 ++++- .../cloud/sleuth/assertions/SpanAssert.java | 29 +++++-- .../TraceChannelInterceptorTests.java | 6 ++ ...extPropagationChannelInterceptorTests.java | 5 ++ .../TraceWebSocketAutoConfigurationTest.java | 11 ++- ...stTemplateTraceAspectIntegrationTests.java | 21 +++-- ...ceFilterAlwaysSamplerIntegrationTests.java | 3 +- .../web/TraceFilterCustomExtractorTests.java | 30 +++---- .../web/TraceFilterIntegrationTests.java | 7 ++ .../TraceFilterMockChainIntegrationTests.java | 5 +- .../instrument/web/TraceFilterTests.java | 73 +++++++++++------ ...stTemplateInterceptorIntegrationTests.java | 7 +- .../TraceRestTemplateInterceptorTests.java | 31 ++++++- .../WebClientDiscoveryExceptionTests.java | 25 +++--- .../web/client/WebClientExceptionTests.java | 35 ++++---- .../instrument/web/client/WebClientTests.java | 64 ++++++++------- .../feign/FeignClientServerErrorTests.java | 18 +++-- .../common/AbstractMvcIntegrationTest.java | 2 + 29 files changed, 462 insertions(+), 186 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpTraceKeysInjector.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpTraceKeysInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpTraceKeysInjector.java new file mode 100644 index 000000000..0d9a1b4f1 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpTraceKeysInjector.java @@ -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> 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> headers) { + for (String name : this.traceKeys.getHttp().getHeaders()) { + for (Map.Entry> entry : headers.entrySet()) { + addTagForEntry(name, entry.getValue()); + } + } + } + + private void addTagForEntry(String name, Collection 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); + } + +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java index 0022aa125..e2bb7acf0 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java @@ -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 spanExtractor; private final SpanInjector spanInjector; + private final HttpTraceKeysInjector httpTraceKeysInjector; private UrlPathHelper urlPathHelper = new UrlPathHelper(); public TraceFilter(Tracer tracer, TraceKeys traceKeys, SpanReporter spanReporter, - SpanExtractor spanExtractor, SpanInjector spanInjector) { + SpanExtractor spanExtractor, + SpanInjector 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 spanExtractor, - SpanInjector spanInjector) { + SpanInjector 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 values = request.getHeaders(name); if (values.hasMoreElements()) { @@ -193,7 +200,7 @@ public class TraceFilter extends OncePerRequestFilter { ArrayList 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); } } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index 755cba087..453feeda5 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -76,9 +76,10 @@ public class TraceWebAutoConfiguration { public TraceFilter traceFilter(Tracer tracer, TraceKeys traceKeys, SkipPatternProvider skipPatternProvider, SpanReporter spanReporter, SpanExtractor spanExtractor, - SpanInjector spanInjector) { + SpanInjector spanInjector, + HttpTraceKeysInjector httpTraceKeysInjector) { return new TraceFilter(tracer, traceKeys, skipPatternProvider.skipPattern(), - spanReporter, spanExtractor, spanInjector); + spanReporter, spanExtractor, spanInjector, httpTraceKeysInjector); } @Bean diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java index 6ce1772d2..7c5e7e960 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java @@ -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 spanInjector; + protected final HttpTraceKeysInjector keysInjector; protected AbstractTraceHttpRequestInterceptor(Tracer tracer, - SpanInjector spanInjector) { + SpanInjector 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 */ diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java index 36691b026..96be114cc 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java @@ -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 spanInjector, - AsyncClientHttpRequestFactory asyncDelegate) { - super(tracer, spanInjector); + public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer, + SpanInjector 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 spanInjector) { - super(tracer, spanInjector); + public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer, + SpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector) { + super(tracer, spanInjector, httpTraceKeysInjector); SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = defaultClientHttpRequestFactory(); this.asyncDelegate = simpleClientHttpRequestFactory; this.syncDelegate = simpleClientHttpRequestFactory; } - public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer, SpanInjector spanInjector, - AsyncClientHttpRequestFactory asyncDelegate, ClientHttpRequestFactory syncDelegate) { - super(tracer, spanInjector); + public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer, + SpanInjector 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; } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java index 986596972..2c07340b0 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java @@ -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 spanInjector) { - super(tracer, spanInjector); + public TraceRestTemplateInterceptor(Tracer tracer, SpanInjector spanInjector, + HttpTraceKeysInjector httpTraceKeysInjector) { + super(tracer, spanInjector, httpTraceKeysInjector); } @Override diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java index e5f7e9412..9e7ae1289 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java @@ -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 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) { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java index 18cde8e19..d07efb11c 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java @@ -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 spanInjector) { - return new TraceRestTemplateInterceptor(tracer, spanInjector); + SpanInjector 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 { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java index 80f96d251..59fae1a2d 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java @@ -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)); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java index fd7c08c89..0c4af84be 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java @@ -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()); + } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java index 0512303a7..93fa64c3e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java @@ -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()); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapper.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapper.java index 873af78af..e4dc0022a 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapper.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapper.java @@ -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; + } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java index 5ec96aca2..1f7d0a081 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java @@ -88,12 +88,7 @@ public class SpanAssert extends AbstractAssert { 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 { 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) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java index 4511186b1..a144fd388 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java @@ -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(); + } + } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java index 74c38d0bf..5461d7283 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java @@ -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(); + } } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfigurationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfigurationTest.java index 12daee4ba..9e9839e01 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfigurationTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfigurationTest.java @@ -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(); + } } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java index 9f6a5018e..04f25fdf6 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java @@ -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 diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterAlwaysSamplerIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterAlwaysSamplerIntegrationTests.java index 15aee6a97..69de76f39 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterAlwaysSamplerIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterAlwaysSamplerIntegrationTests.java @@ -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) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java index ecfff32e4..fd12cce63 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java @@ -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[] diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java index 28c41e68d..6ddf5227e 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java @@ -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(); + } } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java index 3436805f3..db67aee4a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java @@ -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()); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java index 57d4ec127..2516dbc9e 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java @@ -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 spanExtractor = new HttpServletRequestExtractor(new Random(), Pattern .compile(TraceFilter.DEFAULT_SKIP_PATTERN)); SpanInjector 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 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()) { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java index 7764623e8..d47951f67 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java @@ -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.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)); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java index c98c4df7e..dc9e58b3b 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java @@ -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.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 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 exception() { throw new RuntimeException("foo"); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientDiscoveryExceptionTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientDiscoveryExceptionTests.java index f69b92779..971fbd653 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientDiscoveryExceptionTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientDiscoveryExceptionTests.java @@ -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 diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientExceptionTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientExceptionTests.java index 9a3050075..a739f955d 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientExceptionTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientExceptionTests.java @@ -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 diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java index 276b0111d..56bbfd0c5 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java @@ -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 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 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 events = new ArrayList<>(); - public List getEvents() { + public List getSpans() { return this.events; } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignClientServerErrorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignClientServerErrorTests.java index 8fd9da7d5..3bfd2ec93 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignClientServerErrorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignClientServerErrorTests.java @@ -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") diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/AbstractMvcIntegrationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/AbstractMvcIntegrationTest.java index 873be11bb..fee7e35f3 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/AbstractMvcIntegrationTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/AbstractMvcIntegrationTest.java @@ -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 spanExtractor; @Autowired protected SpanInjector spanInjector; + @Autowired protected HttpTraceKeysInjector httpTraceKeysInjector; @Before public void setup() {