diff --git a/pom.xml b/pom.xml index 92a23bf27..149b2d1b9 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,6 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - org.springframework.cloud spring-cloud-sleuth 1.0.0.BUILD-SNAPSHOT pom @@ -27,31 +26,62 @@ + spring-cloud-sleuth-core + spring-cloud-sleuth-correlation spring-cloud-sleuth-zipkin spring-cloud-sleuth-sample docs - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - 1.7 - 1.7 - - - + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.7 + 1.7 + + + + org.codehaus.gmavenplus + gmavenplus-plugin + 1.4 + + + + testCompile + + + + + + maven-surefire-plugin + 2.6 + + false + + **/*Spec.java + + + + + + + org.springframework.cloud + spring-cloud-sleuth-core + ${project.version} + org.springframework.cloud spring-cloud-sleuth-zipkin - 1.0.0.BUILD-SNAPSHOT + ${project.version} com.github.kristofa @@ -99,6 +129,16 @@ guava 18.0 + + commons-lang + commons-lang + 2.5 + + + io.reactivex + rxjava + 1.0.10 + org.springframework.cloud spring-cloud-starter-zuul @@ -111,12 +151,66 @@ 1.12.6 provided + + com.netflix.hystrix + hystrix-core + 1.4.5 + + + org.aspectj + aspectjrt + ${aspectj.version} + + + org.aspectj + aspectjweaver + ${aspectj.version} + + + + org.spockframework + spock-core + ${spock.version} + test + + + org.spockframework + spock-spring + ${spock.version} + test + + + cglib + cglib-nodep + 3.1 + test + + + org.objenesis + objenesis + 2.1 + test + + + org.hamcrest + hamcrest-core + 1.3 + test + + + org.codehaus.groovy + groovy-all + 2.4.3 + test + 2.4.2 1.0.1.BUILD-SNAPSHOT + 1.8.4 + 1.0-groovy-2.4 diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml new file mode 100644 index 000000000..edcea1b3f --- /dev/null +++ b/spring-cloud-sleuth-core/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + spring-cloud-sleuth-core + jar + Spring Cloud Sleuth Core + Spring Cloud Sleuth Core + + + org.springframework.cloud + spring-cloud-sleuth + 1.0.0.BUILD-SNAPSHOT + .. + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.codehaus.gmavenplus + gmavenplus-plugin + + + maven-surefire-plugin + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + true + + + org.projectlombok + lombok + + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + + diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/DefaultRestTemplateConfigurer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/DefaultRestTemplateConfigurer.java new file mode 100644 index 000000000..f7e33be80 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/DefaultRestTemplateConfigurer.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-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.resttemplate; + +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.web.client.RestTemplate; + +import java.util.List; + +/** + * Default configure of {@code RestTemplate} that adds a list {@code ClientHttpRequestInterceptor} + * to {@code RestTemplate} + * + * @see ClientHttpRequestInterceptor + * @see RestTemplate + * + * @author Marcin Grzejszczak, 4financeIT + */ +public class DefaultRestTemplateConfigurer implements RestTemplateConfigurer { + + private final List clientHttpRequestInterceptors; + + public DefaultRestTemplateConfigurer(List clientHttpRequestInterceptors) { + this.clientHttpRequestInterceptors = clientHttpRequestInterceptors; + } + + @Override + public void modifyRestTemplate(RestTemplate restTemplate) { + for (ClientHttpRequestInterceptor clientHttpRequestInterceptor : clientHttpRequestInterceptors) { + restTemplate.getInterceptors().add(clientHttpRequestInterceptor); + } + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/RestTemplateConfigurer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/RestTemplateConfigurer.java new file mode 100644 index 000000000..54b643d40 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/RestTemplateConfigurer.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012-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.resttemplate; + +import org.springframework.web.client.RestTemplate; + +/** + * Interface that allows to modify the {@code RestTemplate} parameters + * + * @see RestTemplate + * + * @author Marcin Grzejszczak, 4financeIT + */ +public interface RestTemplateConfigurer { + + void modifyRestTemplate(RestTemplate restTemplate); +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/SleuthRestTemplateAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/SleuthRestTemplateAutoConfiguration.java new file mode 100644 index 000000000..662176624 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/resttemplate/SleuthRestTemplateAutoConfiguration.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-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.resttemplate; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +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 java.util.ArrayList; +import java.util.List; + +/** + * + * Autoconfiguration that sets up a {@code RestTemplate} as a bean if one is not present + * and performs its additional modification via a {@code RestTemplateConfigurer}. + * + * @author Marcin Grzejszczak, 4financeIT + */ +@Configuration +@ConditionalOnWebApplication +@ConditionalOnProperty(value = "spring.cloud.sleuth.resttemplate.enabled", matchIfMissing = true) +public class SleuthRestTemplateAutoConfiguration { + + @Configuration + protected static class RestTemplateConfig { + + @Autowired + private List clientHttpRequestInterceptors = new ArrayList<>(); + + @Bean + @ConditionalOnMissingBean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + @Bean + @ConditionalOnMissingBean + public RestTemplateConfigurer restTemplateConfigurer(RestTemplate restTemplate) { + DefaultRestTemplateConfigurer configurer = new DefaultRestTemplateConfigurer(clientHttpRequestInterceptors); + configurer.modifyRestTemplate(restTemplate); + return configurer; + } + } + +} 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 new file mode 100644 index 000000000..9d7ec500c --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +# Auto Configuration +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.cloud.sleuth.resttemplate.SleuthRestTemplateAutoConfiguration diff --git a/spring-cloud-sleuth-correlation/pom.xml b/spring-cloud-sleuth-correlation/pom.xml new file mode 100644 index 000000000..697ebf14a --- /dev/null +++ b/spring-cloud-sleuth-correlation/pom.xml @@ -0,0 +1,132 @@ + + + 4.0.0 + + spring-cloud-sleuth-correlation + jar + Spring Cloud Sleuth Correlation + Spring Cloud Sleuth Correlation + + + org.springframework.cloud + spring-cloud-sleuth + 1.0.0.BUILD-SNAPSHOT + .. + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.codehaus.gmavenplus + gmavenplus-plugin + + + maven-surefire-plugin + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-sleuth-core + + + org.springframework.boot + spring-boot-starter-actuator + true + + + commons-lang + commons-lang + + + org.projectlombok + lombok + + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + com.netflix.hystrix + hystrix-core + + + org.aspectj + aspectjrt + + + org.aspectj + aspectjweaver + runtime + + + io.reactivex + rxjava + + + + org.spockframework + spock-core + test + + + org.spockframework + spock-spring + test + + + cglib + cglib-nodep + test + + + org.objenesis + objenesis + test + + + org.hamcrest + hamcrest-core + test + + + org.codehaus.groovy + groovy-all + test + + + org.codehaus.gpars + gpars + 1.2.1 + test + + + com.github.tomakehurst + wiremock + 1.53 + test + + + com.github.stefanbirkner + system-rules + 1.9.0 + test + + + + diff --git a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdAspect.java b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdAspect.java new file mode 100644 index 000000000..b73665aba --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdAspect.java @@ -0,0 +1,134 @@ +/* + * Copyright 2012-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.correlation; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestOperations; + +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; + +import static org.springframework.cloud.sleuth.correlation.CorrelationIdHolder.CORRELATION_ID_HEADER; + +/** + * Aspect that adds correlation id to + *

+ *

    + *
  • {@link RestController} annotated classes + * with public {@link Callable} methods
  • + *
  • {@link Controller} annotated classes + * with public {@link Callable} methods
  • + *
  • explicit {@link RestOperations}.exchange(..) method calls
  • + *
+ *

+ * For controllers an around aspect is created that wraps the {@link Callable#call()} method execution + * in {@link CorrelationIdUpdater#wrapCallableWithId(Callable)} + *

+ * For {@link RestOperations} we are wrapping all executions of the + * exchange methods and we are extracting {@link HttpHeaders} from the passed {@link HttpEntity}. + * Next we are adding correlation id header {@link CorrelationIdHolder#CORRELATION_ID_HEADER} with + * the value taken from {@link CorrelationIdHolder}. Finally the method execution proceeds. + * + * @see RestController + * @see Controller + * @see RestOperations + * @see CorrelationIdHolder + * @see CorrelationIdFilter + * + * @author Tomasz Nurkewicz, 4financeIT + * @author Marcin Grzejszczak, 4financeIT + * @author Michal Chmielarz, 4financeIT + */ +@Aspect +public class CorrelationIdAspect { + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private static final int HTTP_ENTITY_PARAM_INDEX = 2; + + @Pointcut("@target(org.springframework.web.bind.annotation.RestController)") + private void anyRestControllerAnnotated() { + } + + @Pointcut("@target(org.springframework.stereotype.Controller)") + private void anyControllerAnnotated() { + } + + @Pointcut("execution(public java.util.concurrent.Callable *(..))") + private void anyPublicMethodReturningCallable() { + } + + @Pointcut("(anyRestControllerAnnotated() || anyControllerAnnotated()) && anyPublicMethodReturningCallable()") + private void anyControllerOrRestControllerWithPublicAsyncMethod() { + } + + @Around("anyControllerOrRestControllerWithPublicAsyncMethod()") + public Object wrapWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable { + final Callable callable = (Callable) pjp.proceed(); + log.debug("Wrapping callable with correlation id [" + CorrelationIdHolder.get() + "]"); + return CorrelationIdUpdater.wrapCallableWithId(new Callable() { + @Override + public Object call() throws Exception { + return callable.call(); + } + }); + } + + @Pointcut("execution(public * org.springframework.web.client.RestOperations.exchange(..))") + private void anyExchangeRestOperationsMethod() { + } + + @Around("anyExchangeRestOperationsMethod()") + public Object wrapWithCorrelationIdForRestOperations(ProceedingJoinPoint pjp) throws Throwable { + String correlationId = CorrelationIdHolder.get(); + log.debug("Wrapping RestTemplate call with correlation id [" + correlationId + "]"); + HttpEntity httpEntity = (HttpEntity) pjp.getArgs()[HTTP_ENTITY_PARAM_INDEX]; + HttpEntity newHttpEntity = createNewHttpEntity(httpEntity, correlationId); + List newArgs = modifyHttpEntityInMethodArguments(pjp, newHttpEntity); + return pjp.proceed(newArgs.toArray()); + } + + @SuppressWarnings("unchecked") + private HttpEntity createNewHttpEntity(HttpEntity httpEntity, String correlationId) { + HttpHeaders newHttpHeaders = new HttpHeaders(); + newHttpHeaders.putAll(httpEntity.getHeaders()); + newHttpHeaders.add(CORRELATION_ID_HEADER, correlationId); + return new HttpEntity(httpEntity.getBody(), newHttpHeaders); + } + + private List modifyHttpEntityInMethodArguments(ProceedingJoinPoint pjp, HttpEntity newHttpEntity) { + List newArgs = new ArrayList<>(); + for (int i = 0; i < pjp.getArgs().length; i++) { + Object arg = pjp.getArgs()[i]; + if (i != HTTP_ENTITY_PARAM_INDEX) { + newArgs.add(i, arg); + } else { + newArgs.add(i, newHttpEntity); + } + } + return newArgs; + } +} diff --git a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdAutoConfiguration.java b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdAutoConfiguration.java new file mode 100644 index 000000000..51e71dfac --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdAutoConfiguration.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-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.correlation; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.embedded.FilterRegistrationBean; +import org.springframework.cloud.sleuth.resttemplate.SleuthRestTemplateAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.regex.Pattern; + +/** + * Registers beans that add correlation id to requests + * + * @see CorrelationIdAspect + * @see CorrelationIdFilter + * + * @author Tomasz Nurkewicz, 4financeIT + * @author Marcin Grzejszczak, 4financeIT + * @author Michal Chmielarz, 4financeIT + */ +@Configuration +@ConditionalOnProperty(value = "spring.cloud.sleuth.correlation.enabled", matchIfMissing = true) +@AutoConfigureAfter(SleuthRestTemplateAutoConfiguration.class) +public class CorrelationIdAutoConfiguration { + + /** + * Pattern for URLs that should be skipped in correlationID setting + */ + @Value("${spring.cloud.sleuth.correlation.skipPattern:}") + private String skipPattern; + + @Bean + @ConditionalOnMissingBean + public CorrelationIdAspect correlationIdAspect() { + return new CorrelationIdAspect(); + } + + @Bean + @ConditionalOnMissingBean + public FilterRegistrationBean correlationHeaderFilter(UuidGenerator uuidGenerator) { + Pattern pattern = StringUtils.isBlank(skipPattern) ? Pattern.compile(skipPattern) : CorrelationIdFilter.DEFAULT_SKIP_PATTERN; + return new FilterRegistrationBean(new CorrelationIdFilter(uuidGenerator, pattern)); + } + + @Bean + @ConditionalOnMissingBean + public UuidGenerator uuidGenerator() { + return new UuidGenerator(); + } + + @Bean + public CorrelationIdSettingRestTemplateInterceptor correlationIdSettingRestTemplateInterceptor() { + return new CorrelationIdSettingRestTemplateInterceptor(); + } +} diff --git a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdFilter.java b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdFilter.java new file mode 100644 index 000000000..96a6aef07 --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdFilter.java @@ -0,0 +1,151 @@ +/* + * Copyright 2012-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.correlation; + +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.util.concurrent.Callable; +import java.util.regex.Pattern; + +import static org.springframework.cloud.sleuth.correlation.CorrelationIdHolder.CORRELATION_ID_HEADER; +import static org.springframework.util.StringUtils.hasText; + +/** + * Filter that takes the value of the {@link CorrelationIdHolder#CORRELATION_ID_HEADER} header + * from either request or response and sets it in the {@link CorrelationIdHolder}. It also provides + * that value in {@link MDC} logging related class so that logger prints the value of + * correlation id at each log. + * + * @see CorrelationIdHolder + * @see MDC + * + * @author Jakub Nabrdalik, 4financeIT + * @author Tomasz Nurkiewicz, 4financeIT + * @author Marcin Grzejszczak, 4financeIT + */ +public class CorrelationIdFilter extends OncePerRequestFilter { + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + public static final Pattern DEFAULT_SKIP_PATTERN = Pattern.compile("/api-docs.*|/autoconfig|/configprops|/dump|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html"); + + private final Pattern skipCorrId; + private final UuidGenerator uuidGenerator; + + public CorrelationIdFilter() { + this.uuidGenerator = new UuidGenerator(); + this.skipCorrId = null; + } + + public CorrelationIdFilter(UuidGenerator uuidGenerator, Pattern skipCorrId) { + this.uuidGenerator = uuidGenerator; + this.skipCorrId = skipCorrId; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + setupCorrelationId(request, response); + try { + filterChain.doFilter(request, response); + } finally { + cleanupCorrelationId(); + } + } + + private void setupCorrelationId(HttpServletRequest request, HttpServletResponse response) { + String correlationIdFromRequest = getCorrelationIdFrom(request); + String correlationId = (hasText(correlationIdFromRequest)) ? correlationIdFromRequest : getCorrelationIdFrom(response); + if (!hasText(correlationId) && shouldGenerateCorrId(request)) { + correlationId = createNewCorrIdIfEmpty(); + } + CorrelationIdHolder.set(correlationId); + addCorrelationIdToResponseIfNotPresent(response, correlationId); + } + + private String getCorrelationIdFrom(final HttpServletResponse response) { + return withLoggingAs("response", new Callable() { + @Override + public String call() throws Exception { + return response.getHeader(CORRELATION_ID_HEADER); + } + }); + } + + private String getCorrelationIdFrom(final HttpServletRequest request) { + return withLoggingAs("request", new Callable() { + @Override + public String call() throws Exception { + return request.getHeader(CORRELATION_ID_HEADER); + } + }); + } + + private String withLoggingAs(String whereWasFound, Callable correlationIdGetter) { + String correlationId = tryToGetCorrelationId(correlationIdGetter); + if (hasText(correlationId)) { + MDC.put(CORRELATION_ID_HEADER, correlationId); + log.debug("Found correlationId in " + whereWasFound + ": " + correlationId); + } + return correlationId; + } + + private String tryToGetCorrelationId(Callable correlationIdGetter) { + try { + return correlationIdGetter.call(); + } catch (Exception e) { + log.error("Exception occurred while trying to retrieve request header", e); + return StringUtils.EMPTY; + } + } + + private String createNewCorrIdIfEmpty() { + String currentCorrId = uuidGenerator.create(); + MDC.put(CORRELATION_ID_HEADER, currentCorrId); + log.debug("Generating new correlationId: " + currentCorrId); + return currentCorrId; + } + + protected boolean shouldGenerateCorrId(HttpServletRequest request) { + final String i = request.getRequestURI(); + final String uri = StringUtils.defaultIfEmpty(i, StringUtils.EMPTY); + boolean skip = skipCorrId != null && skipCorrId.matcher(uri).matches(); + return !skip; + } + + private void addCorrelationIdToResponseIfNotPresent(HttpServletResponse response, String correlationId) { + if (!hasText(response.getHeader(CORRELATION_ID_HEADER))) { + response.addHeader(CORRELATION_ID_HEADER, correlationId); + } + } + + private void cleanupCorrelationId() { + MDC.remove(CORRELATION_ID_HEADER); + CorrelationIdHolder.remove(); + } + + @Override + protected boolean shouldNotFilterAsyncDispatch() { + return false; + } +} diff --git a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdHolder.java b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdHolder.java new file mode 100644 index 000000000..6474887a5 --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdHolder.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-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.correlation; + +/** + * Component that stores correlation id using {@link ThreadLocal} + * + * @author Jakub Nabrdalik, 4financeIT + * @author Marcin Zajaczkowski, 4financeIT + */ +public class CorrelationIdHolder { + public static final String CORRELATION_ID_HEADER = "Correlation-Id"; + private static final ThreadLocal id = new ThreadLocal(); + + public static void set(String correlationId) { + id.set(correlationId); + } + + public static String get() { + return id.get(); + } + + public static void remove() { + id.remove(); + } +} diff --git a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdSettingRestTemplateInterceptor.java b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdSettingRestTemplateInterceptor.java new file mode 100644 index 000000000..3f1b6fcb8 --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdSettingRestTemplateInterceptor.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-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.correlation; + +import lombok.SneakyThrows; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; + +import java.io.IOException; + +/** + * Interceptor that verifies whether the correlation id has been + * set on the request and sets it if it's missing. + * + * @see org.springframework.web.client.RestTemplate + * @see CorrelationIdHolder + * + * @author Marcin Grzejszczak, 4financeIT + */ +public class CorrelationIdSettingRestTemplateInterceptor implements ClientHttpRequestInterceptor { + + @SneakyThrows + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { + appendCorrelationIdToRequestIfMissing(request); + ClientHttpResponse response = null; + try { + response = execution.execute(request, body); + } catch (final Exception e) { + throw new RuntimeException(e); + } + return response; + } + + private void appendCorrelationIdToRequestIfMissing(HttpRequest request) { + if (!request.getHeaders().containsKey(CorrelationIdHolder.CORRELATION_ID_HEADER)) { + request.getHeaders().add(CorrelationIdHolder.CORRELATION_ID_HEADER, CorrelationIdHolder.get()); + } + } + +} diff --git a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdUpdater.java b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdUpdater.java new file mode 100644 index 000000000..0becfca32 --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/CorrelationIdUpdater.java @@ -0,0 +1,120 @@ +/* + * Copyright 2012-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.correlation; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.springframework.util.StringUtils; + +import java.lang.invoke.MethodHandles; +import java.util.concurrent.Callable; + +import static org.springframework.cloud.sleuth.correlation.CorrelationIdHolder.CORRELATION_ID_HEADER; + +/** + * Class that takes care of updating all necessary components with new value + * of correlation id. + * It sets correlationId on {@link ThreadLocal} in {@link CorrelationIdHolder} + * and in {@link MDC}. + * + * @see CorrelationIdHolder + * @see MDC + * + * @author Jakub Nabrdalik, 4financeIT + * @author Michal Chmielarz, 4financeIT + */ +public class CorrelationIdUpdater { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + public static void updateCorrelationId(String correlationId) { + if (StringUtils.hasText(correlationId)) { + log.debug("Updating correlationId with value: [" + correlationId + "]"); + CorrelationIdHolder.set(correlationId); + MDC.put(CORRELATION_ID_HEADER, correlationId); + } + } + + /** + * Temporarily updates correlation ID inside block of code. + * Makes sure previous ID is restored after block's execution + * + * @param temporaryCorrelationId - correlationID to passed to the executed block of code + * @param block Callable to be executed with new ID + * @return the result of Callable block execution + */ + public static T withId(String temporaryCorrelationId, Callable block) { + final String oldCorrelationId = CorrelationIdHolder.get(); + try { + updateCorrelationId(temporaryCorrelationId); + return block.call(); + } catch (RuntimeException e) { + logException(e); + throw e; + } catch (Exception e) { + logException(e); + throw new RuntimeException(e); + } finally { + updateCorrelationId(oldCorrelationId); + } + + } + + private static void logException(Throwable e) { + log.error("Exception occurred while trying to execute the function", e); + } + + /** + * Wraps given {@link Callable} with another {@link Callable Callable} propagating correlation ID inside nested + * Callable/Closure. + *

+ *

+ * Useful in a situation when a Callable should be executed in a separate thread, for example in aspects. + *

+ *


+	 * @Around('...')
+	 * Object wrapWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable {
+	 *     Callable callable = pjp.proceed() as Callable
+	 *     return CorrelationIdUpdater.wrapCallableWithId {
+	 *         callable.call()
+	 *     }
+	 * }
+	 * 
+ *

+ * Note: Passing only one input parameter currently is supported. + * + * @param block code block to execute in a thread with a correlation ID taken from the original thread + * @return wrapping block as Callable + */ + @SuppressWarnings("unchecked") + public static Callable wrapCallableWithId(final Callable block) { + final String temporaryCorrelationId = CorrelationIdHolder.get(); + // unchecked assignment due to groovyc issues with + return new Callable() { + @Override + public Object call() throws Exception { + final String oldCorrelationId = CorrelationIdHolder.get(); + try { + updateCorrelationId(temporaryCorrelationId); + return block.call(); + } finally { + updateCorrelationId(oldCorrelationId); + } + } + }; + } +} diff --git a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/UuidGenerator.java b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/UuidGenerator.java new file mode 100644 index 000000000..e130097a8 --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/UuidGenerator.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-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.correlation; + +import java.util.UUID; + +/** + * Default Uuid generator + * + * @author extends HystrixCommand { + private final String clientCorrelationId = CorrelationIdHolder.get(); + + protected CorrelatedCommand(HystrixCommandGroupKey group) { + super(group); + } + + protected CorrelatedCommand(Setter setter) { + super(setter); + } + + @Override + protected final R run() throws Exception { + return CorrelationIdUpdater.withId(clientCorrelationId, new Callable() { + @Override + public R call() throws Exception { + return doRun(); + } + + }); + } + + public abstract R doRun() throws Exception; +} diff --git a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/scheduling/ScheduledTaskWithCorrelationIdAspect.java b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/scheduling/ScheduledTaskWithCorrelationIdAspect.java new file mode 100644 index 000000000..6af07d5a7 --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/scheduling/ScheduledTaskWithCorrelationIdAspect.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-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.correlation.scheduling; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.sleuth.correlation.CorrelationIdUpdater; +import org.springframework.cloud.sleuth.correlation.UuidGenerator; +import org.springframework.scheduling.annotation.Scheduled; + +import java.lang.invoke.MethodHandles; +import java.util.concurrent.Callable; + +/** + * Aspect that sets correlationId for running threads executing methods annotated with {@link Scheduled} annotation. + * For every execution of scheduled method a new, i.e. unique one, value of correlationId will be set. + * + * @author Tomasz Nurkewicz, 4financeIT + * @author Michal Chmielarz, 4financeIT + * @author Marcin Grzejszczak, 4financeIT + * + * @see UuidGenerator + * @see CorrelationIdUpdater + */ +@Aspect +public class ScheduledTaskWithCorrelationIdAspect { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private final UuidGenerator uuidGenerator; + + public ScheduledTaskWithCorrelationIdAspect(UuidGenerator uuidGenerator) { + this.uuidGenerator = uuidGenerator; + } + + @Around("execution (@org.springframework.scheduling.annotation.Scheduled * *.*(..))") + public Object setNewCorrelationIdOnThread(final ProceedingJoinPoint pjp) throws Throwable { + String correlationId = uuidGenerator.create(); + return CorrelationIdUpdater.withId(correlationId, new Callable() { + @Override + public Object call() throws Exception { + try { + return pjp.proceed(); + } catch (Throwable throwable) { + log.error("Didn't manage to proceed with the pointcut", throwable); + throw new RuntimeException(throwable); + } + } + }); + } +} diff --git a/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/scheduling/TaskSchedulingConfiguration.java b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/scheduling/TaskSchedulingConfiguration.java new file mode 100644 index 000000000..e0ace7fbe --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/main/java/org/springframework/cloud/sleuth/correlation/scheduling/TaskSchedulingConfiguration.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-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.correlation.scheduling; + +import org.springframework.cloud.sleuth.correlation.UuidGenerator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * Registers beans related to task scheduling. + * + * @see ScheduledTaskWithCorrelationIdAspect + * + * @author Michal Chmielarz, 4financeIT + */ +@Configuration +@EnableScheduling +@EnableAspectJAutoProxy +public class TaskSchedulingConfiguration { + @Bean + public ScheduledTaskWithCorrelationIdAspect scheduledTaskPointcut(UuidGenerator uuidGenerator) { + return new ScheduledTaskWithCorrelationIdAspect(uuidGenerator); + } + +} diff --git a/spring-cloud-sleuth-correlation/src/main/resources/META-INF/spring.factories b/spring-cloud-sleuth-correlation/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..387f34393 --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +# Auto Configuration +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.cloud.sleuth.correlation.CorrelationIdAutoConfiguration diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdAspectISpec.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdAspectISpec.groovy new file mode 100644 index 000000000..a5a368a2f --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdAspectISpec.groovy @@ -0,0 +1,118 @@ +package org.springframework.cloud.sleuth.correlation +import groovy.transform.CompileStatic +import groovy.transform.PackageScope +import groovy.transform.TypeChecked +import org.hamcrest.Description +import org.hamcrest.TypeSafeMatcher +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.test.SpringApplicationContextLoader +import org.springframework.cloud.sleuth.correlation.base.HttpMockServer +import org.springframework.cloud.sleuth.correlation.base.MvcCorrelationIdSettingIntegrationSpec +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.EnableAspectJAutoProxy +import org.springframework.http.MediaType +import org.springframework.scheduling.annotation.EnableAsync +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.web.servlet.MvcResult +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders +import org.springframework.test.web.servlet.result.MockMvcResultHandlers +import org.springframework.test.web.servlet.result.MockMvcResultMatchers +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestMethod +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.client.RestTemplate + +import java.util.concurrent.Callable +import java.util.concurrent.TimeUnit + +import static com.github.tomakehurst.wiremock.client.WireMock.* +import static org.springframework.cloud.sleuth.correlation.CorrelationIdHolder.CORRELATION_ID_HEADER + +@ContextConfiguration(classes = [CorrelationIdAspectSpecConfiguration], loader = SpringApplicationContextLoader) +class CorrelationIdAspectISpec extends MvcCorrelationIdSettingIntegrationSpec { + + public static final String CORRELATION_ID_PATTERN = /^(?!\s*$).+/ + public static final TypeSafeMatcher hasCorrelationIdSet = new TypeSafeMatcher() { + @Override + protected boolean matchesSafely(String item) { + return item.matches(CORRELATION_ID_PATTERN) + } + + @Override + void describeTo(Description description) { + + } + } + + def "should set correlationId on header via aspect in synchronous call"() { + given: + stubInteraction(get(urlMatching('.*')), aResponse().withStatus(200)) + when: + mockMvc.perform(MockMvcRequestBuilders.get('/syncPing').accept(MediaType.TEXT_PLAIN)) + .andExpect(MockMvcResultMatchers.header().string(CORRELATION_ID_HEADER, hasCorrelationIdSet)) + then: + wireMock.verifyThat(getRequestedFor(urlMatching('.*')).withHeader(CORRELATION_ID_HEADER, matching(CORRELATION_ID_PATTERN))) + + } + + def "should set correlationId on header via aspect in asynchronous call"() { + given: + stubInteraction(get(urlMatching('.*')), aResponse().withStatus(200)) + when: + MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get('/asyncPing').accept(MediaType.TEXT_PLAIN)) + .andExpect(MockMvcResultMatchers.request().asyncStarted()) + .andReturn() + and: + mvcResult.getAsyncResult(TimeUnit.SECONDS.toMillis(2)) + then: + mockMvc.perform(MockMvcRequestBuilders.asyncDispatch(mvcResult)). + andDo(MockMvcResultHandlers.print()). + andExpect(MockMvcResultMatchers.status().isOk()). + andExpect(MockMvcResultMatchers.header().string(CORRELATION_ID_HEADER, hasCorrelationIdSet)) + and: + wireMock.verifyThat(getRequestedFor(urlMatching('.*')).withHeader(CORRELATION_ID_HEADER, matching(CORRELATION_ID_PATTERN))) + } + + @CompileStatic + @Configuration + @EnableAsync + @EnableAutoConfiguration + @EnableAspectJAutoProxy(proxyTargetClass = true) + static class CorrelationIdAspectSpecConfiguration { + @Bean + AspectTestingController aspectTestingController() { + return new AspectTestingController() + } + } + + @RestController + @TypeChecked + @PackageScope + static class AspectTestingController { + + @Autowired + private HttpMockServer httpMockServer + @Autowired + private RestTemplate restTemplate + + @RequestMapping(value = "/syncPing", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) + String syncPing() { + callWiremockAndReturnOk() + } + + @RequestMapping(value = "/asyncPing", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) + Callable asyncPing() { + return { + callWiremockAndReturnOk() + } + } + + private String callWiremockAndReturnOk() { + restTemplate.getForObject("http://localhost:${httpMockServer.port()}", String) + return "OK" + } + } + +} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdFilterISpec.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdFilterISpec.groovy new file mode 100644 index 000000000..f825632aa --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdFilterISpec.groovy @@ -0,0 +1,57 @@ +package org.springframework.cloud.sleuth.correlation + +import org.slf4j.MDC +import org.springframework.boot.test.SpringApplicationContextLoader +import org.springframework.cloud.sleuth.correlation.base.BaseConfiguration +import org.springframework.cloud.sleuth.correlation.base.MvcCorrelationIdSettingIntegrationSpec +import org.springframework.http.MediaType +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.web.servlet.MvcResult +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders + +@ContextConfiguration(classes = [BaseConfiguration, CorrelationIdAutoConfiguration], loader = SpringApplicationContextLoader) +class CorrelationIdFilterISpec extends MvcCorrelationIdSettingIntegrationSpec { + + def "should create and return correlationId in HTTP header"() { + when: + MvcResult mvcResult = sendPingWithoutCorrelationId() + + then: + getCorrelationIdFromResponseHeader(mvcResult) != null + } + + def "when correlationId is sent, should not create a new one, but return the existing one instead"() { + given: + String passedCorrelationId = "passedCorId" + + when: + MvcResult mvcResult = sendPingWithCorrelationId(passedCorrelationId) + + then: + getCorrelationIdFromResponseHeader(mvcResult) == passedCorrelationId + } + + def "should clean up MDC after the call"() { + given: + String passedCorrelationId = "passedCorId" + + when: + sendPingWithCorrelationId(passedCorrelationId) + + then: + MDC.get(CorrelationIdHolder.CORRELATION_ID_HEADER) == null + } + + private MvcResult sendPingWithCorrelationId(String passedCorrelationId) { + mockMvc.perform(MockMvcRequestBuilders.get('/ping').accept(MediaType.TEXT_PLAIN) + .header(CorrelationIdHolder.CORRELATION_ID_HEADER, passedCorrelationId)).andReturn() + } + + private MvcResult sendPingWithoutCorrelationId() { + mockMvc.perform(MockMvcRequestBuilders.get('/ping').accept(MediaType.TEXT_PLAIN)).andReturn() + } + + private String getCorrelationIdFromResponseHeader(MvcResult mvcResult) { + mvcResult.response.getHeader(CorrelationIdHolder.CORRELATION_ID_HEADER) + } +} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdFilterSkipPatternSpec.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdFilterSkipPatternSpec.groovy new file mode 100644 index 000000000..b4841a435 --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdFilterSkipPatternSpec.groovy @@ -0,0 +1,65 @@ +package org.springframework.cloud.sleuth.correlation + +import spock.lang.Specification +import spock.lang.Unroll + +import javax.servlet.FilterChain +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse + +@Unroll +class CorrelationIdFilterSkipPatternSpec extends Specification { + + CorrelationIdFilter filter = new CorrelationIdFilter(Stub(UuidGenerator), CorrelationIdFilter.DEFAULT_SKIP_PATTERN) + + def 'should skip meaningless URIs like #uri'() { + given: + HttpServletResponse responseMock = Mock(HttpServletResponse) + HttpServletRequest requestMock = Mock(HttpServletRequest) + and: + requestMock.getRequestURI() >> uri + when: + filter.doFilter(requestMock, responseMock, Stub(FilterChain)) + then: + 0 * responseMock.addHeader(CorrelationIdHolder.CORRELATION_ID_HEADER, _ as String) + where: + uri | _ + '/api-docs' | _ + '/api-docs/default' | _ + '/swagger' | _ + '/trace' | _ + '/metrics' | _ + '/metrics/foo' | _ + '/mappings' | _ + '/autoconfig' | _ + '/configprops' | _ + '/info' | _ + '/dump' | _ + '/swagger/foo' | _ + '/foo.js' | _ + '/foo/bar.png' | _ + '/foo/bar.html' | _ + '/foo/bar.css' | _ + '/foo/bar.js' | _ + '/foo/bar.js' | _ + } + + def 'should not skip #uri'() { + given: + HttpServletResponse responseMock = Mock(HttpServletResponse) + HttpServletRequest requestMock = Mock(HttpServletRequest) + and: + requestMock.getRequestURI() >> uri + when: + filter.doFilter(requestMock, responseMock, Stub(FilterChain)) + then: + 1 * responseMock.addHeader(CorrelationIdHolder.CORRELATION_ID_HEADER, _ as String) + where: + uri | _ + '/business/api-docs' | _ + '/business/swagger/foo' | _ + '/foo.js/service' | _ + } + + +} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdUpdaterSpec.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdUpdaterSpec.groovy new file mode 100644 index 000000000..b6cd61c7f --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/CorrelationIdUpdaterSpec.groovy @@ -0,0 +1,66 @@ +package org.springframework.cloud.sleuth.correlation + +import groovyx.gpars.GParsPool +import spock.lang.Specification + +import java.util.concurrent.Callable +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + +class CorrelationIdUpdaterSpec extends Specification { + + def cleanup() { + CorrelationIdHolder.remove() + } + + def "correlation ID should not be propagated to other thread by default"() { + given: + CorrelationIdUpdater.updateCorrelationId('A') + expect: + GParsPool.withPool(1) { + ["1"].eachParallel { + assert CorrelationIdHolder.get() == null + } + } + } + + def "should propagate correlation ID into nested Callable"() { + given: + ExecutorService threadPool = Executors.newFixedThreadPool(1) + CorrelationIdUpdater.updateCorrelationId('A') + Callable callable = new CorrelationIdTestCallable() + when: + Callable wrappedCallable = CorrelationIdUpdater.wrapCallableWithId(callable) + String nestedCorrelationId = threadPool.submit(wrappedCallable).get(1, TimeUnit.SECONDS) + then: + nestedCorrelationId == 'A' + cleanup: + threadPool.shutdown() + } + + def "should restore previous correlation ID after Callable execution in other thread"() { + given: + ExecutorService threadPool = Executors.newFixedThreadPool(1) + CorrelationIdUpdater.updateCorrelationId('A') + Callable callable = new CorrelationIdTestCallable() + and: + threadPool.submit({ CorrelationIdHolder.set('B') }).get(1, TimeUnit.SECONDS) + when: + threadPool.submit(CorrelationIdUpdater.wrapCallableWithId(callable)).get(1, TimeUnit.SECONDS) + then: + def restoredCorrelationId = threadPool.submit({ + CorrelationIdHolder.get() + } as Callable).get(1, TimeUnit.SECONDS) + restoredCorrelationId == 'B' + cleanup: + threadPool.shutdown() + } + + private static class CorrelationIdTestCallable implements Callable { + @Override + String call() throws Exception { + CorrelationIdHolder.get() + } + } +} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/BaseConfiguration.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/BaseConfiguration.groovy new file mode 100644 index 000000000..d3fc8744f --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/BaseConfiguration.groovy @@ -0,0 +1,17 @@ +package org.springframework.cloud.sleuth.correlation.base + +import groovy.transform.CompileStatic +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer + +@CompileStatic +@Configuration +class BaseConfiguration { + + @Bean + static PropertySourcesPlaceholderConfigurer placeholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer() + } + +} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/HttpMockServer.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/HttpMockServer.groovy new file mode 100755 index 000000000..629190b34 --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/HttpMockServer.groovy @@ -0,0 +1,31 @@ +package org.springframework.cloud.sleuth.correlation.base + +import com.github.tomakehurst.wiremock.WireMockServer +import groovy.transform.CompileStatic + +/** + * Custom implementation of {@link WireMockServer} that by default registers itself at port + * {@link HttpMockServer#DEFAULT_PORT}. + * + * @see WireMockServer + */ +@CompileStatic +class HttpMockServer extends WireMockServer { + + public static final int DEFAULT_PORT = 8030 + + HttpMockServer(int port) { + super(port) + } + + HttpMockServer() { + super(DEFAULT_PORT) + } + + void shutdownServer() { + if (isRunning()) { + stop() + } + shutdown() + } +} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MockServerConfiguration.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MockServerConfiguration.groovy new file mode 100755 index 000000000..7e9a97c61 --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MockServerConfiguration.groovy @@ -0,0 +1,25 @@ +package org.springframework.cloud.sleuth.correlation.base + +import groovy.transform.CompileStatic +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.util.SocketUtils + +/** + * Configuration that registers {@link HttpMockServer} as a Spring bean. Takes care + * of graceful shutdown process. + * + * @see HttpMockServer + */ +@CompileStatic +@Configuration +class MockServerConfiguration { + + @Bean(destroyMethod = 'shutdownServer') + HttpMockServer httpMockServer() { + HttpMockServer httpMockServer = new HttpMockServer(SocketUtils.findAvailableTcpPort()) + httpMockServer.start() + return httpMockServer + } + +} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MvcCorrelationIdSettingIntegrationSpec.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MvcCorrelationIdSettingIntegrationSpec.groovy new file mode 100644 index 000000000..6c55d7e19 --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MvcCorrelationIdSettingIntegrationSpec.groovy @@ -0,0 +1,13 @@ +package org.springframework.cloud.sleuth.correlation.base + +import org.springframework.cloud.sleuth.correlation.CorrelationIdFilter +import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder + +class MvcCorrelationIdSettingIntegrationSpec extends org.springframework.cloud.sleuth.correlation.base.MvcWiremockIntegrationSpec { + + @Override + protected void configureMockMvcBuilder(ConfigurableMockMvcBuilder mockMvcBuilder) { + super.configureMockMvcBuilder(mockMvcBuilder) + mockMvcBuilder.addFilter(new CorrelationIdFilter()) + } +} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MvcIntegrationSpec.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MvcIntegrationSpec.groovy new file mode 100755 index 000000000..4cd63fd9e --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MvcIntegrationSpec.groovy @@ -0,0 +1,45 @@ +package org.springframework.cloud.sleuth.correlation.base + +import groovy.transform.CompileStatic +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.ApplicationContext +import org.springframework.test.context.web.WebAppConfiguration +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.web.context.WebApplicationContext +import spock.lang.Specification + +/** + * Base for specifications that use Spring's {@link MockMvc}. Provides also {@link WebApplicationContext}, + * {@link ApplicationContext}. The latter you can use to specify what + * kind of address should be returned for a given dependency name. + * + * @see WebApplicationContext + * @see ApplicationContext + */ +@CompileStatic +@WebAppConfiguration +abstract class MvcIntegrationSpec extends Specification { + + @Autowired + protected WebApplicationContext webApplicationContext + @Autowired + protected ApplicationContext applicationContext + + protected MockMvc mockMvc + + void setup() { + ConfigurableMockMvcBuilder mockMvcBuilder = MockMvcBuilders.webAppContextSetup(webApplicationContext) + configureMockMvcBuilder(mockMvcBuilder) + mockMvc = mockMvcBuilder.build() + } + + /** + * Override in a subclass to modify mockMvcBuilder configuration (e.g. add filter). + * + * The method from super class should be called. + */ + protected void configureMockMvcBuilder(ConfigurableMockMvcBuilder mockMvcBuilder) { + } +} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MvcWiremockIntegrationSpec.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MvcWiremockIntegrationSpec.groovy new file mode 100755 index 000000000..5e84ff830 --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/base/MvcWiremockIntegrationSpec.groovy @@ -0,0 +1,37 @@ +package org.springframework.cloud.sleuth.correlation.base + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder +import com.github.tomakehurst.wiremock.client.WireMock +import groovy.transform.CompileStatic +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.test.context.ContextConfiguration + +/** + * Base specification for tests that use Wiremock as HTTP server stub. + * By extending this specification you gain a bean with {@link HttpMockServer} and a {@link WireMock} + * instance that you can stub by using {@link MvcWiremockIntegrationSpec#stubInteraction(com.github.tomakehurst.wiremock.client.MappingBuilder, com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder)} + * + * @see MockServerConfiguration + * @see WireMock + * @see HttpMockServer + * @see MvcIntegrationSpec + */ +@CompileStatic +@ContextConfiguration(classes = [MockServerConfiguration]) +abstract class MvcWiremockIntegrationSpec extends MvcIntegrationSpec { + + @Autowired + protected HttpMockServer httpMockServer + protected WireMock wireMock + + void setup() { + wireMock = new WireMock('localhost', httpMockServer.port()) + wireMock.resetToDefaultMappings() + } + + protected void stubInteraction(MappingBuilder mapping, ResponseDefinitionBuilder response) { + wireMock.register(mapping.willReturn(response)) + } + +} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/hystrix/CorrelatedCommandSpec.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/hystrix/CorrelatedCommandSpec.groovy new file mode 100644 index 000000000..e97c65761 --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/hystrix/CorrelatedCommandSpec.groovy @@ -0,0 +1,40 @@ +package org.springframework.cloud.sleuth.correlation.hystrix + +import com.netflix.hystrix.HystrixCommand +import com.netflix.hystrix.HystrixCommandGroupKey +import org.springframework.cloud.sleuth.correlation.CorrelationIdHolder +import org.springframework.cloud.sleuth.correlation.CorrelationIdUpdater +import spock.lang.Specification + +class CorrelatedCommandSpec extends Specification { + + public static final String CORRELATION_ID = 'A' + + def 'should run Hystrix command with client correlation ID'() { + given: + CorrelationIdUpdater.updateCorrelationId(CORRELATION_ID) + def command = new CorrelatedCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(""))) { + String doRun() { + return CorrelationIdHolder.get() + } + } + when: + def result = command.execute() + then: + result == CORRELATION_ID + } + + def 'should run Hystrix command in different thread'() { + given: + def command = new CorrelatedCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(""))) { + String doRun() { + return Thread.currentThread().name + } + } + when: + def threadName = command.execute() + then: + Thread.currentThread().name != threadName + } + +} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/scheduling/CorrelationIdOnScheduledMethodISpec.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/scheduling/CorrelationIdOnScheduledMethodISpec.groovy new file mode 100644 index 000000000..1d58c1c50 --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/scheduling/CorrelationIdOnScheduledMethodISpec.groovy @@ -0,0 +1,24 @@ +package org.springframework.cloud.sleuth.correlation.scheduling + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.cloud.sleuth.correlation.base.BaseConfiguration +import org.springframework.cloud.sleuth.correlation.CorrelationIdAutoConfiguration +import org.springframework.test.context.ContextConfiguration +import spock.lang.Specification +import spock.util.concurrent.PollingConditions + +@ContextConfiguration(classes = [TaskSchedulingConfiguration, ScheduledBeanConfiguration, CorrelationIdAutoConfiguration, BaseConfiguration]) +class CorrelationIdOnScheduledMethodISpec extends Specification { + + @Autowired + TestBeanWithScheduledMethod beanWithScheduledMethod + + def "should have correlationId set after scheduled method has been called"() { + PollingConditions conditions = new PollingConditions(timeout: 1.5, initialDelay: 0.1, factor: 1.05) + expect: + conditions.eventually { + beanWithScheduledMethod.correlationId != null + } + } + +} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/scheduling/ScheduledBeanConfiguration.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/scheduling/ScheduledBeanConfiguration.groovy new file mode 100644 index 000000000..7bddf1cdb --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/scheduling/ScheduledBeanConfiguration.groovy @@ -0,0 +1,14 @@ +package org.springframework.cloud.sleuth.correlation.scheduling + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class ScheduledBeanConfiguration { + + @Bean + TestBeanWithScheduledMethod testBeanWithScheduledMethod() { + return new TestBeanWithScheduledMethod() + } + +} diff --git a/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/scheduling/TestBeanWithScheduledMethod.groovy b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/scheduling/TestBeanWithScheduledMethod.groovy new file mode 100644 index 000000000..66eb128ed --- /dev/null +++ b/spring-cloud-sleuth-correlation/src/test/groovy/org/springframework/cloud/sleuth/correlation/scheduling/TestBeanWithScheduledMethod.groovy @@ -0,0 +1,15 @@ +package org.springframework.cloud.sleuth.correlation.scheduling + +import org.springframework.cloud.sleuth.correlation.CorrelationIdHolder +import org.springframework.scheduling.annotation.Scheduled + +class TestBeanWithScheduledMethod { + + String correlationId + + @Scheduled(fixedDelay = 50L) + void scheduledMethod() { + correlationId = CorrelationIdHolder.get() + } + +} diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index a1cd2f182..262b5359d 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -16,11 +16,31 @@ .. + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.codehaus.gmavenplus + gmavenplus-plugin + + + maven-surefire-plugin + + + + org.springframework.boot spring-boot-starter-web + + org.springframework.cloud + spring-cloud-sleuth-core + org.springframework.boot spring-boot-starter-actuator @@ -65,7 +85,6 @@ spring-boot-starter-test test - diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java index 472d2d483..c4a3e69f4 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java @@ -1,16 +1,5 @@ package org.springframework.cloud.sleuth.zipkin; -import java.util.List; - -import com.github.kristofa.brave.zipkin.ZipkinSpanCollector; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - import com.github.kristofa.brave.AnnotationSubmitterConfig; import com.github.kristofa.brave.ClientTracer; import com.github.kristofa.brave.ClientTracerConfig; @@ -24,7 +13,18 @@ import com.github.kristofa.brave.TraceFilters; import com.github.kristofa.brave.client.ClientRequestInterceptor; import com.github.kristofa.brave.client.ClientResponseInterceptor; import com.github.kristofa.brave.client.spanfilter.SpanNameFilter; +import com.github.kristofa.brave.zipkin.ZipkinSpanCollector; import com.google.common.base.Optional; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import java.util.List; /** * @author Spencer Gibb @@ -32,6 +32,7 @@ import com.google.common.base.Optional; @Configuration @EnableConfigurationProperties @ConditionalOnClass(ServerTracerConfig.class) +@ConditionalOnProperty(value = "spring.cloud.sleuth.zipkin.enabled", matchIfMissing = true) @Import({ AnnotationSubmitterConfig.class, ClientTracerConfig.class, EndPointSubmitterConfig.class, ServerSpanThreadBinderConfig.class, ServerTracerConfig.class }) diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinRestTemplateInterceptor.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinRestTemplateInterceptor.java index 718feae85..bb6128ddf 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinRestTemplateInterceptor.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinRestTemplateInterceptor.java @@ -33,7 +33,8 @@ public class ZipkinRestTemplateInterceptor implements ClientHttpRequestIntercept @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { - clientRequestInterceptor.handle(new RequestAdapter(request), Optional.absent()); + RequestAdapter requestAdapter = new RequestAdapter(request); + clientRequestInterceptor.handle(requestAdapter, Optional.absent()); ClientHttpResponse response = null; Exception exception = null; diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/web/ZipkinWebAutoConfiguration.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/web/ZipkinWebAutoConfiguration.java index 56c2d1c34..ba22a6999 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/web/ZipkinWebAutoConfiguration.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/web/ZipkinWebAutoConfiguration.java @@ -1,24 +1,22 @@ package org.springframework.cloud.sleuth.zipkin.web; -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.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.cloud.sleuth.zipkin.ZipkinAutoConfiguration; -import org.springframework.cloud.sleuth.zipkin.ZipkinRestTemplateInterceptor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; - import com.github.kristofa.brave.EndPointSubmitter; import com.github.kristofa.brave.ServerTracer; import com.github.kristofa.brave.ServerTracerConfig; import com.github.kristofa.brave.client.ClientRequestInterceptor; import com.github.kristofa.brave.client.ClientResponseInterceptor; +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.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.cloud.sleuth.zipkin.ZipkinAutoConfiguration; +import org.springframework.cloud.sleuth.zipkin.ZipkinRestTemplateInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; /** * @author Spencer Gibb @@ -26,6 +24,7 @@ import com.github.kristofa.brave.client.ClientResponseInterceptor; @Configuration @ConditionalOnClass(ServerTracerConfig.class) @ConditionalOnWebApplication +@ConditionalOnProperty(value = "spring.cloud.sleuth.zipkin.enabled", matchIfMissing = true) @AutoConfigureAfter(ZipkinAutoConfiguration.class) public class ZipkinWebAutoConfiguration { @@ -35,10 +34,10 @@ public class ZipkinWebAutoConfiguration { @Autowired private ServerTracer serverTracer; - /*@Bean + @Bean public ZipkinHandlerInterceptor zipkinHandlerInterceptor() { return new ZipkinHandlerInterceptor(httpServletRequestInterceptor()); - }*/ + } @Bean public ZipkinFilter zipkinFilter() { @@ -65,15 +64,6 @@ public class ZipkinWebAutoConfiguration { @Autowired private ClientResponseInterceptor clientResponseInterceptor; - @Bean - @ConditionalOnMissingBean - public RestTemplate restTemplate() { - //TODO: howto add this to an existing restTemplate without circular dependencies - RestTemplate restTemplate = new RestTemplate(); - restTemplate.getInterceptors().add(zipkinRestTemplateInterceptor()); - return restTemplate; - } - @Bean public ZipkinRestTemplateInterceptor zipkinRestTemplateInterceptor() { return new ZipkinRestTemplateInterceptor(clientRequestInterceptor,