From 0de00870a693a2af75ee64d5a70acd31eb3b813b Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Fri, 26 Jun 2015 16:01:50 -0600 Subject: [PATCH] add TraceWebAspect to wrap Callable's returned in Controllers. --- .../sleuth/instrument/web/TraceWebAspect.java | 82 +++++++++++++++++++ .../web/TraceWebAutoConfiguration.java | 6 ++ .../sleuth/sample/SampleApplication.java | 68 ++------------- .../cloud/sleuth/sample/SampleController.java | 82 +++++++++++++++++++ 4 files changed, 178 insertions(+), 60 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java create mode 100644 spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleController.java 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 + *

+ *

+ *

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