CorrelationID setting by 4financeIT
Below you can find people who helped to create this solution Tomasz Nurkiewicz <tomasz.nurkiewicz@4finance.com> Marcin Zajaczkowski <marcin.zajaczkowski@4finance.com> Kamil Szymanski <kamil.szymanski@4finance.com> Michal Chmielarz <michal.chmielarz@4finance.com> Marcin Grzejszczak <marcin.grzejszczak@4finance.com> Jakub Nabdralik <jakub.nabrdalik@4finance.com> Urszula Choromanska <urszula.choromanska@4finance.com> Tomasz Dziurko <tomasz.dziurko@4finance.com> Tomasz Szymanski <tomasz.szymanski@4finance.com> Adam Chudzik <adam.chudzik@4finance.com>
This commit is contained in:
committed by
Marcin Grzejszczak
parent
b5b3a21a88
commit
dad0505d5a
120
pom.xml
120
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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-sleuth</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
@@ -27,31 +26,62 @@
|
||||
</scm>
|
||||
|
||||
<modules>
|
||||
<module>spring-cloud-sleuth-core</module>
|
||||
<module>spring-cloud-sleuth-correlation</module>
|
||||
<module>spring-cloud-sleuth-zipkin</module>
|
||||
<module>spring-cloud-sleuth-sample</module>
|
||||
<module>docs</module>
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.gmavenplus</groupId>
|
||||
<artifactId>gmavenplus-plugin</artifactId>
|
||||
<version>1.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>testCompile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<configuration>
|
||||
<useFile>false</useFile>
|
||||
<includes>
|
||||
<include>**/*Spec.java</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-sleuth-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.kristofa</groupId>
|
||||
@@ -99,6 +129,16 @@
|
||||
<artifactId>guava</artifactId>
|
||||
<version>18.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.reactivex</groupId>
|
||||
<artifactId>rxjava</artifactId>
|
||||
<version>1.0.10</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-zuul</artifactId>
|
||||
@@ -111,12 +151,66 @@
|
||||
<version>1.12.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.hystrix</groupId>
|
||||
<artifactId>hystrix-core</artifactId>
|
||||
<version>1.4.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjrt</artifactId>
|
||||
<version>${aspectj.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
<version>${aspectj.version}</version>
|
||||
</dependency>
|
||||
<!-- Spock -->
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-core</artifactId>
|
||||
<version>${spock.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-spring</artifactId>
|
||||
<version>${spock.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cglib</groupId>
|
||||
<artifactId>cglib-nodep</artifactId>
|
||||
<version>3.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.objenesis</groupId>
|
||||
<artifactId>objenesis</artifactId>
|
||||
<version>2.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
<version>1.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-all</artifactId>
|
||||
<version>2.4.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<properties>
|
||||
<brave.version>2.4.2</brave.version>
|
||||
<spring-cloud.version>1.0.1.BUILD-SNAPSHOT</spring-cloud.version>
|
||||
<aspectj.version>1.8.4</aspectj.version>
|
||||
<spock.version>1.0-groovy-2.4</spock.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
|
||||
58
spring-cloud-sleuth-core/pom.xml
Normal file
58
spring-cloud-sleuth-core/pom.xml
Normal file
@@ -0,0 +1,58 @@
|
||||
<?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-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Cloud Sleuth Core</name>
|
||||
<description>Spring Cloud Sleuth Core</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-sleuth</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.gmavenplus</groupId>
|
||||
<artifactId>gmavenplus-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<!-- Only needed at compile time -->
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -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<ClientHttpRequestInterceptor> clientHttpRequestInterceptors;
|
||||
|
||||
public DefaultRestTemplateConfigurer(List<ClientHttpRequestInterceptor> clientHttpRequestInterceptors) {
|
||||
this.clientHttpRequestInterceptors = clientHttpRequestInterceptors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyRestTemplate(RestTemplate restTemplate) {
|
||||
for (ClientHttpRequestInterceptor clientHttpRequestInterceptor : clientHttpRequestInterceptors) {
|
||||
restTemplate.getInterceptors().add(clientHttpRequestInterceptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<ClientHttpRequestInterceptor> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Auto Configuration
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.sleuth.resttemplate.SleuthRestTemplateAutoConfiguration
|
||||
132
spring-cloud-sleuth-correlation/pom.xml
Normal file
132
spring-cloud-sleuth-correlation/pom.xml
Normal file
@@ -0,0 +1,132 @@
|
||||
<?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-correlation</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Cloud Sleuth Correlation</name>
|
||||
<description>Spring Cloud Sleuth Correlation</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-sleuth</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.gmavenplus</groupId>
|
||||
<artifactId>gmavenplus-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-sleuth-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<!-- Only needed at compile time -->
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.hystrix</groupId>
|
||||
<artifactId>hystrix-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjrt</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.reactivex</groupId>
|
||||
<artifactId>rxjava</artifactId>
|
||||
</dependency>
|
||||
<!-- Spock -->
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-spring</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cglib</groupId>
|
||||
<artifactId>cglib-nodep</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.objenesis</groupId>
|
||||
<artifactId>objenesis</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-all</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.gpars</groupId>
|
||||
<artifactId>gpars</artifactId>
|
||||
<version>1.2.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock</artifactId>
|
||||
<version>1.53</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.stefanbirkner</groupId>
|
||||
<artifactId>system-rules</artifactId>
|
||||
<version>1.9.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -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
|
||||
* <p/>
|
||||
* <ul>
|
||||
* <li>{@link RestController} annotated classes
|
||||
* with public {@link Callable} methods</li>
|
||||
* <li>{@link Controller} annotated classes
|
||||
* with public {@link Callable} methods</li>
|
||||
* <li>explicit {@link RestOperations}.exchange(..) method calls</li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* For controllers an around aspect is created that wraps the {@link Callable#call()} method execution
|
||||
* in {@link CorrelationIdUpdater#wrapCallableWithId(Callable)}
|
||||
* <p/>
|
||||
* For {@link RestOperations} we are wrapping all executions of the
|
||||
* <b>exchange</b> 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<Object> 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<Object> modifyHttpEntityInMethodArguments(ProceedingJoinPoint pjp, HttpEntity newHttpEntity) {
|
||||
List<Object> 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<String>() {
|
||||
@Override
|
||||
public String call() throws Exception {
|
||||
return response.getHeader(CORRELATION_ID_HEADER);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getCorrelationIdFrom(final HttpServletRequest request) {
|
||||
return withLoggingAs("request", new Callable<String>() {
|
||||
@Override
|
||||
public String call() throws Exception {
|
||||
return request.getHeader(CORRELATION_ID_HEADER);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String withLoggingAs(String whereWasFound, Callable<String> 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<String> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<String> id = new ThreadLocal<String>();
|
||||
|
||||
public static void set(String correlationId) {
|
||||
id.set(correlationId);
|
||||
}
|
||||
|
||||
public static String get() {
|
||||
return id.get();
|
||||
}
|
||||
|
||||
public static void remove() {
|
||||
id.remove();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> T withId(String temporaryCorrelationId, Callable<T> 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.
|
||||
* <p/>
|
||||
* <p/>
|
||||
* Useful in a situation when a Callable should be executed in a separate thread, for example in aspects.
|
||||
* <p/>
|
||||
* <pre><code>
|
||||
* @Around('...')
|
||||
* Object wrapWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable {
|
||||
* Callable callable = pjp.proceed() as Callable
|
||||
* return CorrelationIdUpdater.wrapCallableWithId {
|
||||
* callable.call()
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
* <p/>
|
||||
* <b>Note</b>: 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 <T> Callable<T> wrapCallableWithId(final Callable<T> block) {
|
||||
final String temporaryCorrelationId = CorrelationIdHolder.get();
|
||||
// unchecked assignment due to groovyc issues with <T>
|
||||
return new Callable() {
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
final String oldCorrelationId = CorrelationIdHolder.get();
|
||||
try {
|
||||
updateCorrelationId(temporaryCorrelationId);
|
||||
return block.call();
|
||||
} finally {
|
||||
updateCorrelationId(oldCorrelationId);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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 <a href="http://www.4financeit.com>4financeIT</a>
|
||||
*/
|
||||
public class UuidGenerator {
|
||||
public String create() {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.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 java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* Abstraction over {@code HystrixCommand} that wraps command execution with CorrelationID setting
|
||||
*
|
||||
* @see HystrixCommand
|
||||
* @see CorrelationIdUpdater
|
||||
*
|
||||
* @author Tomasz Nurkiewicz, 4financeIT
|
||||
* @author Marcin Grzejszczak, 4financeIT
|
||||
*/
|
||||
public abstract class CorrelatedCommand<R> extends HystrixCommand<R> {
|
||||
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<R>() {
|
||||
@Override
|
||||
public R call() throws Exception {
|
||||
return doRun();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public abstract R doRun() throws Exception;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Auto Configuration
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.sleuth.correlation.CorrelationIdAutoConfiguration
|
||||
@@ -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<String> hasCorrelationIdSet = new TypeSafeMatcher<String>() {
|
||||
@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<String> asyncPing() {
|
||||
return {
|
||||
callWiremockAndReturnOk()
|
||||
}
|
||||
}
|
||||
|
||||
private String callWiremockAndReturnOk() {
|
||||
restTemplate.getForObject("http://localhost:${httpMockServer.port()}", String)
|
||||
return "OK"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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' | _
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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<String> callable = new CorrelationIdTestCallable()
|
||||
when:
|
||||
Callable<String> 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<String> 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<String> {
|
||||
@Override
|
||||
String call() throws Exception {
|
||||
CorrelationIdHolder.get()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String>(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<String>(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(""))) {
|
||||
String doRun() {
|
||||
return Thread.currentThread().name
|
||||
}
|
||||
}
|
||||
when:
|
||||
def threadName = command.execute()
|
||||
then:
|
||||
Thread.currentThread().name != threadName
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,11 +16,31 @@
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.gmavenplus</groupId>
|
||||
<artifactId>gmavenplus-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-sleuth-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
@@ -65,7 +85,6 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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.<String>absent());
|
||||
RequestAdapter requestAdapter = new RequestAdapter(request);
|
||||
clientRequestInterceptor.handle(requestAdapter, Optional.<String>absent());
|
||||
|
||||
ClientHttpResponse response = null;
|
||||
Exception exception = null;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user