diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc
index da967c847..ab722bd7e 100644
--- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc
+++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc
@@ -5,6 +5,7 @@
:toc: left
:toclevels: 8
:nofooter:
+:sectlinks: true
Spring Cloud Sleuth
====================
@@ -768,7 +769,8 @@ You can disable this behaviour by setting the value of `spring.sleuth.async.enab
If you annotate your method with `@Async` then we'll automatically create a new Span with the following characteristics:
- - the Span name will be the annotated method name
+ - if the method is annotated with `@SpanName` then the value of the annotation will be the Span's name
+ - if the method is *not* annotated with `@SpanName` the Span name will be the annotated method name
- the Span will be tagged with that method's class name and the method name too
==== @Scheduled annotated methods
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/DefaultSpanNamer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/DefaultSpanNamer.java
index 2f077cfa9..c8fd215f3 100644
--- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/DefaultSpanNamer.java
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/DefaultSpanNamer.java
@@ -16,13 +16,16 @@
package org.springframework.cloud.sleuth;
+import java.lang.reflect.Method;
+
import org.springframework.core.annotation.AnnotationUtils;
/**
* Default implementation of SpanNamer that tries to get the span name as follows:
*
*
- * from the @SpanName annotation if one is present
+ * from the @SpanName annotation on the class if one is present
+ * from the @SpanName annotation on the method if passed object is of a {@link Method} type
* from the toString() of the delegate if it's not the
* default {@link Object#toString()}
* the default provided value
@@ -37,8 +40,7 @@ public class DefaultSpanNamer implements SpanNamer {
@Override
public String name(Object object, String defaultValue) {
- SpanName annotation = AnnotationUtils
- .findAnnotation(object.getClass(), SpanName.class);
+ SpanName annotation = annotation(object);
String spanName = annotation != null ? annotation.value() : object.toString();
// If there is no overridden toString method we'll put a constant value
if (isDefaultToString(object, spanName)) {
@@ -47,7 +49,18 @@ public class DefaultSpanNamer implements SpanNamer {
return spanName;
}
+ private SpanName annotation(Object o) {
+ if (o instanceof Method) {
+ return AnnotationUtils.findAnnotation((Method) o, SpanName.class);
+ }
+ return AnnotationUtils
+ .findAnnotation(o.getClass(), SpanName.class);
+ }
+
private static boolean isDefaultToString(Object delegate, String spanName) {
+ if (delegate instanceof Method) {
+ return delegate.toString().equals(spanName);
+ }
return (delegate.getClass().getName() + "@" +
Integer.toHexString(delegate.hashCode())).equals(spanName);
}
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanName.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanName.java
index f676fc0d8..729294111 100644
--- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanName.java
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanName.java
@@ -55,10 +55,13 @@ import java.lang.annotation.Target;
* };
* }
*
+ * Starting with version {@code 1.3.0} you can also put the annotation on an {@link org.springframework.scheduling.annotation.Async}
+ * annotated method and the value of that annotation will be used as the span name.
+ *
* @author Marcin Grzejszczak
* @since 1.0.0
*/
-@Target(ElementType.TYPE)
+@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SpanName {
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java
index 65a82ce86..2d92887f5 100644
--- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java
@@ -22,6 +22,7 @@ import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.sleuth.Span;
+import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.util.SpanNameUtil;
@@ -46,6 +47,7 @@ public class TraceAsyncAspect {
private final Tracer tracer;
private final TraceKeys traceKeys;
private final BeanFactory beanFactory;
+ private SpanNamer spanNamer;
public TraceAsyncAspect(Tracer tracer, TraceKeys traceKeys, BeanFactory beanFactory) {
this.tracer = tracer;
@@ -55,8 +57,9 @@ public class TraceAsyncAspect {
@Around("execution (@org.springframework.scheduling.annotation.Async * *.*(..))")
public Object traceBackgroundThread(final ProceedingJoinPoint pjp) throws Throwable {
- Span span = this.tracer.createSpan(
+ String spanName = spanNamer().name(getMethod(pjp, pjp.getTarget()),
SpanNameUtil.toLowerHyphen(pjp.getSignature().getName()));
+ Span span = this.tracer.createSpan(spanName);
this.tracer.addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, ASYNC_COMPONENT);
this.tracer.addTag(this.traceKeys.getAsync().getPrefix() +
this.traceKeys.getAsync().getClassNameKey(), pjp.getTarget().getClass().getSimpleName());
@@ -76,4 +79,11 @@ public class TraceAsyncAspect {
.findMethod(object.getClass(), method.getName(), method.getParameterTypes());
}
+ SpanNamer spanNamer() {
+ if (this.spanNamer == null) {
+ this.spanNamer = this.beanFactory.getBean(SpanNamer.class);
+ }
+ return this.spanNamer;
+ }
+
}
diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTests.java
index 18f27dd42..cc865e797 100644
--- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTests.java
+++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTests.java
@@ -16,7 +16,10 @@
package org.springframework.cloud.sleuth;
+import java.lang.reflect.Method;
+
import org.junit.Test;
+import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.BDDAssertions.then;
@@ -42,6 +45,18 @@ public class DefaultSpanNamerTests {
then(this.defaultSpanNamer.name(new ClassWithoutToString(), "default")).isEqualTo("default");
}
+ @Test
+ public void should_return_value_of_span_name_from_annotation_on_method() throws Exception {
+ Method method = ReflectionUtils.findMethod(ClassWithAnnotatedMethod.class, "method");
+ then(this.defaultSpanNamer.name(method, "default")).isEqualTo("foo");
+ }
+
+ @Test
+ public void should_return_default_value_of_span_name_from_annotation_on_method() throws Exception {
+ Method method = ReflectionUtils.findMethod(ClassWithNonAnnotatedMethod.class, "method");
+ then(this.defaultSpanNamer.name(method, "default")).isEqualTo("default");
+ }
+
@SpanName("somevalue")
static class ClassWithAnnotation {}
@@ -60,4 +75,13 @@ public class DefaultSpanNamerTests {
}
static class ClassWithoutToString {}
+
+ static class ClassWithAnnotatedMethod {
+ @SpanName("foo")
+ void method() {}
+ }
+
+ static class ClassWithNonAnnotatedMethod {
+ void method() {}
+ }
}
\ No newline at end of file
diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java
index f51b979b3..94a776b88 100644
--- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java
+++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java
@@ -38,6 +38,7 @@ import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.hateoas.PagedResources;
import org.springframework.http.RequestEntity;
import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;
@@ -58,6 +59,7 @@ import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ReservationServiceApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@DirtiesContext
+@ActiveProfiles("data")
public class SpringDataInstrumentationTests {
@Autowired
diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceAsyncIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceAsyncIntegrationTests.java
index 29645c716..a4928643e 100644
--- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceAsyncIntegrationTests.java
+++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceAsyncIntegrationTests.java
@@ -2,17 +2,21 @@
package org.springframework.cloud.sleuth.instrument.web;
import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.assertj.core.api.BDDAssertions.then;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
import java.util.concurrent.atomic.AtomicReference;
+import org.assertj.core.api.BDDAssertions;
import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.Span;
+import org.springframework.cloud.sleuth.SpanName;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
@@ -35,6 +39,11 @@ public class TraceAsyncIntegrationTests {
@Autowired
Tracer tracer;
+ @Before
+ public void cleanup() {
+ this.classPerformingAsyncLogic.clear();
+ }
+
@Test
public void should_set_span_on_an_async_annotated_method() {
Span span = givenASpanInCurrentThread();
@@ -42,6 +51,17 @@ public class TraceAsyncIntegrationTests {
whenAsyncProcessingTakesPlace();
thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOne(span);
+ this.tracer.close(span);
+ }
+
+ @Test
+ public void should_set_span_with_custom_method_on_an_async_annotated_method() {
+ Span span = givenASpanInCurrentThread();
+
+ whenAsyncProcessingTakesPlaceWithCustomSpanName();
+
+ thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOneAndSpanHasCustomName(span);
+ this.tracer.close(span);
}
private Span givenASpanInCurrentThread() {
@@ -52,6 +72,10 @@ public class TraceAsyncIntegrationTests {
this.classPerformingAsyncLogic.invokeAsynchronousLogic();
}
+ private void whenAsyncProcessingTakesPlaceWithCustomSpanName() {
+ this.classPerformingAsyncLogic.customNameInvokeAsynchronousLogic();
+ }
+
private void thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOne(final Span span) {
Awaitility.await().atMost(5, SECONDS).untilAsserted(
() -> then(TraceAsyncIntegrationTests.this.classPerformingAsyncLogic.getSpan())
@@ -62,6 +86,16 @@ public class TraceAsyncIntegrationTests {
.hasATag("method", "invokeAsynchronousLogic"));
}
+ private void thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOneAndSpanHasCustomName(final Span span) {
+ Awaitility.await().atMost(5, SECONDS).untilAsserted(
+ () -> then(TraceAsyncIntegrationTests.this.classPerformingAsyncLogic.getSpan())
+ .hasTraceIdEqualTo(span.getTraceId())
+ .hasNameEqualTo("foo")
+ .isALocalComponentSpan()
+ .hasATag("class", "ClassPerformingAsyncLogic")
+ .hasATag("method", "customNameInvokeAsynchronousLogic"));
+ }
+
@After
public void cleanTrace() {
TestSpanContextHolder.removeCurrentSpan();
@@ -93,8 +127,18 @@ public class TraceAsyncIntegrationTests {
this.span.set(TestSpanContextHolder.getCurrentSpan());
}
+ @Async
+ @SpanName("foo")
+ public void customNameInvokeAsynchronousLogic() {
+ this.span.set(TestSpanContextHolder.getCurrentSpan());
+ }
+
public Span getSpan() {
return this.span.get();
}
+
+ public void clear() {
+ this.span.set(null);
+ }
}
}
diff --git a/spring-cloud-sleuth-core/src/test/resources/application-data.yml b/spring-cloud-sleuth-core/src/test/resources/application-data.yml
new file mode 100644
index 000000000..de733c518
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/test/resources/application-data.yml
@@ -0,0 +1 @@
+spring.autoconfigure.exclude:
\ No newline at end of file
diff --git a/spring-cloud-sleuth-core/src/test/resources/application.yml b/spring-cloud-sleuth-core/src/test/resources/application.yml
index 293cf2e33..2de958e10 100644
--- a/spring-cloud-sleuth-core/src/test/resources/application.yml
+++ b/spring-cloud-sleuth-core/src/test/resources/application.yml
@@ -15,4 +15,7 @@ ribbon.eureka.enabled: false
spring.sleuth.scheduled.skipPattern: "^org.*TestBeanWithScheduledMethodToBeIgnored$"
# comma separated list of matchers
spring.sleuth.rxjava.schedulers.ignoredthreads: HystixMetricPoller,^MyCustomThread.*$,^RxComputation.*$
-security.ignored: /**
\ No newline at end of file
+security.ignored: /**
+
+#disable hibernate by default
+spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
\ No newline at end of file