diff --git a/pom.xml b/pom.xml index e85d23abd..1b22ce281 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,6 @@ spring-cloud-sleuth-core spring-cloud-sleuth-zipkin spring-cloud-sleuth-stream - spring-cloud-sleuth-reactor spring-cloud-sleuth-zipkin-stream spring-cloud-starter-sleuth spring-cloud-starter-zipkin @@ -172,6 +171,21 @@ pom import + + + io.projectreactor + reactor-bom + ${reactor.version} + pom + import + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + org.spockframework @@ -201,16 +215,6 @@ commons-logging 1.2 - - io.projectreactor - reactor-core - ${reactor.version} - - - org.reactivestreams - reactive-streams - ${reactive-streams.version} - org.hamcrest hamcrest-core @@ -254,8 +258,9 @@ 2.0.0.BUILD-SNAPSHOT Elmhurst.BUILD-SNAPSHOT 2.0.0.BUILD-SNAPSHOT - 3.1.0.BUILD-SNAPSHOT - 1.0.0 + Bismuth-BUILD-SNAPSHOT + + 2.0.0.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 71d54a570..a62eb5022 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -21,6 +21,21 @@ spring-boot-starter-web true + + org.springframework.boot + spring-boot-starter-webflux + true + + + io.projectreactor + reactor-core + true + + + org.reactivestreams + reactive-streams + true + org.springframework.boot spring-boot-starter-websocket diff --git a/spring-cloud-sleuth-reactor/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/SpanSubscriber.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/SpanSubscriber.java similarity index 82% rename from spring-cloud-sleuth-reactor/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/SpanSubscriber.java rename to spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/SpanSubscriber.java index eaa976613..f49df0725 100644 --- a/spring-cloud-sleuth-reactor/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/SpanSubscriber.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/SpanSubscriber.java @@ -18,7 +18,8 @@ import reactor.util.context.Context; * @author Marcin Grzejszczak * @since 1.3.0 */ -class SpanSubscriber extends AtomicBoolean implements Subscription, CoreSubscriber { +class SpanSubscriber extends AtomicBoolean + implements Subscription, CoreSubscriber { private static final Logger log = Loggers.getLogger(SpanSubscriber.class); @@ -61,29 +62,30 @@ class SpanSubscriber extends AtomicBoolean implements Subscription, CoreSubscrib } @Override public void request(long n) { - if (log.isTraceEnabled()) { - log.trace("Request"); - } this.tracer.continueSpan(this.span); if (log.isTraceEnabled()) { log.trace("Request - continued"); } this.s.request(n); + Span localSpan = this.span; + Span rootSpan = this.rootSpan; // We're in the main thread so we don't want to pollute it with wrong spans // that's why we need to detach the current one and continue with its parent - Span localRootSpan = this.span; - while (localRootSpan != null) { - if (this.rootSpan != null) { - if (localRootSpan.getSpanId() != this.rootSpan.getSpanId() && - !isRootParentSpan(localRootSpan)) { - localRootSpan = continueDetachedSpan(localRootSpan); + if (log.isTraceEnabled()) { + log.trace("Will detach spans. Root span is " + rootSpan + " and stored span is " + localSpan); + } + while (localSpan != null) { + if (rootSpan != null) { + if (localSpan.getSpanId() != rootSpan.getSpanId() && + !isRootParentSpan(localSpan)) { + localSpan = continueDetachedSpan(localSpan); } else { - localRootSpan = null; + localSpan = null; } - } else if (!isRootParentSpan(localRootSpan)) { - localRootSpan = continueDetachedSpan(localRootSpan); + } else if (!isRootParentSpan(localSpan)) { + localSpan = continueDetachedSpan(localSpan); } else { - localRootSpan = null; + localSpan = null; } } if (log.isTraceEnabled()) { @@ -101,7 +103,12 @@ class SpanSubscriber extends AtomicBoolean implements Subscription, CoreSubscrib log.trace("Will detach span {}", localRootSpan); } Span detachedSpan = this.tracer.detach(localRootSpan); - return this.tracer.continueSpan(detachedSpan); + Span continuedSpan = this.tracer.continueSpan(detachedSpan); + if (log.isTraceEnabled()) { + log.trace("Now current span is " + continuedSpan + ". Root span is " + this.rootSpan + + " and stored span for the subscriber is " + this.span); + } + return continuedSpan; } @Override public void cancel() { diff --git a/spring-cloud-sleuth-reactor/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/TraceReactorAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/TraceReactorAutoConfiguration.java similarity index 89% rename from spring-cloud-sleuth-reactor/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/TraceReactorAutoConfiguration.java rename to spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/TraceReactorAutoConfiguration.java index 60ab0de42..5890d2bd3 100644 --- a/spring-cloud-sleuth-reactor/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/TraceReactorAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/TraceReactorAutoConfiguration.java @@ -8,12 +8,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.sleuth.SpanNamer; 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.async.TraceableScheduledExecutorService; +import org.springframework.cloud.sleuth.instrument.web.TraceWebFluxAutoConfiguration; import org.springframework.context.annotation.Configuration; import reactor.core.publisher.Hooks; import reactor.core.publisher.Mono; @@ -30,11 +31,12 @@ import reactor.core.scheduler.Schedulers; @Configuration @ConditionalOnProperty(value="spring.sleuth.reactor.enabled", matchIfMissing=true) @ConditionalOnClass(Mono.class) -@AutoConfigureAfter(TraceAutoConfiguration.class) +@AutoConfigureAfter(TraceWebFluxAutoConfiguration.class) public class TraceReactorAutoConfiguration { @Configuration @ConditionalOnBean(Tracer.class) + @ConditionalOnNotWebApplication static class TraceReactorConfiguration { @Autowired Tracer tracer; @Autowired TraceKeys traceKeys; diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ServerHttpRequestTextMap.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ServerHttpRequestTextMap.java new file mode 100644 index 000000000..45e5c5039 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ServerHttpRequestTextMap.java @@ -0,0 +1,41 @@ +package org.springframework.cloud.sleuth.instrument.web; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.springframework.cloud.sleuth.SpanTextMap; +import org.springframework.http.server.reactive.ServerHttpRequest; + +/** + * Created by mgrzejszczak. + */ +class ServerHttpRequestTextMap implements SpanTextMap { + + private final ServerHttpRequest delegate; + private final Map additionalHeaders = new HashMap<>(); + + ServerHttpRequestTextMap(ServerHttpRequest delegate) { + this.delegate = delegate; + this.additionalHeaders.put(ZipkinHttpSpanExtractor.URI_HEADER, + delegate.getPath().pathWithinApplication().value()); + } + + @Override + public Iterator> iterator() { + Map map = new HashMap<>(); + for (Map.Entry> entry : this.delegate.getHeaders() + .entrySet()) { + map.put(entry.getKey(), entry.getValue() != null ? + entry.getValue().isEmpty() ? "" : entry.getValue().get(0) : ""); + } + map.putAll(this.additionalHeaders); + return map.entrySet().iterator(); + } + + @Override + public void put(String key, String value) { + this.additionalHeaders.put(key, value); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProvider.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProvider.java new file mode 100644 index 000000000..8b1fb4be2 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProvider.java @@ -0,0 +1,13 @@ +package org.springframework.cloud.sleuth.instrument.web; + +import java.util.regex.Pattern; + +/** + * Internal interface to describe patterns to skip tracing + * + * @author Marcin Grzejszczak + * @since 2.0.0 + */ +interface SkipPatternProvider { + Pattern skipPattern(); +} 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 d00bafdcd..9a539de11 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 @@ -65,7 +65,7 @@ import org.springframework.web.util.UrlPathHelper; * * @see Tracer * @see TraceKeys - * @see TraceWebAutoConfiguration#traceFilter + * @see TraceWebServletAutoConfiguration#traceFilter */ @Order(TraceFilter.ORDER) public class TraceFilter extends GenericFilterBean { @@ -92,12 +92,12 @@ public class TraceFilter extends GenericFilterBean { private Tracer tracer; private TraceKeys traceKeys; - private Pattern skipPattern; + private final Pattern skipPattern; private SpanReporter spanReporter; private HttpSpanExtractor spanExtractor; private HttpTraceKeysInjector httpTraceKeysInjector; private ErrorParser errorParser; - private BeanFactory beanFactory; + private final BeanFactory beanFactory; private UrlPathHelper urlPathHelper = new UrlPathHelper(); 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 f9cffada3..42c9e6a3a 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 @@ -17,7 +17,6 @@ package org.springframework.cloud.sleuth.instrument.web; import java.util.regex.Pattern; -import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -27,79 +26,26 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClas import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.util.StringUtils; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; - -import static javax.servlet.DispatcherType.ASYNC; -import static javax.servlet.DispatcherType.ERROR; -import static javax.servlet.DispatcherType.FORWARD; -import static javax.servlet.DispatcherType.INCLUDE; -import static javax.servlet.DispatcherType.REQUEST; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration - * Auto-configuration} enables tracing to HTTP requests. + * Auto-configuration} that sets up common building blocks for both reactive + * and servlet based web application. * - * @author Tomasz Nurkewicz, 4financeIT - * @author Michal Chmielarz, 4financeIT * @author Marcin Grzejszczak - * @author Spencer Gibb * @since 1.0.0 */ @Configuration @ConditionalOnProperty(value = "spring.sleuth.web.enabled", matchIfMissing = true) -@ConditionalOnWebApplication +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.ANY) @ConditionalOnBean(Tracer.class) @AutoConfigureAfter(TraceHttpAutoConfiguration.class) public class TraceWebAutoConfiguration { - /** - * Nested config that configures Web MVC if it's present (without adding a runtime - * dependency to it) - */ - @Configuration - @ConditionalOnClass(WebMvcConfigurerAdapter.class) - @Import(TraceWebMvcConfigurer.class) - protected static class TraceWebMvcAutoConfiguration { - } - - @Bean - public TraceWebAspect traceWebAspect(Tracer tracer, TraceKeys traceKeys, - SpanNamer spanNamer, ErrorParser errorParser) { - return new TraceWebAspect(tracer, spanNamer, traceKeys, errorParser); - } - - @Bean - @ConditionalOnClass(name = "org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping") - public TraceSpringDataBeanPostProcessor traceSpringDataBeanPostProcessor( - BeanFactory beanFactory) { - return new TraceSpringDataBeanPostProcessor(beanFactory); - } - - @Bean - public FilterRegistrationBean traceWebFilter(TraceFilter traceFilter) { - FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean( - traceFilter); - filterRegistrationBean.setDispatcherTypes(ASYNC, ERROR, FORWARD, INCLUDE, - REQUEST); - filterRegistrationBean.setOrder(TraceFilter.ORDER); - return filterRegistrationBean; - } - - @Bean - public TraceFilter traceFilter(BeanFactory beanFactory, - SkipPatternProvider skipPatternProvider) { - return new TraceFilter(beanFactory, skipPatternProvider.skipPattern()); - } - @Configuration @ConditionalOnClass(ManagementServerProperties.class) @ConditionalOnMissingBean(SkipPatternProvider.class) @@ -157,12 +103,7 @@ public class TraceWebAutoConfiguration { private static SkipPatternProvider defaultSkipPatternProvider( final String skipPattern) { - return new SkipPatternProvider() { - @Override - public Pattern skipPattern() { - return defaultSkipPattern(skipPattern); - } - }; + return () -> defaultSkipPattern(skipPattern); } private static Pattern defaultSkipPattern(String skipPattern) { @@ -170,7 +111,5 @@ public class TraceWebAutoConfiguration { : Pattern.compile(SleuthWebProperties.DEFAULT_SKIP_PATTERN); } - interface SkipPatternProvider { - Pattern skipPattern(); - } } + diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFilter.java new file mode 100644 index 000000000..1c7cb8ce6 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFilter.java @@ -0,0 +1,262 @@ +package org.springframework.cloud.sleuth.instrument.web; + +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.sleuth.ErrorParser; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.SpanReporter; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.sampler.NeverSampler; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.util.StringUtils; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Mono; + +/** + * A {@link WebFilter} that creates / continues / closes and detaches spans + * for a reactive web application. + * + * @author Marcin Grzejszczak + * @since 2.0.0 + */ +public class TraceWebFilter implements WebFilter { + + private static final Log log = LogFactory.getLog(TraceWebFilter.class); + + protected static final String TRACE_REQUEST_ATTR = TraceWebFilter.class.getName() + + ".TRACE"; + private static final String HTTP_COMPONENT = "http"; + + private Tracer tracer; + private TraceKeys traceKeys; + private final Pattern skipPattern; + private SpanReporter spanReporter; + private HttpSpanExtractor spanExtractor; + private HttpTraceKeysInjector httpTraceKeysInjector; + private ErrorParser errorParser; + private final BeanFactory beanFactory; + + TraceWebFilter(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + this.skipPattern = Pattern.compile(SleuthWebProperties.DEFAULT_SKIP_PATTERN); + } + + TraceWebFilter(BeanFactory beanFactory, Pattern skipPattern) { + this.beanFactory = beanFactory; + this.skipPattern = skipPattern; + } + + @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + ServerHttpResponse response = exchange.getResponse(); + String uri = request.getPath().pathWithinApplication().value(); + boolean skip = this.skipPattern.matcher(uri).matches() + || Span.SPAN_NOT_SAMPLED.equals(sampledHeader(request)); + if (log.isDebugEnabled()) { + log.debug("Received a request to uri [" + uri + "] that should not be sampled [" + skip + "]"); + } + Optional spanFromAttribute = getSpanFromAttribute(exchange); + spanFromAttribute.ifPresent(span -> continueSpan(exchange, span)); + String name = HTTP_COMPONENT + ":" + uri; + Span span = createSpan(request, exchange, skip, spanFromAttribute, name); + return chain.filter(exchange).compose(f -> f.doOnSuccess(t -> { + addResponseTags(response, null); + }).doOnError(t -> { + errorParser().parseErrorTags(tracer().getCurrentSpan(), t); + addResponseTags(response, t); + }).doFinally(t -> detachOrCloseSpans(span))); + } + + private String sampledHeader(ServerHttpRequest request) { + return getHeader(request, Span.SAMPLED_NAME); + } + + private void continueSpan(ServerWebExchange exchange, Span spanFromRequest) { + tracer().continueSpan(spanFromRequest); + exchange.getAttributes().put(TraceRequestAttributes.SPAN_CONTINUED_REQUEST_ATTR, "true"); + if (log.isDebugEnabled()) { + log.debug("There has already been a span in the request " + spanFromRequest); + } + } + + /** + * Creates a span and appends it as the current request's attribute + */ + private Span createSpan(ServerHttpRequest request, ServerWebExchange exchange, + boolean skip, Optional spanFromAttribute, String name) { + Span spanFromRequest = null; + if (spanFromAttribute.isPresent()) { + if (log.isDebugEnabled()) { + log.debug("Span has already been created - continuing with the previous one"); + } + return spanFromAttribute.get(); + } + Span parent = spanExtractor().joinTrace(new ServerHttpRequestTextMap(request)); + if (parent != null) { + if (log.isDebugEnabled()) { + log.debug("Found a parent span " + parent + " in the request"); + } + addRequestTagsForParentSpan(request, parent); + spanFromRequest = parent; + tracer().continueSpan(spanFromRequest); + if (parent.isRemote()) { + parent.logEvent(Span.SERVER_RECV); + } + exchange.getAttributes().put(TRACE_REQUEST_ATTR, spanFromRequest); + if (log.isDebugEnabled()) { + log.debug("Parent span is " + parent + ""); + } + } else { + if (skip) { + spanFromRequest = tracer().createSpan(name, NeverSampler.INSTANCE); + } + else { + String header = getHeader(request, Span.SPAN_FLAGS); + if (Span.SPAN_SAMPLED.equals(header)) { + spanFromRequest = tracer().createSpan(name, new AlwaysSampler()); + } else { + spanFromRequest = tracer().createSpan(name); + } + } + spanFromRequest.logEvent(Span.SERVER_RECV); + exchange.getAttributes().put(TRACE_REQUEST_ATTR, spanFromRequest); + if (log.isDebugEnabled()) { + log.debug("No parent span present - creating a new span"); + } + } + return spanFromRequest; + } + + private String getHeader(ServerHttpRequest request, String headerName) { + List list = request.getHeaders().get(headerName); + return list == null ? "" : list.isEmpty() ? "" : list.get(0); + } + + /** Override to add annotations not defined in {@link TraceKeys}. */ + protected void addRequestTags(Span span, ServerHttpRequest request) { + keysInjector().addRequestTags(span, request.getURI(), request.getMethod().toString()); + for (String name : traceKeys().getHttp().getHeaders()) { + List values = request.getHeaders().get(name); + if (values != null && !values.isEmpty()) { + String key = traceKeys().getHttp().getPrefix() + name.toLowerCase(); + String value = values.size() == 1 ? values.get(0) + : StringUtils.collectionToDelimitedString(values, ",", "'", "'"); + keysInjector().tagSpan(span, key, value); + } + } + } + + /** Override to add annotations not defined in {@link TraceKeys}. */ + protected void addResponseTags(ServerHttpResponse response, Throwable e) { + HttpStatus httpStatus = response.getStatusCode(); + if (httpStatus != null && httpStatus == HttpStatus.OK && e != null) { + // Filter chain threw exception but the response status may not have been set + // yet, so we have to guess. + tracer().addTag(traceKeys().getHttp().getStatusCode(), + String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value())); + } + // only tag valid http statuses + else if (httpStatus != null && + (httpStatus.value() >= 100 && (httpStatus.value() < 200) || (httpStatus.value() > 399))) { + tracer().addTag(traceKeys().getHttp().getStatusCode(), + String.valueOf(response.getStatusCode().value())); + } + } + + /** + * In order not to send unnecessary data we're not adding request tags to the server + * side spans. All the tags are there on the client side. + */ + private void addRequestTagsForParentSpan(ServerHttpRequest request, Span spanFromRequest) { + if (spanFromRequest.getName().contains("parent")) { + addRequestTags(spanFromRequest, request); + } + } + + private Optional getSpanFromAttribute(ServerWebExchange exchange) { + Optional attribute = exchange.getAttribute(TRACE_REQUEST_ATTR); + return attribute == null ? Optional.ofNullable(null) : attribute; + } + + private void detachOrCloseSpans(Span spanFromRequest) { + Span span = spanFromRequest; + if (span != null) { + if (span.hasSavedSpan()) { + recordParentSpan(span.getSavedSpan()); + } + recordParentSpan(span); + tracer().close(span); + } + } + + private void recordParentSpan(Span parent) { + if (parent == null) { + return; + } + if (parent.isRemote()) { + if (log.isDebugEnabled()) { + log.debug("Trying to send the parent span " + parent + " to Zipkin"); + } + parent.stop(); + // should be already done by HttpServletResponse wrappers + SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(parent); + spanReporter().report(parent); + } else { + // should be already done by HttpServletResponse wrappers + SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(parent); + } + } + + Tracer tracer() { + if (this.tracer == null) { + this.tracer = this.beanFactory.getBean(Tracer.class); + } + return this.tracer; + } + + TraceKeys traceKeys() { + if (this.traceKeys == null) { + this.traceKeys = this.beanFactory.getBean(TraceKeys.class); + } + return this.traceKeys; + } + + SpanReporter spanReporter() { + if (this.spanReporter == null) { + this.spanReporter = this.beanFactory.getBean(SpanReporter.class); + } + return this.spanReporter; + } + + HttpSpanExtractor spanExtractor() { + if (this.spanExtractor == null) { + this.spanExtractor = this.beanFactory.getBean(HttpSpanExtractor.class); + } + return this.spanExtractor; + } + + HttpTraceKeysInjector keysInjector() { + if (this.httpTraceKeysInjector == null) { + this.httpTraceKeysInjector = this.beanFactory.getBean(HttpTraceKeysInjector.class); + } + return this.httpTraceKeysInjector; + } + + ErrorParser errorParser() { + if (this.errorParser == null) { + this.errorParser = this.beanFactory.getBean(ErrorParser.class); + } + return this.errorParser; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFluxAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFluxAutoConfiguration.java new file mode 100644 index 000000000..bb5867a27 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFluxAutoConfiguration.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cloud.sleuth.instrument.web; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration + * Auto-configuration} enables tracing to HTTP requests with Spring WebFlux. + * + * @author Marcin Grzejszczak + * @since 2.0.0 + */ +@Configuration +@ConditionalOnProperty(value = "spring.sleuth.web.enabled", matchIfMissing = true) +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) +@ConditionalOnBean(Tracer.class) +@AutoConfigureAfter(TraceWebAutoConfiguration.class) +public class TraceWebFluxAutoConfiguration { + + @Bean + public TraceWebFilter traceFilter(BeanFactory beanFactory, + SkipPatternProvider skipPatternProvider) { + return new TraceWebFilter(beanFactory, skipPatternProvider.skipPattern()); + } + +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebServletAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebServletAutoConfiguration.java new file mode 100644 index 000000000..67ffe073d --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebServletAutoConfiguration.java @@ -0,0 +1,95 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cloud.sleuth.instrument.web; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.cloud.sleuth.ErrorParser; +import org.springframework.cloud.sleuth.SpanNamer; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +import static javax.servlet.DispatcherType.ASYNC; +import static javax.servlet.DispatcherType.ERROR; +import static javax.servlet.DispatcherType.FORWARD; +import static javax.servlet.DispatcherType.INCLUDE; +import static javax.servlet.DispatcherType.REQUEST; + +/** + * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration + * Auto-configuration} enables tracing to HTTP requests. + * + * @author Tomasz Nurkewicz, 4financeIT + * @author Michal Chmielarz, 4financeIT + * @author Marcin Grzejszczak + * @author Spencer Gibb + * @since 1.0.0 + */ +@Configuration +@ConditionalOnProperty(value = "spring.sleuth.web.enabled", matchIfMissing = true) +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +@ConditionalOnBean(Tracer.class) +@AutoConfigureAfter(TraceHttpAutoConfiguration.class) +public class TraceWebServletAutoConfiguration { + + /** + * Nested config that configures Web MVC if it's present (without adding a runtime + * dependency to it) + */ + @Configuration + @ConditionalOnClass(WebMvcConfigurerAdapter.class) + @Import(TraceWebMvcConfigurer.class) + protected static class TraceWebMvcAutoConfiguration { + } + + @Bean + public TraceWebAspect traceWebAspect(Tracer tracer, TraceKeys traceKeys, + SpanNamer spanNamer, ErrorParser errorParser) { + return new TraceWebAspect(tracer, spanNamer, traceKeys, errorParser); + } + + @Bean + @ConditionalOnClass(name = "org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping") + public TraceSpringDataBeanPostProcessor traceSpringDataBeanPostProcessor( + BeanFactory beanFactory) { + return new TraceSpringDataBeanPostProcessor(beanFactory); + } + + @Bean + public FilterRegistrationBean traceWebFilter(TraceFilter traceFilter) { + FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean( + traceFilter); + filterRegistrationBean.setDispatcherTypes(ASYNC, ERROR, FORWARD, INCLUDE, + REQUEST); + filterRegistrationBean.setOrder(TraceFilter.ORDER); + return filterRegistrationBean; + } + + @Bean + public TraceFilter traceFilter(BeanFactory beanFactory, + SkipPatternProvider skipPatternProvider) { + return new TraceFilter(beanFactory, skipPatternProvider.skipPattern()); + } +} 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 8b067730d..1f08e4220 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,7 +26,7 @@ import org.springframework.cloud.sleuth.ErrorParser; import org.springframework.cloud.sleuth.instrument.web.HttpSpanInjector; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; -import org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration; +import org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.task.AsyncListenableTaskExecutor; @@ -49,7 +49,7 @@ import org.springframework.web.client.AsyncRestTemplate; @ConditionalOnProperty(value = "spring.sleuth.web.async.client.enabled", matchIfMissing = true) @ConditionalOnClass(AsyncRestTemplate.class) @ConditionalOnBean(HttpTraceKeysInjector.class) -@AutoConfigureAfter(TraceWebAutoConfiguration.class) +@AutoConfigureAfter(TraceWebServletAutoConfiguration.class) public class TraceWebAsyncClientAutoConfiguration { @Autowired 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 8e6b19d49..4d88bf934 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 @@ -21,6 +21,7 @@ import java.util.Collection; import java.util.List; import javax.annotation.PostConstruct; +import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -30,11 +31,12 @@ import org.springframework.cloud.sleuth.ErrorParser; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.web.HttpSpanInjector; import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; -import org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration; +import org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.WebClient; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration @@ -46,39 +48,50 @@ import org.springframework.web.client.RestTemplate; */ @Configuration @SleuthWebClientEnabled -@ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(HttpTraceKeysInjector.class) -@AutoConfigureAfter(TraceWebAutoConfiguration.class) +@AutoConfigureAfter(TraceWebServletAutoConfiguration.class) public class TraceWebClientAutoConfiguration { - @Bean - @ConditionalOnMissingBean - public TraceRestTemplateInterceptor traceRestTemplateInterceptor(Tracer tracer, - HttpSpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector, - ErrorParser errorParser) { - return new TraceRestTemplateInterceptor(tracer, spanInjector, - httpTraceKeysInjector, errorParser); - } + @ConditionalOnClass(RestTemplate.class) + static class RestTemplateConfig { + @Bean + @ConditionalOnMissingBean + public TraceRestTemplateInterceptor traceRestTemplateInterceptor(Tracer tracer, + HttpSpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector, + ErrorParser errorParser) { + return new TraceRestTemplateInterceptor(tracer, spanInjector, + httpTraceKeysInjector, errorParser); + } - @Configuration - protected static class TraceInterceptorConfiguration { + @Configuration + protected static class TraceInterceptorConfiguration { - @Autowired(required = false) - private Collection restTemplates; + @Autowired(required = false) + private Collection restTemplates; - @Autowired - private TraceRestTemplateInterceptor traceRestTemplateInterceptor; + @Autowired + private TraceRestTemplateInterceptor traceRestTemplateInterceptor; - @PostConstruct - public void init() { - if (this.restTemplates != null) { - for (RestTemplate restTemplate : this.restTemplates) { - List interceptors = new ArrayList( - restTemplate.getInterceptors()); - interceptors.add(this.traceRestTemplateInterceptor); - restTemplate.setInterceptors(interceptors); + @PostConstruct + public void init() { + if (this.restTemplates != null) { + for (RestTemplate restTemplate : this.restTemplates) { + List interceptors = new ArrayList( + restTemplate.getInterceptors()); + interceptors.add(this.traceRestTemplateInterceptor); + restTemplate.setInterceptors(interceptors); + } } } } } + + @ConditionalOnClass(WebClient.class) + static class WebClientConfig { + + @Bean + TraceWebClientBeanPostProcessor traceWebClientBeanPostProcessor(BeanFactory beanFactory) { + return new TraceWebClientBeanPostProcessor(beanFactory); + } + } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientBeanPostProcessor.java new file mode 100644 index 000000000..09b8daa40 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientBeanPostProcessor.java @@ -0,0 +1,230 @@ +package org.springframework.cloud.sleuth.instrument.web.client; + +import java.net.URI; +import java.util.AbstractMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.cloud.sleuth.ErrorParser; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.SpanTextMap; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.instrument.web.HttpSpanInjector; +import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; +import org.springframework.cloud.sleuth.util.SpanNameUtil; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClientException; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.ExchangeFunction; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +/** + * {@link BeanPostProcessor} to wrap a {@link WebClient} instance into + * its trace representation + * + * @author Marcin Grzejszczak + * @since 2.0.0 + */ +class TraceWebClientBeanPostProcessor implements BeanPostProcessor { + + private final BeanFactory beanFactory; + + TraceWebClientBeanPostProcessor(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + @Override public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof WebClient) { + WebClient webClient = (WebClient) bean; + return webClient + .mutate() + .filter(new TraceExchangeFilterFunction(this.beanFactory)) + .build(); + } + return bean; + } +} + +class TraceExchangeFilterFunction implements ExchangeFilterFunction { + + private static final Log log = LogFactory.getLog(TraceExchangeFilterFunction.class); + + private Tracer tracer; + private HttpSpanInjector spanInjector; + private HttpTraceKeysInjector keysInjector; + private ErrorParser errorParser; + private final BeanFactory beanFactory; + + TraceExchangeFilterFunction(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override public Mono filter(ClientRequest request, + ExchangeFunction next) { + if (log.isDebugEnabled()) { + log.debug("Creating a client span for the RPC"); + } + final Span clientSpan = createNewSpan(request); + ClientRequest.Builder builder = ClientRequest.from(request); + httpSpanInjector().inject(clientSpan, new ClientRequestTextMap(request, builder)); + if (log.isDebugEnabled()) { + log.debug("Headers got injected to the client span " + clientSpan); + } + Mono exchange = next.exchange(builder.build()) + .doOnError(throwable -> { + tracer().continueSpan(clientSpan); + errorParser().parseErrorTags(clientSpan, throwable); + }).doOnSuccess(response -> { + tracer().continueSpan(clientSpan); + boolean error = response.statusCode().is4xxClientError() || response + .statusCode().is5xxServerError(); + if (error) { + if (log.isDebugEnabled()) { + log.debug( + "Non positive status code was returned from the call. Will close the span [" + + clientSpan + "]"); + } + errorParser().parseErrorTags(clientSpan, new RestClientException( + "Status code of the response is [" + response.statusCode() + .value() + "] and the reason is [" + response + .statusCode().getReasonPhrase() + "]")); + } + }).doFinally(signalType -> finish(clientSpan)); + if (log.isDebugEnabled()) { + log.debug("Will detach the client span " + clientSpan); + } + Span detachedSpan = tracer().detach(clientSpan); + tracer().continueSpan(detachedSpan); + if (log.isDebugEnabled()) { + log.debug("Client span detached"); + } + return exchange; + } + + /** + * Enriches the request with proper headers and publishes + * the client sent event + */ + private Span createNewSpan(ClientRequest request) { + URI uri = request.url(); + String spanName = getName(uri); + Span newSpan = tracer().createSpan(spanName); + addRequestTags(request); + newSpan.logEvent(Span.CLIENT_SEND); + if (log.isDebugEnabled()) { + log.debug("Starting new client span [" + newSpan + "]"); + } + return newSpan; + } + + private String getName(URI uri) { + return SpanNameUtil.shorten(uriScheme(uri) + ":" + uri.getPath()); + } + + private String uriScheme(URI uri) { + return uri.getScheme() == null ? "http" : uri.getScheme(); + } + + /** + * Adds HTTP tags to the client side span + */ + private void addRequestTags(ClientRequest request) { + keysInjector().addRequestTags(request.url().toString(), + request.url().getHost(), + request.url().getPath(), + request.method().name(), + request.headers()); + } + + /** + * Close the current span and log the client received event + */ + private void finish(Span span) { + tracer().continueSpan(span); + if (log.isDebugEnabled()) { + log.debug("Will close span and mark it with Client Received" + span); + } + span.logEvent(Span.CLIENT_RECV); + tracer().close(span); + } + + private Tracer tracer() { + if (this.tracer == null) { + this.tracer = this.beanFactory.getBean(Tracer.class); + } + return this.tracer; + } + + private HttpSpanInjector httpSpanInjector() { + if (this.spanInjector == null) { + this.spanInjector = this.beanFactory.getBean(HttpSpanInjector.class); + } + return this.spanInjector; + } + + private HttpTraceKeysInjector keysInjector() { + if (this.keysInjector == null) { + this.keysInjector = this.beanFactory.getBean(HttpTraceKeysInjector.class); + } + return this.keysInjector; + } + + private ErrorParser errorParser() { + if (this.errorParser == null) { + this.errorParser = this.beanFactory.getBean(ErrorParser.class); + } + return this.errorParser; + } +} + +class ClientRequestTextMap implements SpanTextMap { + + private final ClientRequest.Builder writeDelegate; + private final ClientRequest readDelegate; + + ClientRequestTextMap(ClientRequest readDelegate, + ClientRequest.Builder writeDelegate) { + this.readDelegate = readDelegate; + this.writeDelegate = writeDelegate; + } + + @Override + public Iterator> iterator() { + final Iterator>> iterator = this.readDelegate.headers() + .entrySet().iterator(); + return new Iterator>() { + @Override public boolean hasNext() { + return iterator.hasNext(); + } + + @Override public Map.Entry next() { + Map.Entry> next = iterator.next(); + List value = next.getValue(); + return new AbstractMap.SimpleEntry<>(next.getKey(), value.isEmpty() ? "" : value.get(0)); + } + }; + } + + @Override + public void put(String key, String value) { + if (!StringUtils.hasText(value)) { + return; + } + this.writeDelegate.header(key, value); + } +} \ No newline at end of file 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 5b717e6a0..3c1b4d0bf 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 @@ -26,7 +26,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.netflix.feign.FeignAutoConfiguration; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.hystrix.SleuthHystrixAutoConfiguration; -import org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration; +import org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; @@ -47,7 +47,7 @@ import feign.okhttp.OkHttpClient; @ConditionalOnClass(Client.class) @ConditionalOnBean(Tracer.class) @AutoConfigureBefore(FeignAutoConfiguration.class) -@AutoConfigureAfter({SleuthHystrixAutoConfiguration.class, TraceWebAutoConfiguration.class}) +@AutoConfigureAfter({SleuthHystrixAutoConfiguration.class, TraceWebServletAutoConfiguration.class}) public class TraceFeignClientAutoConfiguration { @Bean diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java index f0b47c4a9..bbb508e14 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java @@ -32,7 +32,7 @@ import org.springframework.cloud.sleuth.instrument.web.HttpSpanInjector; 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.instrument.web.TraceWebAutoConfiguration; +import org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -50,7 +50,7 @@ import okhttp3.Request; @ConditionalOnWebApplication @ConditionalOnClass(ZuulFilter.class) @ConditionalOnBean(Tracer.class) -@AutoConfigureAfter(TraceWebAutoConfiguration.class) +@AutoConfigureAfter(TraceWebServletAutoConfiguration.class) public class TraceZuulAutoConfiguration { @Bean diff --git a/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories b/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories index 1c93d4d72..16dd38a41 100644 --- a/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories @@ -9,9 +9,12 @@ org.springframework.cloud.sleuth.instrument.messaging.websocket.TraceWebSocketAu org.springframework.cloud.sleuth.instrument.async.AsyncCustomAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.async.AsyncDefaultAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.hystrix.SleuthHystrixAutoConfiguration,\ +org.springframework.cloud.sleuth.instrument.reactor.TraceReactorAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.scheduling.TraceSchedulingAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.web.TraceHttpAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration,\ +org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration,\ +org.springframework.cloud.sleuth.instrument.web.TraceWebFluxAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.web.client.TraceWebClientAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.web.client.TraceWebAsyncClientAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.web.client.feign.TraceFeignClientAutoConfiguration,\ diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java index 96583fef0..0220ea822 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java @@ -272,7 +272,7 @@ public class ListOfSpansAssert extends AbstractAssert exchange = context.getBean(WebClient.class).get().uri("http://localhost:" + port + "/api/c2/10").exchange(); + + Awaitility.await().untilAsserted(() -> { + ClientResponse response = exchange.block(); + SleuthAssertions.then(response.statusCode().value()).isEqualTo(200); + SleuthAssertions.then(ExceptionUtils.getLastException()).isNull(); + SleuthAssertions.then(new ListOfSpans(accumulator.getSpans())) + .hasASpanWithLogEqualTo(Span.CLIENT_SEND) + .hasASpanWithLogEqualTo(Span.SERVER_RECV) + .hasASpanWithLogEqualTo(Span.SERVER_SEND) + .hasASpanWithLogEqualTo(Span.CLIENT_RECV); + }); + } finally { + context.getBean(Tracer.class).close(span); + } + + } + + @Configuration + @EnableAutoConfiguration + static class Config { + + @Bean WebClient webClient() { + return WebClient.create(); + } + + @Bean Sampler sampler() { + return new AlwaysSampler(); + } + + @Bean SpanReporter spanReporter() { + return new ArrayListSpanAccumulator(); + } + + @Bean + Controller2 controller2() { + return new Controller2(); + } + } + + @RestController + @RequestMapping("/api/c2") + static class Controller2 { + @GetMapping("/{id}") + public Flux successful(@PathVariable Long id) { + return Flux.just(id.toString()); + } + } +} + diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java index 4ddd3faa1..53423da71 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java @@ -125,7 +125,7 @@ public class WebClientTests { // 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"); - then(new ListOfSpans(spans)).hasRpcTagsInProperOrder(); + then(new ListOfSpans(spans)).hasRpcLogsInProperOrder(); }); } diff --git a/spring-cloud-sleuth-core/src/test/resources/logback.xml b/spring-cloud-sleuth-core/src/test/resources/logback.xml index 4d584ab3f..2e4d4f0fd 100644 --- a/spring-cloud-sleuth-core/src/test/resources/logback.xml +++ b/spring-cloud-sleuth-core/src/test/resources/logback.xml @@ -6,6 +6,7 @@ + diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index e512e893f..d12c5ef97 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -24,11 +24,6 @@ spring-cloud-sleuth-core ${project.version} - - org.springframework.cloud - spring-cloud-sleuth-reactor - ${project.version} - org.springframework.cloud spring-cloud-sleuth-zipkin diff --git a/spring-cloud-sleuth-reactor/.jdk8 b/spring-cloud-sleuth-reactor/.jdk8 deleted file mode 100644 index e69de29bb..000000000 diff --git a/spring-cloud-sleuth-reactor/pom.xml b/spring-cloud-sleuth-reactor/pom.xml deleted file mode 100644 index 987b4ae85..000000000 --- a/spring-cloud-sleuth-reactor/pom.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - 4.0.0 - - spring-cloud-sleuth-reactor - jar - Spring Cloud Sleuth Reactor - Spring Cloud Sleuth Reactor - - - org.springframework.cloud - spring-cloud-sleuth - 2.0.0.BUILD-SNAPSHOT - .. - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - default-compile - - true - true - - 1.8 - 1.8 - - - - - default-testCompile - - true - true - - 1.8 - 1.8 - - - - - - - - - - - org.springframework.cloud - spring-cloud-sleuth-core - - - org.springframework.boot - spring-boot-starter - - - io.projectreactor - reactor-core - true - - - org.reactivestreams - reactive-streams - true - - - org.aspectj - aspectjweaver - test - - - org.assertj - assertj-core - test - - - org.awaitility - awaitility - test - - - org.springframework.boot - spring-boot-starter-test - test - - - - diff --git a/spring-cloud-sleuth-reactor/src/main/resources/META-INF/spring.factories b/spring-cloud-sleuth-reactor/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 3938ffd61..000000000 --- a/spring-cloud-sleuth-reactor/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,3 +0,0 @@ -# Auto Configuration -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -org.springframework.cloud.sleuth.instrument.reactor.TraceReactorAutoConfiguration diff --git a/spring-cloud-sleuth-reactor/src/test/resources/application.properties b/spring-cloud-sleuth-reactor/src/test/resources/application.properties deleted file mode 100644 index 8edfd35f9..000000000 --- a/spring-cloud-sleuth-reactor/src/test/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -logging.level.org.springframework.cloud.sleuth.instrument.reactor=TRACE \ No newline at end of file diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml index 29295c7de..524667170 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml @@ -61,6 +61,7 @@ org.codehaus.mojo animal-sniffer-maven-plugin + 1.14 true