Added intial webclient & webflux support

fixes #635
This commit is contained in:
Marcin Grzejszczak
2017-07-11 12:34:27 +02:00
parent 6ef4a27d42
commit 49eac36d26
31 changed files with 928 additions and 249 deletions

31
pom.xml
View File

@@ -30,7 +30,6 @@
<module>spring-cloud-sleuth-core</module>
<module>spring-cloud-sleuth-zipkin</module>
<module>spring-cloud-sleuth-stream</module>
<module>spring-cloud-sleuth-reactor</module>
<module>spring-cloud-sleuth-zipkin-stream</module>
<module>spring-cloud-starter-sleuth</module>
<module>spring-cloud-starter-zipkin</module>
@@ -172,6 +171,21 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- For Reactor -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>${reactor.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spock -->
<dependency>
<groupId>org.spockframework</groupId>
@@ -201,16 +215,6 @@
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>${reactor.version}</version>
</dependency>
<dependency>
<groupId>org.reactivestreams</groupId>
<artifactId>reactive-streams</artifactId>
<version>${reactive-streams.version}</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
@@ -254,8 +258,9 @@
<spring-cloud-commons.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-commons.version>
<spring-cloud-stream.version>Elmhurst.BUILD-SNAPSHOT</spring-cloud-stream.version>
<spring-cloud-netflix.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-netflix.version>
<reactor.version>3.1.0.BUILD-SNAPSHOT</reactor.version>
<reactive-streams.version>1.0.0</reactive-streams.version>
<reactor.version>Bismuth-BUILD-SNAPSHOT</reactor.version>
<!-- To make Reactor work -->
<spring-boot.version>2.0.0.BUILD-SNAPSHOT</spring-boot.version>
</properties>
<profiles>

View File

@@ -21,6 +21,21 @@
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.reactivestreams</groupId>
<artifactId>reactive-streams</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>

View File

@@ -18,7 +18,8 @@ import reactor.util.context.Context;
* @author Marcin Grzejszczak
* @since 1.3.0
*/
class SpanSubscriber extends AtomicBoolean implements Subscription, CoreSubscriber<Object> {
class SpanSubscriber extends AtomicBoolean
implements Subscription, CoreSubscriber<Object> {
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() {

View File

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

View File

@@ -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<String, String> additionalHeaders = new HashMap<>();
ServerHttpRequestTextMap(ServerHttpRequest delegate) {
this.delegate = delegate;
this.additionalHeaders.put(ZipkinHttpSpanExtractor.URI_HEADER,
delegate.getPath().pathWithinApplication().value());
}
@Override
public Iterator<Map.Entry<String, String>> iterator() {
Map<String, String> map = new HashMap<>();
for (Map.Entry<String, List<String>> 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);
}
}

View File

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

View File

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

View File

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

View File

@@ -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<Void> 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<Span> 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<Span> 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<String> 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<String> 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<Span> getSpanFromAttribute(ServerWebExchange exchange) {
Optional<Span> 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;
}
}

View File

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

View File

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

View File

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

View File

@@ -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<RestTemplate> restTemplates;
@Autowired(required = false)
private Collection<RestTemplate> restTemplates;
@Autowired
private TraceRestTemplateInterceptor traceRestTemplateInterceptor;
@Autowired
private TraceRestTemplateInterceptor traceRestTemplateInterceptor;
@PostConstruct
public void init() {
if (this.restTemplates != null) {
for (RestTemplate restTemplate : this.restTemplates) {
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>(
restTemplate.getInterceptors());
interceptors.add(this.traceRestTemplateInterceptor);
restTemplate.setInterceptors(interceptors);
@PostConstruct
public void init() {
if (this.restTemplates != null) {
for (RestTemplate restTemplate : this.restTemplates) {
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>(
restTemplate.getInterceptors());
interceptors.add(this.traceRestTemplateInterceptor);
restTemplate.setInterceptors(interceptors);
}
}
}
}
}
@ConditionalOnClass(WebClient.class)
static class WebClientConfig {
@Bean
TraceWebClientBeanPostProcessor traceWebClientBeanPostProcessor(BeanFactory beanFactory) {
return new TraceWebClientBeanPostProcessor(beanFactory);
}
}
}

View File

@@ -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<ClientResponse> 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<ClientResponse> 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<Map.Entry<String, String>> iterator() {
final Iterator<Map.Entry<String, List<String>>> iterator = this.readDelegate.headers()
.entrySet().iterator();
return new Iterator<Map.Entry<String, String>>() {
@Override public boolean hasNext() {
return iterator.hasNext();
}
@Override public Map.Entry<String, String> next() {
Map.Entry<String, List<String>> next = iterator.next();
List<String> 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);
}
}

View File

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

View File

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

View File

@@ -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,\

View File

@@ -272,7 +272,7 @@ public class ListOfSpansAssert extends AbstractAssert<ListOfSpansAssert, ListOfS
return this;
}
public ListOfSpansAssert hasRpcTagsInProperOrder() {
public ListOfSpansAssert hasRpcLogsInProperOrder() {
isNotNull();
printSpans();
RpcLogKeeper rpcLogKeeper = findRpcLogs();

View File

@@ -5,6 +5,7 @@ import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.awaitility.Awaitility;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -21,6 +22,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit4.SpringRunner;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Hooks;
import reactor.core.scheduler.Schedulers;
import static org.assertj.core.api.BDDAssertions.then;
@@ -56,7 +58,7 @@ public class SpanSubscriberTests {
.subscribe(System.out::println);
then(this.tracer.getCurrentSpan()).isNull();
then(spanInOperation.get().getParents().get(0)).isEqualTo(span.getSpanId());
then(spanInOperation.get().getTraceId()).isEqualTo(span.getTraceId());
then(ExceptionUtils.getLastException()).isNull();
}
@@ -105,10 +107,16 @@ public class SpanSubscriberTests {
then(this.tracer.getCurrentSpan()).isEqualTo(foo2);
then(ExceptionUtils.getLastException()).isNull();
// parent cause there's an async span in the meantime
then(spanInOperation.get().getSavedSpan().getParents().get(0)).isEqualTo(foo2.getSpanId());
then(spanInOperation.get().getTraceId()).isEqualTo(foo2.getTraceId());
tracer.close(foo2);
}
@AfterClass
public static void cleanup() {
Hooks.resetOnNewSubscriber();
Schedulers.resetFactory();
}
@EnableAutoConfiguration
@Configuration
static class Config {

View File

@@ -16,11 +16,10 @@
package org.springframework.cloud.sleuth.instrument.web;
import java.util.regex.Pattern;
import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties;
import org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration.SkipPatternProviderConfig;
import java.util.regex.Pattern;
import static org.assertj.core.api.BDDAssertions.then;
@@ -33,7 +32,7 @@ public class SkipPatternProviderConfigTest {
public void should_combine_skip_pattern_and_management_context_when_they_are_both_not_empty() throws Exception {
SleuthWebProperties sleuthWebProperties = new SleuthWebProperties();
sleuthWebProperties.setSkipPattern("foo.*|bar.*");
Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties(
Pattern pattern = TraceWebAutoConfiguration.SkipPatternProviderConfig.getPatternForManagementServerProperties(
managementServerPropertiesWithContextPath(), sleuthWebProperties);
then(pattern.pattern()).isEqualTo("foo.*|bar.*|/management/context.*");
@@ -44,7 +43,7 @@ public class SkipPatternProviderConfigTest {
SleuthWebProperties sleuthWebProperties = new SleuthWebProperties();
sleuthWebProperties.setSkipPattern("foo.*|bar.*");
Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties(new ManagementServerProperties(), sleuthWebProperties);
Pattern pattern = TraceWebAutoConfiguration.SkipPatternProviderConfig.getPatternForManagementServerProperties(new ManagementServerProperties(), sleuthWebProperties);
then(pattern.pattern()).isEqualTo("foo.*|bar.*|/application.*");
}
@@ -54,7 +53,7 @@ public class SkipPatternProviderConfigTest {
SleuthWebProperties sleuthWebProperties = new SleuthWebProperties();
sleuthWebProperties.setSkipPattern("");
Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties(
Pattern pattern = TraceWebAutoConfiguration.SkipPatternProviderConfig.getPatternForManagementServerProperties(
managementServerPropertiesWithContextPath(), sleuthWebProperties);
then(pattern.pattern()).isEqualTo("/management/context.*");
@@ -65,7 +64,7 @@ public class SkipPatternProviderConfigTest {
SleuthWebProperties sleuthWebProperties = new SleuthWebProperties();
sleuthWebProperties.setSkipPattern("");
Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties(
Pattern pattern = TraceWebAutoConfiguration.SkipPatternProviderConfig.getPatternForManagementServerProperties(
new ManagementServerProperties() {
@Override
public String getContextPath() {

View File

@@ -47,7 +47,6 @@ import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.net.URI;
import java.util.Collection;
import java.util.stream.Stream;
import static org.assertj.core.api.BDDAssertions.then;
@@ -88,7 +87,7 @@ public class SpringDataInstrumentationTests {
});
then(this.tracer.getCurrentSpan()).isNull();
then(ExceptionUtils.getLastException()).isNull();
then(new ListOfSpans(this.arrayListSpanAccumulator.getSpans())).hasRpcTagsInProperOrder();
then(new ListOfSpans(this.arrayListSpanAccumulator.getSpans())).hasRpcLogsInProperOrder();
}
long namesCount() {

View File

@@ -77,7 +77,7 @@ public class TraceFilterWebIntegrationTests {
then(new ListOfSpans(this.accumulator.getSpans()))
.hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME,
"Request processing failed; nested exception is java.lang.RuntimeException: Throwing exception")
.hasRpcTagsInProperOrder();
.hasRpcLogsInProperOrder();
}
private int port() {

View File

@@ -0,0 +1,104 @@
package org.springframework.cloud.sleuth.instrument.web;
import org.awaitility.Awaitility;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
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.assertions.ListOfSpans;
import org.springframework.cloud.sleuth.assertions.SleuthAssertions;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator;
import org.springframework.cloud.sleuth.util.ExceptionUtils;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Hooks;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import rx.plugins.RxJavaHooks;
public class TraceWebFluxTests {
@BeforeClass
public static void setup() {
RxJavaHooks.reset();
RxJavaHooks.clear();
Hooks.resetOnNewSubscriber();
Schedulers.resetFactory();
}
@Test public void should_instrument_web_filter() throws Exception {
ConfigurableApplicationContext context = new SpringApplicationBuilder(TraceWebFluxTests.Config.class)
.web(WebApplicationType.REACTIVE).properties("server.port=0", "spring.jmx.enabled=false",
"spring.application.name=TraceWebFluxTests").run();
ExceptionUtils.setFail(true);
Span span = null;
try {
span = context.getBean(Tracer.class).createSpan("foo");
int port = context.getBean(Environment.class).getProperty("local.server.port", Integer.class);
ArrayListSpanAccumulator accumulator = context.getBean(ArrayListSpanAccumulator.class);
Mono<ClientResponse> 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<String> successful(@PathVariable Long id) {
return Flux.just(id.toString());
}
}
}

View File

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

View File

@@ -6,6 +6,7 @@
<logger name="org.springframework.cloud.sleuth.log" level="DEBUG"/>
<logger name="org.springframework.cloud.sleuth.trace" level="DEBUG"/>
<logger name="org.springframework.cloud.sleuth.instrument.rxjava" level="DEBUG"/>
<logger name="org.springframework.cloud.sleuth.instrument.reactor" level="TRACE"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>

View File

@@ -24,11 +24,6 @@
<artifactId>spring-cloud-sleuth-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-reactor</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>

View File

@@ -1,96 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-sleuth-reactor</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Sleuth Reactor</name>
<description>Spring Cloud Sleuth Reactor</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
<executions>
<execution>
<id>default-compile</id>
<configuration>
<showDeprecation>true</showDeprecation>
<showWarnings>true</showWarnings>
<compilerArguments>
<source>1.8</source>
<target>1.8</target>
</compilerArguments>
</configuration>
</execution>
<execution>
<id>default-testCompile</id>
<configuration>
<showDeprecation>true</showDeprecation>
<showWarnings>true</showWarnings>
<compilerArguments>
<source>1.8</source>
<target>1.8</target>
</compilerArguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.reactivestreams</groupId>
<artifactId>reactive-streams</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,3 +0,0 @@
# Auto Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.sleuth.instrument.reactor.TraceReactorAutoConfiguration

View File

@@ -1 +0,0 @@
logging.level.org.springframework.cloud.sleuth.instrument.reactor=TRACE

View File

@@ -61,6 +61,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
<version>1.14</version>
<configuration>
<skip>true</skip>
<signature>