diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java
new file mode 100644
index 000000000..7ffe04d2b
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java
@@ -0,0 +1,82 @@
+package org.springframework.cloud.sleuth.instrument.web;
+
+import java.util.concurrent.Callable;
+
+import lombok.extern.apachecommons.CommonsLog;
+
+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.springframework.cloud.sleuth.Trace;
+import org.springframework.cloud.sleuth.TraceContextHolder;
+import org.springframework.cloud.sleuth.instrument.TraceCallable;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestOperations;
+
+/**
+ * Aspect that adds correlation id to
+ *
+ *
+ * - {@link RestController} annotated classes with public {@link Callable} methods
+ * - {@link Controller} annotated classes with public {@link Callable} methods
+ *
+ *
+ * For controllers an around aspect is created that wraps the {@link Callable#call()}
+ * method execution in {@link TraceCallable}
+ *
+ *
+ * @see RestController
+ * @see Controller
+ * @see RestOperations
+ * @see TraceCallable
+ * @see Trace
+ *
+ * @author Tomasz Nurkewicz, 4financeIT
+ * @author Marcin Grzejszczak, 4financeIT
+ * @author Michal Chmielarz, 4financeIT
+ * @author Spencer Gibb
+ */
+@Aspect
+@CommonsLog
+public class TraceWebAspect {
+
+ private final Trace trace;
+
+ public TraceWebAspect(Trace trace) {
+ this.trace = trace;
+ }
+
+ @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()")
+ @SuppressWarnings("unchecked")
+ public Object wrapWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable {
+ Callable callable = (Callable) pjp.proceed();
+ if (TraceContextHolder.isTracing()) {
+ log.debug("Wrapping callable with span ["
+ + TraceContextHolder.getCurrentSpan() + "]");
+
+ return new TraceCallable(this.trace, callable);
+ }
+ else {
+ return callable;
+ }
+ }
+
+}
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java
index 00aebe585..5e4d8045b 100644
--- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java
@@ -52,6 +52,12 @@ public class TraceWebAutoConfiguration {
@Autowired
private Trace trace;
+ @Bean
+ @ConditionalOnMissingBean
+ public TraceWebAspect traceWebAspect() {
+ return new TraceWebAspect(trace);
+ }
+
@Bean
@ConditionalOnMissingBean
public TraceHandlerInterceptor traceHandlerInterceptor() {
diff --git a/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleApplication.java b/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleApplication.java
index 90b1f9d88..8897123cf 100644
--- a/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleApplication.java
+++ b/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleApplication.java
@@ -1,90 +1,38 @@
package org.springframework.cloud.sleuth.sample;
-import java.util.Random;
-
-import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
import org.springframework.cloud.sleuth.Sampler;
-import org.springframework.cloud.sleuth.Trace;
-import org.springframework.cloud.sleuth.TraceScope;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
-import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.scheduling.annotation.EnableAsync;
/**
* @author Spencer Gibb
*/
@Configuration
@EnableAutoConfiguration
-@EnableAspectJAutoProxy
+@EnableAspectJAutoProxy(proxyTargetClass = true)
+@EnableAsync
@Slf4j
public class SampleApplication {
public static final String CLIENT_NAME = "testApp";
- @RestController
- protected static class SampleController implements
- ApplicationListener {
- @Autowired
- private RestTemplate restTemplate;
- @Autowired
- private Trace trace;
- private int port;
-
- @SneakyThrows
- @RequestMapping("/")
- public String hi() {
- final Random random = new Random();
- Thread.sleep(random.nextInt(1000));
-
- String s = restTemplate.getForObject("http://localhost:" + port + "/hi2",
- String.class);
- return "hi/" + s;
- }
-
- @SneakyThrows
- @RequestMapping("/hi2")
- public String hi2() {
- final Random random = new Random();
- Thread.sleep(random.nextInt(1000));
- return "hi2";
- }
-
- @SneakyThrows
- @RequestMapping("/traced")
- public String traced() {
- TraceScope scope = trace.startSpan("customTraceEndpoint", new AlwaysSampler());
- final Random random = new Random();
- int millis = random.nextInt(1000);
- log.info("Sleeping for {} millis", millis);
- Thread.sleep(millis);
-
- String s = restTemplate.getForObject("http://localhost:" + port + "/hi2", String.class);
- scope.close();
- return "hi/" + s;
- }
-
- @Override
- public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
- port = event.getEmbeddedServletContainer().getPort();
- }
- }
-
@Bean
public Sampler defaultSampler() {
return new AlwaysSampler();
}
+ @Bean
+ public SampleController sampleController() {
+ return new SampleController();
+ }
+
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
diff --git a/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleController.java b/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleController.java
new file mode 100644
index 000000000..242ce39e0
--- /dev/null
+++ b/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleController.java
@@ -0,0 +1,82 @@
+package org.springframework.cloud.sleuth.sample;
+
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
+import org.springframework.cloud.sleuth.Span;
+import org.springframework.cloud.sleuth.Trace;
+import org.springframework.cloud.sleuth.TraceContextHolder;
+import org.springframework.cloud.sleuth.TraceScope;
+import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
+import org.springframework.context.ApplicationListener;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Random;
+import java.util.concurrent.Callable;
+
+/**
+ * @author Spencer Gibb
+ */
+@Slf4j
+@RestController
+class SampleController implements
+ ApplicationListener {
+ @Autowired
+ private RestTemplate restTemplate;
+ @Autowired
+ private Trace trace;
+ private int port;
+
+ @SneakyThrows
+ @RequestMapping("/")
+ public String hi() {
+ final Random random = new Random();
+ Thread.sleep(random.nextInt(1000));
+
+ String s = restTemplate.getForObject("http://localhost:" + port + "/hi2",
+ String.class);
+ return "hi/" + s;
+ }
+
+ @RequestMapping("/call")
+ public Callable call() {
+ return new Callable() {
+ @Override
+ public String call() throws Exception {
+ Span currentSpan = TraceContextHolder.getCurrentSpan();
+ return "async hi: "+currentSpan;
+ }
+ };
+ }
+
+
+ @SneakyThrows
+ @RequestMapping("/hi2")
+ public String hi2() {
+ final Random random = new Random();
+ Thread.sleep(random.nextInt(1000));
+ return "hi2";
+ }
+
+ @SneakyThrows
+ @RequestMapping("/traced")
+ public String traced() {
+ TraceScope scope = trace.startSpan("customTraceEndpoint", new AlwaysSampler());
+ final Random random = new Random();
+ int millis = random.nextInt(1000);
+ log.info("Sleeping for {} millis", millis);
+ Thread.sleep(millis);
+
+ String s = restTemplate.getForObject("http://localhost:" + port + "/hi2", String.class);
+ scope.close();
+ return "hi/" + s;
+ }
+
+ @Override
+ public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
+ port = event.getEmbeddedServletContainer().getPort();
+ }
+}