Managing spans with annotations (#526)

The main arguments for these features are

* api-agnostic means to collaborate with a span
    - use of annotations allows users to add to a span with no library dependency on a span api.
    This allows Sleuth to change its core api less impact to user code.
* reduced surface area for basic span operations.
    - without this feature one has to use the span api, which has lifecycle commands that
    could be used incorrectly. By only exposing scope, tag and log functionality, users can
    collaborate without accidentally breaking span lifecycle.
* collaboration with runtime generated code
    - with libraries such as Spring Data / Feign the implementations of interfaces are generated
    at runtime thus span wrapping of objects was tedious. Now you can provide annotations
     over interfaces and arguments of those interfaces

This PR is an adoption of @Koizumi85 work started here - https://github.com/Koizumi85/spring-cloud-sleuth-annotation

fixes #182
This commit is contained in:
Marcin Grzejszczak
2017-02-27 15:26:03 +01:00
committed by GitHub
parent b7659ede11
commit 0f29735c11
34 changed files with 2273 additions and 7 deletions

View File

@@ -629,6 +629,8 @@ works via Zipkin-compatible request headers. This propagation logic is defined a
* Sleuth gives you the possibility to propagate context (also known as baggage) between processes. That means that if you set on a Span
a baggage element then it will be sent downstream either via HTTP or messaging to other processes.
* Provides a way to create / continue spans and add tags and logs via annotations.
* Provides simple metrics of accepted / dropped spans.
* If `spring-cloud-sleuth-zipkin` then the app will generate and collect Zipkin-compatible traces.

View File

@@ -248,7 +248,7 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.3.3.RELEASE</version>
<version>1.5.1.RELEASE</version>
<executions>
<execution>
<goals>

View File

@@ -26,6 +26,7 @@ import javax.annotation.PreDestroy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -33,8 +34,15 @@ import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory
import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.annotation.ContinueSpan;
import org.springframework.cloud.sleuth.annotation.NewSpan;
import org.springframework.cloud.sleuth.annotation.SpanTag;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
@@ -57,6 +65,9 @@ public class SleuthBenchmarkingSpringApp implements
public int port;
@Autowired(required = false) Tracer tracer;
@Autowired AClass aClass;
@RequestMapping("/foo")
public String foo() {
return "foo";
@@ -77,6 +88,14 @@ public class SleuthBenchmarkingSpringApp implements
return this.pool.submit(() -> "async");
}
public String manualSpan() {
return this.aClass.manualSpan();
}
public String newSpan() {
return this.aClass.newSpan();
}
@Override
public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
this.port = event.getEmbeddedServletContainer().getPort();
@@ -93,11 +112,18 @@ public class SleuthBenchmarkingSpringApp implements
this.pool.shutdownNow();
}
@Bean
public Sampler alwaysSampler() {
@Bean Sampler alwaysSampler() {
return new AlwaysSampler();
}
@Bean AnotherClass anotherClass() {
return new AnotherClass(this.tracer);
}
@Bean AClass aClass() {
return new AClass(this.tracer, anotherClass());
}
public ExecutorService getPool() {
return this.pool;
}
@@ -106,3 +132,48 @@ public class SleuthBenchmarkingSpringApp implements
SpringApplication.run(SleuthBenchmarkingSpringApp.class, args);
}
}
class AClass {
private final Tracer tracer;
private final AnotherClass anotherClass;
AClass(Tracer tracer, AnotherClass anotherClass) {
this.tracer = tracer;
this.anotherClass = anotherClass;
}
public String manualSpan() {
Span manual = this.tracer.createSpan("span-name");
try {
return this.anotherClass.continuedSpan();
} finally {
this.tracer.close(manual);
}
}
@NewSpan
public String newSpan() {
return this.anotherClass.continuedAnnotation("bar");
}
}
class AnotherClass {
private final Tracer tracer;
AnotherClass(Tracer tracer) {
this.tracer = tracer;
}
@ContinueSpan(log = "continuedspan")
public String continuedAnnotation(@SpanTag("foo") String tagValue) {
return "continued";
}
public String continuedSpan() {
Span continuedSpan = this.tracer.continueSpan(this.tracer.getCurrentSpan());
this.tracer.addTag("foo", "bar");
continuedSpan.logEvent("continuedspan.before");
String response = "continued";
continuedSpan.logEvent("continuedspan.after");
return response;
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2013-2016 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.benchmarks.jmh.benchmarks;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.sleuth.benchmarks.app.SleuthBenchmarkingSpringApp;
import org.springframework.cloud.sleuth.util.ExceptionUtils;
import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.BDDAssertions.then;
@Measurement(iterations = 5)
@Warmup(iterations = 10)
@Fork(3)
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Threads(Threads.MAX)
public class AnnotationBenchmarks {
@State(Scope.Benchmark)
public static class BenchmarkContext {
volatile ConfigurableApplicationContext withSleuth;
volatile SleuthBenchmarkingSpringApp sleuth;
@Setup public void setup() {
this.withSleuth = new SpringApplication(
SleuthBenchmarkingSpringApp.class)
.run("--spring.jmx.enabled=false",
"--spring.application.name=withSleuth");
this.sleuth = this.withSleuth.getBean(
SleuthBenchmarkingSpringApp.class);
}
@TearDown public void clean() {
this.sleuth.clean();
this.withSleuth.close();
}
}
@Benchmark
public void manuallyCreatedSpans(BenchmarkContext context)
throws Exception {
then(context.sleuth.manualSpan()).isEqualTo("continued");
then(ExceptionUtils.getLastException()).isNull();
}
@Benchmark
public void spanCreatedWithAnnotations(BenchmarkContext context)
throws Exception {
then(context.sleuth.newSpan()).isEqualTo("continued");
then(ExceptionUtils.getLastException()).isNull();
}
}

View File

@@ -18,7 +18,6 @@ package org.springframework.cloud.sleuth.benchmarks.jmh.benchmarks;
import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;

View File

@@ -18,7 +18,6 @@ package org.springframework.cloud.sleuth.benchmarks.jmh.benchmarks;
import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import org.openjdk.jmh.annotations.Benchmark;

View File

@@ -37,6 +37,8 @@ works via Zipkin-compatible request headers. This propagation logic is defined a
* Sleuth gives you the possibility to propagate context (also known as baggage) between processes. That means that if you set on a Span
a baggage element then it will be sent downstream either via HTTP or messaging to other processes.
* Provides a way to create / continue spans and add tags and logs via annotations.
* Provides simple metrics of accepted / dropped spans.
* If `spring-cloud-sleuth-zipkin` then the app will generate and collect Zipkin-compatible traces.

View File

@@ -201,6 +201,146 @@ include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/
will lead in creating a span named `calculateTax`.
== Managing spans with annotations
=== Rationale
The main arguments for this features are
* api-agnostic means to collaborate with a span
- use of annotations allows users to add to a span with no library dependency on a span api.
This allows Sleuth to change its core api less impact to user code.
* reduced surface area for basic span operations.
- without this feature one has to use the span api, which has lifecycle commands that
could be used incorrectly. By only exposing scope, tag and log functionality, users can
collaborate without accidentally breaking span lifecycle.
* collaboration with runtime generated code
- with libraries such as Spring Data / Feign the implementations of interfaces are generated
at runtime thus span wrapping of objects was tedious. Now you can provide annotations
over interfaces and arguments of those interfaces
=== Creating new spans
If you really don't want to take care of creating local spans manually you can profit from the
`@NewSpan` annotation. Also we give you the `@SpanTag` annotation to add tags in an automated
fashion.
Let's look at some examples of usage.
[source,java]
----
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAspectTests.java[tags=annotated_method,indent=0]
----
Annotating the method without any parameter will lead to a creation of a new span whose name
will be equal to annotated method name.
[source,java]
----
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAspectTests.java[tags=custom_name_on_annotated_method,indent=0]
----
If you provide the value in the annotation (either directly or via the `name` parameter) then
the created span will have the name as the provided value.
[source,java]
----
// method declaration
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAspectTests.java[tags=custom_name_and_tag_on_annotated_method,indent=0]
// and method execution
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAspectTests.java[tags=execution,indent=0]
----
You can combine both the name and a tag. Let's focus on the latter. In this case whatever the value of
the annotated method's parameter runtime value will be - that will be the value of the tag. In our sample
the tag key will be `testTag` and the tag value will be `test`.
[source,java]
----
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAspectTests.java[tags=name_on_implementation,indent=0]
----
You can place the `@NewSpan` annotation on both the class and an interface. If you override the
interface's method and provide a different value of the `@NewSpan` annotation then the most
concrete one wins (in this case `customNameOnTestMethod3` will be set).
=== Continuing spans
If you want to just add tags and annotations to an existing span it's enough
to use the `@ContinueSpan` annotation as presented below. Note that in contrast
with the `@NewSpan` annotation you can also add logs via the `log` parameter:
[source,java]
----
// method declaration
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAspectTests.java[tags=continue_span,indent=0]
// method execution
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAspectTests.java[tags=continue_span_execution,indent=0]
----
That way the span will get continued and:
- logs with name `testMethod11.before` and `testMethod11.after` will be created
- if an exception will be thrown a log `testMethod11.afterFailure` will also be created
- tag with key `testTag11` and value `test` will be created
=== More advanced tag setting
There are 3 different ways to add tags to a span. All of them are controlled by the `SpanTag` annotation.
Precedence is:
- try with the bean of `TagValueResolver` type and provided name
- if one hasn't provided the bean name, try to evaluate an expression. We're searching for a `TagValueExpressionResolver` bean.
The default implementation uses SPEL expression resolution.
- if one hasn't provided any expression to evaluate just return a `toString()` value of the parameter
==== Custom extractor
The value of the tag for following method will be computed by an implementation of `TagValueResolver` interface.
Its class name has to be passed as the value of the `resolver` attribute.
Having such an annotated method:
[source,java]
----
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SpanTagAnnotationHandlerTests.java[tags=resolver_bean,indent=0]
----
and such a `TagValueResolver` bean implementation
[source,java]
----
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SpanTagAnnotationHandlerTests.java[tags=custom_resolver,indent=0]
----
Will lead to setting of a tag value equal to `Value from myCustomTagValueResolver`.
==== Resolving expressions for value
Having such an annotated method:
[source,java]
----
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SpanTagAnnotationHandlerTests.java[tags=spel,indent=0]
----
and no custom implementation of a `TagValueExpressionResolver` will lead to evaluation of the SPEL expression and a tag with value `4 characters` will be set on the span.
If you want to use some other expression resolution mechanism you can create your own implementation
of the bean.
==== Using toString method
Having such an annotated method:
[source,java]
----
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SpanTagAnnotationHandlerTests.java[tags=toString,indent=0]
----
if executed with a value of `15` will lead to setting of a tag with a String value of `"15"`.
== Customizations
Thanks to the `SpanInjector` and `SpanExtractor` you can customize the way spans

View File

@@ -2,4 +2,4 @@
echo "Running JMH Benchmarks"
./mvnw clean install -DskipTests --projects benchmarks --also-make -Pbenchmarks,jmh
java -Djmh.ignoreLock=true -jar benchmarks/target/benchmarks.jar org.springframework.cloud.sleuth.benchmarks.jmh.* | tee target/benchmarks.log
java -Djmh.ignoreLock=true -jar benchmarks/target/benchmarks.jar org.springframework.cloud.sleuth.benchmarks.jmh.* -rf csv -rff target/jmh-result.csv | tee target/benchmarks.log

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2013-2016 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.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Tells Sleuth that all Sleuth related annotations should be applied
* to an existing span instead of creating a new one.
*
* @author Marcin Grzejszczak
* @since 1.2.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target(value = { ElementType.METHOD, ElementType.TYPE })
public @interface ContinueSpan {
/**
* The value passed to the annotation will be used and the framework
* will create two events with the {@code .start} and {@code .end} suffixes
*/
String log() default "";
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2013-2016 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.annotation;
import java.lang.invoke.MethodHandles;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.util.SpanNameUtil;
import org.springframework.util.StringUtils;
/**
* Default implementation of the {@link SpanCreator} that creates
* a new span around the annotated method.
*
* @author Christian Schwerdtfeger
* @since 1.2.0
*/
class DefaultSpanCreator implements SpanCreator {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private final Tracer tracer;
DefaultSpanCreator(Tracer tracer) {
this.tracer = tracer;
}
@Override public Span createSpan(MethodInvocation pjp, NewSpan newSpanAnnotation) {
String name = StringUtils.isEmpty(newSpanAnnotation.name()) ?
pjp.getMethod().getName() : newSpanAnnotation.name();
String changedName = SpanNameUtil.toLowerHyphen(name);
if (log.isDebugEnabled()) {
log.debug("For the class [" + pjp.getThis().getClass() + "] method "
+ "[" + pjp.getMethod().getName() + "] will name the span [" + changedName + "]");
}
return createSpan(changedName);
}
private Span createSpan(String name) {
if (this.tracer.isTracing()) {
return this.tracer.createSpan(name, this.tracer.getCurrentSpan());
}
return this.tracer.createSpan(name);
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2013-2016 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.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/**
* Allows to create a new span around a public method or a class. The new span
* will be either a child of an existing span if a trace is already in progress
* or a new span will be created if there was no previous trace.
* <p>
* Method parameters can be annotated with {@link SpanTag}, which will end
* in adding the parameter value as a tag value to the span. The tag key will be
* the value of the {@code key} annotation from {@link SpanTag}.
*
*
* @author Christian Schwerdtfeger
* @since 1.2.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target(value = { ElementType.METHOD, ElementType.TYPE })
public @interface NewSpan {
/**
* The name of the span which will be created. Default is the annotated method's name separated by hyphens.
*/
@AliasFor("value")
String name() default "";
/**
* The name of the span which will be created. Default is the annotated method's name separated by hyphens.
*/
@AliasFor("name")
String value() default "";
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2013-2016 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.annotation;
/**
* Does nothing
*
* @author Marcin Grzejszczak
* @since 1.2.0
*/
class NoOpTagValueResolver implements TagValueResolver {
@Override public String resolve(Object parameter) {
return null;
}
}

View File

@@ -0,0 +1,324 @@
/*
* Copyright 2013-2016 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.annotation;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.PostConstruct;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.IntroductionAdvisor;
import org.springframework.aop.IntroductionInterceptor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.DynamicMethodMatcherPointcut;
import org.springframework.aop.support.annotation.AnnotationClassFilter;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.util.ExceptionUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* Custom pointcut advisor that picks all classes / interfaces that
* have the Sleuth related annotations.
*
* @author Marcin Grzejszczak
* @since 1.2.0
*/
class SleuthAdvisorConfig extends AbstractPointcutAdvisor implements
IntroductionAdvisor, BeanFactoryAware {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private Advice advice;
private Pointcut pointcut;
private BeanFactory beanFactory;
@PostConstruct
public void init() {
this.pointcut = buildPointcut();
this.advice = buildAdvice();
if (this.advice instanceof BeanFactoryAware) {
((BeanFactoryAware) this.advice).setBeanFactory(this.beanFactory);
}
}
/**
* Set the {@code BeanFactory} to be used when looking up executors by qualifier.
*/
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public ClassFilter getClassFilter() {
return this.pointcut.getClassFilter();
}
@Override
public Class<?>[] getInterfaces() {
return new Class[] {};
}
@Override
public void validateInterfaces() throws IllegalArgumentException {
}
@Override
public Advice getAdvice() {
return this.advice;
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
private Advice buildAdvice() {
return new SleuthInterceptor();
}
private Pointcut buildPointcut() {
return new AnnotationClassOrMethodOrArgsPointcut();
}
/**
* Checks if a class or a method is is annotated with Sleuth related annotations
*/
private final class AnnotationClassOrMethodOrArgsPointcut extends
DynamicMethodMatcherPointcut {
private final DynamicMethodMatcherPointcut methodResolver;
AnnotationClassOrMethodOrArgsPointcut() {
this.methodResolver = new DynamicMethodMatcherPointcut() {
@Override public boolean matches(Method method, Class<?> targetClass,
Object... args) {
if (SleuthAnnotationUtils.isMethodAnnotated(method)) {
if (log.isDebugEnabled()) {
log.debug("Found a method with Sleuth annotation");
}
return true;
}
if (SleuthAnnotationUtils.hasAnnotatedParams(method, args)) {
if (log.isDebugEnabled()) {
log.debug("Found annotated arguments of the method");
}
return true;
}
return false;
}
};
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return getClassFilter().matches(targetClass) ||
this.methodResolver.matches(method, targetClass, args);
}
@Override public ClassFilter getClassFilter() {
return new ClassFilter() {
@Override public boolean matches(Class<?> clazz) {
return new AnnotationClassOrMethodFilter(NewSpan.class).matches(clazz) ||
new AnnotationClassOrMethodFilter(ContinueSpan.class).matches(clazz);
}
};
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof AnnotationClassOrMethodOrArgsPointcut)) {
return false;
}
AnnotationClassOrMethodOrArgsPointcut otherAdvisor = (AnnotationClassOrMethodOrArgsPointcut) other;
return ObjectUtils.nullSafeEquals(this.methodResolver, otherAdvisor.methodResolver);
}
}
private final class AnnotationClassOrMethodFilter extends AnnotationClassFilter {
private final AnnotationMethodsResolver methodResolver;
AnnotationClassOrMethodFilter(Class<? extends Annotation> annotationType) {
super(annotationType, true);
this.methodResolver = new AnnotationMethodsResolver(annotationType);
}
@Override
public boolean matches(Class<?> clazz) {
return super.matches(clazz) || this.methodResolver.hasAnnotatedMethods(clazz);
}
}
/**
* Checks if a method is properly annotated with a given Sleuth annotation
*/
private static class AnnotationMethodsResolver {
private Class<? extends Annotation> annotationType;
public AnnotationMethodsResolver(Class<? extends Annotation> annotationType) {
this.annotationType = annotationType;
}
public boolean hasAnnotatedMethods(Class<?> clazz) {
final AtomicBoolean found = new AtomicBoolean(false);
ReflectionUtils.doWithMethods(clazz,
new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException,
IllegalAccessException {
if (found.get()) {
return;
}
Annotation annotation = AnnotationUtils.findAnnotation(method,
AnnotationMethodsResolver.this.annotationType);
if (annotation != null) { found.set(true); }
}
});
return found.get();
}
}
}
/**
* Interceptor that creates or continues a span depending on the provided
* annotation. Also it adds logs and tags if necessary.
*/
class SleuthInterceptor implements IntroductionInterceptor, BeanFactoryAware {
private static final Log logger = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private BeanFactory beanFactory;
private SpanCreator spanCreator;
private Tracer tracer;
private SpanTagAnnotationHandler spanTagAnnotationHandler;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
if (method == null) {
return invocation.proceed();
}
Method mostSpecificMethod = AopUtils
.getMostSpecificMethod(method, invocation.getThis().getClass());
NewSpan newSpan = SleuthAnnotationUtils.findAnnotation(mostSpecificMethod, NewSpan.class);
ContinueSpan continueSpan = SleuthAnnotationUtils.findAnnotation(mostSpecificMethod, ContinueSpan.class);
if (newSpan == null && continueSpan == null) {
return invocation.proceed();
}
Span span = tracer().getCurrentSpan();
String log = log(continueSpan);
boolean hasLog = StringUtils.hasText(log);
try {
if (newSpan != null) {
span = spanCreator().createSpan(invocation, newSpan);
}
if (hasLog) {
logEvent(span, log + ".before");
}
spanTagAnnotationHandler().addAnnotatedParameters(invocation);
return invocation.proceed();
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("Exception occurred while trying to continue the pointcut", e);
}
if (hasLog) {
logEvent(span, log + ".afterFailure");
}
tracer().addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(e));
throw e;
} finally {
if (span != null) {
if (hasLog) {
logEvent(span, log + ".after");
}
if (newSpan != null) {
tracer().close(span);
}
}
}
}
private void logEvent(Span span, String name) {
if (span == null) {
logger.warn("You were trying to continue a span which was null. Please "
+ "remember that if two proxied methods are calling each other from "
+ "the same class then the aspect will not be properly resolved");
return;
}
span.logEvent(name);
}
private String log(ContinueSpan continueSpan) {
if (continueSpan != null) {
return continueSpan.log();
}
return "";
}
private Tracer tracer() {
if (this.tracer == null) {
this.tracer = this.beanFactory.getBean(Tracer.class);
}
return this.tracer;
}
private SpanCreator spanCreator() {
if (this.spanCreator == null) {
this.spanCreator = this.beanFactory.getBean(SpanCreator.class);
}
return this.spanCreator;
}
private SpanTagAnnotationHandler spanTagAnnotationHandler() {
if (this.spanTagAnnotationHandler == null) {
this.spanTagAnnotationHandler = new SpanTagAnnotationHandler(this.beanFactory);
}
return this.spanTagAnnotationHandler;
}
@Override public boolean implementsInterface(Class<?> intf) {
return true;
}
@Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2013-2016 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.annotation;
/**
* A container class that holds information about the parameter
* of the annotated method argument.
*
* @author Christian Schwerdtfeger
* @since 1.2.0
*/
class SleuthAnnotatedParameter {
int parameterIndex;
SpanTag annotation;
Object argument;
SleuthAnnotatedParameter(int parameterIndex, SpanTag annotation,
Object argument) {
this.parameterIndex = parameterIndex;
this.annotation = annotation;
this.argument = argument;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2013-2016 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.annotation;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} that allows creating spans by means of a
* {@link NewSpan} annotation. You can annotate classes or just methods.
* You can also apply this annotation to an interface.
*
* @author Christian Schwerdtfeger
* @author Marcin Grzejszczak
* @since 1.2.0
*/
@Configuration
@ConditionalOnBean(Tracer.class)
@ConditionalOnProperty(name = "spring.sleuth.annotation.enabled", matchIfMissing = true)
@AutoConfigureAfter(TraceAutoConfiguration.class)
@EnableConfigurationProperties(SleuthAnnotationProperties.class)
public class SleuthAnnotationAutoConfiguration {
@Bean
@ConditionalOnMissingBean
SpanCreator spanCreator(Tracer tracer) {
return new DefaultSpanCreator(tracer);
}
@Bean
@ConditionalOnMissingBean
TagValueExpressionResolver spelTagValueExpressionResolver() {
return new SpelTagValueExpressionResolver();
}
@Bean
@ConditionalOnMissingBean
TagValueResolver noOpTagValueResolver() {
return new NoOpTagValueResolver();
}
@Bean
SleuthAdvisorConfig sleuthAdvisorConfig() {
return new SleuthAdvisorConfig();
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2013-2016 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.annotation;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Sleuth annotation settings
*
* @author Marcin Grzejszczak
* @since 1.2.0
*/
@ConfigurationProperties("spring.sleuth.annotation")
public class SleuthAnnotationProperties {
private boolean enabled = true;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2013-2016 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.annotation;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationUtils;
/**
* Utility class that can verify whether the method is annotated with
* the Sleuth annotations.
*
* @author Christian Schwerdtfeger
* @since 1.2.0
*/
class SleuthAnnotationUtils {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
static boolean isMethodAnnotated(Method method) {
return findAnnotation(method, NewSpan.class) != null ||
findAnnotation(method, ContinueSpan.class) != null;
}
static boolean hasAnnotatedParams(Method method, Object[] args) {
return !findAnnotatedParameters(method, args).isEmpty();
}
static List<SleuthAnnotatedParameter> findAnnotatedParameters(Method method, Object[] args) {
Annotation[][] parameters = method.getParameterAnnotations();
List<SleuthAnnotatedParameter> result = new ArrayList<>();
int i = 0;
for (Annotation[] parameter : parameters) {
for (Annotation parameter2 : parameter) {
if (parameter2 instanceof SpanTag) {
result.add(new SleuthAnnotatedParameter(i, (SpanTag) parameter2, args[i]));
}
}
i++;
}
return result;
}
/**
* Searches for an annotation either on a method or inside the method parameters
*/
static <T extends Annotation> T findAnnotation(Method method, Class<T> clazz) {
T annotation = AnnotationUtils.findAnnotation(method, clazz);
if (annotation == null) {
try {
annotation = AnnotationUtils.findAnnotation(
method.getDeclaringClass().getMethod(method.getName(),
method.getParameterTypes()), clazz);
} catch (NoSuchMethodException | SecurityException e) {
if (log.isDebugEnabled()) {
log.debug("Exception occurred while tyring to find the annotation", e);
}
}
}
return annotation;
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2013-2016 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.annotation;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.cloud.sleuth.Span;
/**
* A contract for creating a new span for a given join point
* and the {@link NewSpan} annotation.
*
* @author Christian Schwerdtfeger
* @since 1.2.0
*/
public interface SpanCreator {
/**
* Returns a new {@link Span} for the join point and {@link NewSpan}
*/
Span createSpan(MethodInvocation methodInvocation, NewSpan newSpan);
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2013-2016 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.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/**
* There are 3 different ways to add tags to a span. All of them are controlled by the annotation values.
* Precedence is:
*
* <ul>
* <li>try with the {@link TagValueResolver} bean</li>
* <li>if the value of the bean wasn't set, try to evaluate a SPEL expression</li>
* <li>if theres no SPEL expression just return a {@code toString()} value of the parameter</li>
* </ul>
*
* @author Christian Schwerdtfeger
* @since 1.2.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target(value = { ElementType.PARAMETER })
public @interface SpanTag {
/**
* The name of the key of the tag which should be created.
*/
@AliasFor("key")
String value() default "";
/**
* The name of the key of the tag which should be created.
*/
@AliasFor("value")
String key() default "";
/**
* Execute this SPEL expression to calculate the tag value. Will be analyzed if no value of the
* {@link SpanTag#resolver()} was set.
*/
String expression() default "";
/**
* Use this bean to resolve the tag value. Has the highest precedence.
*/
Class<? extends TagValueResolver> resolver() default NoOpTagValueResolver.class;
}

View File

@@ -0,0 +1,149 @@
/*
* Copyright 2013-2016 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.annotation;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.util.StringUtils;
/**
* This class is able to find all methods annotated with the
* Sleuth annotations. All methods mean that if you have both an interface
* and an implementation annotated with Sleuth annotations then this class is capable
* of finding both of them and merging into one set of tracing information.
*
* This information is then used to add proper tags to the span from the
* method arguments that are annotated with {@link SpanTag}.
*
* @author Christian Schwerdtfeger
* @since 1.2.0
*/
class SpanTagAnnotationHandler {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private final BeanFactory beanFactory;
private Tracer tracer;
SpanTagAnnotationHandler(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
void addAnnotatedParameters(MethodInvocation pjp) {
try {
Method method = pjp.getMethod();
Method mostSpecificMethod = AopUtils.getMostSpecificMethod(method,
pjp.getThis().getClass());
List<SleuthAnnotatedParameter> annotatedParameters =
SleuthAnnotationUtils.findAnnotatedParameters(mostSpecificMethod, pjp.getArguments());
getAnnotationsFromInterfaces(pjp, mostSpecificMethod, annotatedParameters);
mergeAnnotatedMethodsIfNecessary(pjp, method, mostSpecificMethod,
annotatedParameters);
addAnnotatedArguments(annotatedParameters);
} catch (SecurityException e) {
log.error("Exception occurred while trying to add annotated parameters", e);
}
}
private void getAnnotationsFromInterfaces(MethodInvocation pjp,
Method mostSpecificMethod,
List<SleuthAnnotatedParameter> annotatedParameters) {
Class<?>[] implementedInterfaces = pjp.getThis().getClass().getInterfaces();
if (implementedInterfaces.length > 0) {
for (Class<?> implementedInterface : implementedInterfaces) {
for (Method methodFromInterface : implementedInterface.getMethods()) {
if (methodsAreTheSame(mostSpecificMethod, methodFromInterface)) {
List<SleuthAnnotatedParameter> annotatedParametersForActualMethod =
SleuthAnnotationUtils.findAnnotatedParameters(methodFromInterface, pjp.getArguments());
mergeAnnotatedParameters(annotatedParameters, annotatedParametersForActualMethod);
}
}
}
}
}
private boolean methodsAreTheSame(Method mostSpecificMethod, Method method1) {
return method1.getName().equals(mostSpecificMethod.getName()) &&
Arrays.equals(method1.getParameterTypes(), mostSpecificMethod.getParameterTypes());
}
private void mergeAnnotatedMethodsIfNecessary(MethodInvocation pjp, Method method,
Method mostSpecificMethod, List<SleuthAnnotatedParameter> annotatedParameters) {
// that can happen if we have an abstraction and a concrete class that is
// annotated with @NewSpan annotation
if (!method.equals(mostSpecificMethod)) {
List<SleuthAnnotatedParameter> annotatedParametersForActualMethod = SleuthAnnotationUtils.findAnnotatedParameters(
method, pjp.getArguments());
mergeAnnotatedParameters(annotatedParameters, annotatedParametersForActualMethod);
}
}
private void mergeAnnotatedParameters(List<SleuthAnnotatedParameter> annotatedParametersIndices,
List<SleuthAnnotatedParameter> annotatedParametersIndicesForActualMethod) {
for (SleuthAnnotatedParameter container : annotatedParametersIndicesForActualMethod) {
final int index = container.parameterIndex;
boolean parameterContained = false;
for (SleuthAnnotatedParameter parameterContainer : annotatedParametersIndices) {
if (parameterContainer.parameterIndex == index) {
parameterContained = true;
break;
}
}
if (!parameterContained) {
annotatedParametersIndices.add(container);
}
}
}
private void addAnnotatedArguments(List<SleuthAnnotatedParameter> toBeAdded) {
for (SleuthAnnotatedParameter container : toBeAdded) {
String tagValue = resolveTagValue(container.annotation, container.argument);
tracer().addTag(container.annotation.value(), tagValue);
}
}
String resolveTagValue(SpanTag annotation, Object argument) {
if (argument == null) {
return "";
}
if (annotation.resolver() != NoOpTagValueResolver.class) {
TagValueResolver tagValueResolver = this.beanFactory.getBean(annotation.resolver());
return tagValueResolver.resolve(argument);
} else if (StringUtils.hasText(annotation.expression())) {
return this.beanFactory.getBean(TagValueExpressionResolver.class)
.resolve(annotation.expression(), argument);
}
return argument.toString();
}
private Tracer tracer() {
if (this.tracer == null) {
this.tracer = this.beanFactory.getBean(Tracer.class);
}
return this.tracer;
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2013-2016 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.annotation;
import java.lang.invoke.MethodHandles;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
/**
* Uses SPEL to evaluate the expression. If an exception is thrown will return
* the {@code toString()} of the parameter.
*
* @author Marcin Grzejszczak
* @since 1.2.0
*/
class SpelTagValueExpressionResolver implements TagValueExpressionResolver {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
@Override
public String resolve(String expression, Object parameter) {
try {
ExpressionParser expressionParser = new SpelExpressionParser();
Expression expressionToEvaluate = expressionParser.parseExpression(expression);
return expressionToEvaluate.getValue(parameter, String.class);
} catch (Exception e) {
log.error("Exception occurred while tying to evaluate the SPEL expression [" + expression + "]", e);
}
return parameter.toString();
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2013-2016 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.annotation;
/**
* Resolves the tag value for the given parameter and the provided expression.
*
* @author Marcin Grzejszczak
* @since 1.2.0
*/
public interface TagValueExpressionResolver {
/**
* Returns the tag value for the given parameter and the provided expression
*
* @param expression - the expression coming from {@link SpanTag#expression()}
* @param parameter - parameter annotated with {@link SpanTag}
* @return the value of the tag
*/
String resolve(String expression, Object parameter);
}

View File

@@ -0,0 +1,19 @@
package org.springframework.cloud.sleuth.annotation;
/**
* Resolves the tag value for the given parameter.
*
* @author Christian Schwerdtfeger
* @since 1.2.0
*/
public interface TagValueResolver {
/**
* Returns the tag value for the given parameter
*
* @param parameter - parameter annotated with {@link SpanTag}
* @return the value of the tag
*/
String resolve(Object parameter);
}

View File

@@ -16,7 +16,8 @@ org.springframework.cloud.sleuth.instrument.web.client.TraceWebClientAutoConfigu
org.springframework.cloud.sleuth.instrument.web.client.TraceWebAsyncClientAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.web.client.feign.TraceFeignClientAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.zuul.TraceZuulAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.rxjava.RxJavaAutoConfiguration
org.springframework.cloud.sleuth.instrument.rxjava.RxJavaAutoConfiguration,\
org.springframework.cloud.sleuth.annotation.SleuthAnnotationAutoConfiguration
# Environment Post Processor
org.springframework.boot.env.EnvironmentPostProcessor=\

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2013-2016 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.annotation;
import org.junit.Test;
import static org.assertj.core.api.BDDAssertions.then;
/**
* @author Marcin Grzejszczak
*/
public class NoOpTagValueResolverTests {
@Test public void should_return_null() throws Exception {
then(new NoOpTagValueResolver().resolve("")).isNull();
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2013-2016 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.annotation;
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.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SleuthAnnotationAutoConfiguration.class,
properties = "spring.sleuth.annotation.enabled=false")
public class SleuthSpanCreatorAnnotationDisableTests {
@Autowired(required = false) SpanCreator spanCreator;
@Test
public void shouldNotAutowireBecauseConfigIsDisabled() {
assertThat(this.spanCreator).isNull();
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2013-2016 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.annotation;
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.Tracer;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SleuthAnnotationAutoConfiguration.class,
properties = "spring.sleuth.enabled=false")
public class SleuthSpanCreatorAnnotationNoSleuthTests {
@Autowired(required = false) SpanCreator spanCreator;
@Autowired(required = false) Tracer tracer;
@Test
public void shouldNotAutowireBecauseConfigIsDisabled() {
assertThat(this.spanCreator).isNull();
assertThat(this.tracer).isNull();
}
}

View File

@@ -0,0 +1,156 @@
/*
* Copyright 2013-2016 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.annotation;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanReporter;
import org.springframework.cloud.sleuth.annotation.SleuthSpanCreatorAspectNegativeTests.TestConfiguration;
import org.springframework.cloud.sleuth.assertions.ListOfSpans;
import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator;
import org.springframework.cloud.sleuth.util.ExceptionUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit4.SpringRunner;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestConfiguration.class)
public class SleuthSpanCreatorAspectNegativeTests {
@Autowired NotAnnotatedTestBeanInterface testBean;
@Autowired TestBeanInterface annotatedTestBean;
@Autowired ArrayListSpanAccumulator accumulator;
@Before
public void setup() {
ExceptionUtils.setFail(true);
this.accumulator.clear();
}
@Test
public void shouldNotCallAdviceForNotAnnotatedBean() {
this.testBean.testMethod();
then(this.accumulator.getSpans()).isEmpty();
then(ExceptionUtils.getLastException()).isNull();
}
@Test
public void shouldCallAdviceForAnnotatedBean() throws Throwable {
this.annotatedTestBean.testMethod();
List<Span> spans = new ArrayList<>(this.accumulator.getSpans());
then(new ListOfSpans(spans)).hasSize(1).hasASpanWithName("test-method");
then(ExceptionUtils.getLastException()).isNull();
}
protected interface NotAnnotatedTestBeanInterface {
void testMethod();
}
protected static class NotAnnotatedTestBean implements NotAnnotatedTestBeanInterface {
@Override
public void testMethod() {
}
}
protected interface TestBeanInterface {
@NewSpan
void testMethod();
void testMethod2();
void testMethod3();
@NewSpan(name = "testMethod4")
void testMethod4();
@NewSpan(name = "testMethod5")
void testMethod5(@SpanTag("testTag") String test);
void testMethod6(String test);
void testMethod7();
}
protected static class TestBean implements TestBeanInterface {
@Override
public void testMethod() {
}
@NewSpan
@Override
public void testMethod2() {
}
@NewSpan(name = "testMethod3")
@Override
public void testMethod3() {
}
@Override
public void testMethod4() {
}
@Override
public void testMethod5(String test) {
}
@NewSpan(name = "testMethod6")
@Override
public void testMethod6(@SpanTag("testTag6") String test) {
}
@Override
public void testMethod7() {
}
}
@Configuration
@EnableAutoConfiguration
protected static class TestConfiguration {
@Bean SpanReporter spanReporter() {
return new ArrayListSpanAccumulator();
}
@Bean
public NotAnnotatedTestBeanInterface testBean() {
return new NotAnnotatedTestBean();
}
@Bean
public TestBeanInterface annotatedTestBean() {
return new TestBean();
}
}
}

View File

@@ -0,0 +1,347 @@
/*
* Copyright 2013-2016 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.annotation;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanReporter;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.annotation.SleuthSpanCreatorAspectTests.TestConfiguration;
import org.springframework.cloud.sleuth.assertions.ListOfSpans;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator;
import org.springframework.cloud.sleuth.util.ExceptionUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
@SpringBootTest(classes = TestConfiguration.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class SleuthSpanCreatorAspectTests {
@Autowired TestBeanInterface testBean;
@Autowired Tracer tracer;
@Autowired ArrayListSpanAccumulator accumulator;
@Before
public void setup() {
ExceptionUtils.setFail(true);
this.accumulator.clear();
}
@Test
public void shouldCreateSpanWhenAnnotationOnInterfaceMethod() {
this.testBean.testMethod();
List<Span> spans = new ArrayList<>(this.accumulator.getSpans());
then(new ListOfSpans(spans)).hasSize(1).hasASpanWithName("test-method");
then(ExceptionUtils.getLastException()).isNull();
}
@Test
public void shouldCreateSpanWhenAnnotationOnClassMethod() {
this.testBean.testMethod2();
List<Span> spans = new ArrayList<>(this.accumulator.getSpans());
then(new ListOfSpans(spans)).hasSize(1).hasASpanWithName("test-method2");
then(ExceptionUtils.getLastException()).isNull();
}
@Test
public void shouldCreateSpanWithCustomNameWhenAnnotationOnClassMethod() {
this.testBean.testMethod3();
List<Span> spans = new ArrayList<>(this.accumulator.getSpans());
then(new ListOfSpans(spans)).hasSize(1).hasASpanWithName("custom-name-on-test-method3");
then(ExceptionUtils.getLastException()).isNull();
}
@Test
public void shouldCreateSpanWithCustomNameWhenAnnotationOnInterfaceMethod() {
this.testBean.testMethod4();
List<Span> spans = new ArrayList<>(this.accumulator.getSpans());
then(new ListOfSpans(spans)).hasSize(1).hasASpanWithName("custom-name-on-test-method4");
then(ExceptionUtils.getLastException()).isNull();
}
@Test
public void shouldCreateSpanWithTagWhenAnnotationOnInterfaceMethod() {
// tag::execution[]
this.testBean.testMethod5("test");
// end::execution[]
List<Span> spans = new ArrayList<>(this.accumulator.getSpans());
then(new ListOfSpans(spans)).hasSize(1)
.hasASpanWithName("custom-name-on-test-method5")
.hasASpanWithTagEqualTo("testTag", "test");
then(ExceptionUtils.getLastException()).isNull();
}
@Test
public void shouldCreateSpanWithTagWhenAnnotationOnClassMethod() {
this.testBean.testMethod6("test");
List<Span> spans = new ArrayList<>(this.accumulator.getSpans());
then(new ListOfSpans(spans)).hasSize(1)
.hasASpanWithName("custom-name-on-test-method6")
.hasASpanWithTagEqualTo("testTag6", "test");
then(ExceptionUtils.getLastException()).isNull();
}
@Test
public void shouldCreateSpanWithLogWhenAnnotationOnInterfaceMethod() {
this.testBean.testMethod8("test");
List<Span> spans = new ArrayList<>(this.accumulator.getSpans());
then(new ListOfSpans(spans)).hasSize(1)
.hasASpanWithName("custom-name-on-test-method8");
then(ExceptionUtils.getLastException()).isNull();
}
@Test
public void shouldCreateSpanWithLogWhenAnnotationOnClassMethod() {
this.testBean.testMethod9("test");
List<Span> spans = new ArrayList<>(this.accumulator.getSpans());
then(new ListOfSpans(spans)).hasSize(1)
.hasASpanWithName("custom-name-on-test-method9");
then(ExceptionUtils.getLastException()).isNull();
}
@Test
public void shouldContinueSpanWithLogWhenAnnotationOnInterfaceMethod() {
Span span = this.tracer.createSpan("foo");
this.testBean.testMethod10("test");
this.tracer.close(span);
List<Span> spans = new ArrayList<>(this.accumulator.getSpans());
then(new ListOfSpans(spans)).hasSize(1)
.hasASpanWithName("foo")
.hasASpanWithTagEqualTo("customTestTag10", "test")
.hasASpanWithLogEqualTo("customTest.before")
.hasASpanWithLogEqualTo("customTest.after");
then(ExceptionUtils.getLastException()).isNull();
}
@Test
public void shouldContinueSpanWithLogWhenAnnotationOnClassMethod() {
Span span = this.tracer.createSpan("foo");
// tag::continue_span_execution[]
this.testBean.testMethod11("test");
// end::continue_span_execution[]
this.tracer.close(span);
List<Span> spans = new ArrayList<>(this.accumulator.getSpans());
then(new ListOfSpans(spans)).hasSize(1)
.hasASpanWithName("foo")
.hasASpanWithTagEqualTo("customTestTag11", "test")
.hasASpanWithLogEqualTo("customTest.before")
.hasASpanWithLogEqualTo("customTest.after");
then(ExceptionUtils.getLastException()).isNull();
}
@Test
public void shouldAddErrorTagWhenExceptionOccurredInNewSpan() {
try {
this.testBean.testMethod12("test");
} catch (RuntimeException ignored) {
}
List<Span> spans = new ArrayList<>(this.accumulator.getSpans());
then(new ListOfSpans(spans)).hasSize(1)
.hasASpanWithName("test-method12")
.hasASpanWithTagEqualTo("testTag12", "test")
.hasASpanWithTagEqualTo("error", "test exception 12");
then(ExceptionUtils.getLastException()).isNull();
}
@Test
public void shouldAddErrorTagWhenExceptionOccurredInContinueSpan() {
Span span = this.tracer.createSpan("foo");
try {
this.testBean.testMethod13();
} catch (RuntimeException ignored) {
}
finally {
this.tracer.close(span);
}
List<Span> spans = new ArrayList<>(this.accumulator.getSpans());
then(new ListOfSpans(spans)).hasSize(1)
.hasASpanWithName("foo")
.hasASpanWithTagEqualTo("error", "test exception 13")
.hasASpanWithLogEqualTo("testMethod13.before")
.hasASpanWithLogEqualTo("testMethod13.afterFailure")
.hasASpanWithLogEqualTo("testMethod13.after");
then(ExceptionUtils.getLastException()).isNull();
}
@Test
public void shouldNotCreateSpanWhenNotAnnotated() {
this.testBean.testMethod7();
List<Span> spans = new ArrayList<>(this.accumulator.getSpans());
then(spans).isEmpty();
}
protected interface TestBeanInterface {
// tag::annotated_method[]
@NewSpan
void testMethod();
// end::annotated_method[]
void testMethod2();
@NewSpan(name = "interfaceCustomNameOnTestMethod3")
void testMethod3();
// tag::custom_name_on_annotated_method[]
@NewSpan("customNameOnTestMethod4")
void testMethod4();
// end::custom_name_on_annotated_method[]
// tag::custom_name_and_tag_on_annotated_method[]
@NewSpan(name = "customNameOnTestMethod5")
void testMethod5(@SpanTag("testTag") String param);
// end::custom_name_and_tag_on_annotated_method[]
void testMethod6(String test);
void testMethod7();
@NewSpan(name = "customNameOnTestMethod8")
void testMethod8(String param);
@NewSpan(name = "testMethod9")
void testMethod9(String param);
@ContinueSpan(log = "customTest")
void testMethod10(@SpanTag("testTag10") String param);
// tag::continue_span[]
@ContinueSpan(log = "testMethod11")
void testMethod11(@SpanTag("testTag11") String param);
// end::continue_span[]
@NewSpan
void testMethod12(@SpanTag("testTag12") String param);
@ContinueSpan(log = "testMethod13")
void testMethod13();
}
protected static class TestBean implements TestBeanInterface {
@Override
public void testMethod() {
}
@NewSpan
@Override
public void testMethod2() {
}
// tag::name_on_implementation[]
@NewSpan(name = "customNameOnTestMethod3")
@Override
public void testMethod3() {
}
// end::name_on_implementation[]
@Override
public void testMethod4() {
}
@Override
public void testMethod5(String test) {
}
@NewSpan(name = "customNameOnTestMethod6")
@Override
public void testMethod6(@SpanTag("testTag6") String test) {
}
@Override
public void testMethod7() {
}
@Override
public void testMethod8(String param) {
}
@NewSpan(name = "customNameOnTestMethod9")
@Override
public void testMethod9(String param) {
}
@Override
public void testMethod10(@SpanTag("customTestTag10") String param) {
}
@ContinueSpan(log = "customTest")
@Override
public void testMethod11(@SpanTag("customTestTag11") String param) {
}
@Override
public void testMethod12(String param) {
throw new RuntimeException("test exception 12");
}
@Override
public void testMethod13() {
throw new RuntimeException("test exception 13");
}
}
@Configuration
@EnableAutoConfiguration
protected static class TestConfiguration {
@Bean
public TestBeanInterface testBean() {
return new TestBean();
}
@Bean SpanReporter spanReporter() {
return new ArrayListSpanAccumulator();
}
@Bean AlwaysSampler alwaysSampler() {
return new AlwaysSampler();
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2013-2016 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.annotation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.sleuth.SpanReporter;
import org.springframework.cloud.sleuth.annotation.SleuthSpanCreatorCircularDependencyTests.TestConfiguration;
import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest(classes = TestConfiguration.class)
@RunWith(SpringRunner.class)
public class SleuthSpanCreatorCircularDependencyTests {
@Test public void contextLoads() throws Exception {
}
private static class Service1 {
@Autowired private Service2 service2;
@NewSpan public void foo() {
}
}
private static class Service2 {
@Autowired private Service1 service1;
@NewSpan public void bar() {
}
}
@Configuration @EnableAutoConfiguration
protected static class TestConfiguration {
@Bean SpanReporter spanReporter() {
return new ArrayListSpanAccumulator();
}
@Bean public Service1 service1() {
return new Service1();
}
@Bean public Service2 service2() {
return new Service2();
}
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2013-2016 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.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.sleuth.annotation.SpanTagAnnotationHandlerTests.TestConfiguration;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.cloud.sleuth.util.ExceptionUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
@SpringBootTest(classes = TestConfiguration.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class SpanTagAnnotationHandlerTests {
@Autowired BeanFactory beanFactory;
@Autowired TagValueResolver tagValueResolver;
SpanTagAnnotationHandler handler;
@Before
public void setup() {
ExceptionUtils.setFail(true);
this.handler = new SpanTagAnnotationHandler(this.beanFactory);
}
@Test
public void shouldUseCustomTagValueResolver() throws NoSuchMethodException, SecurityException {
Method method = AnnotationMockClass.class.getMethod("getAnnotationForTagValueResolver", String.class);
Annotation annotation = method.getParameterAnnotations()[0][0];
if (annotation instanceof SpanTag) {
String resolvedValue = handler.resolveTagValue((SpanTag) annotation, "test");
assertThat(resolvedValue).isEqualTo("Value from myCustomTagValueResolver");
} else {
fail("Annotation was not SleuthSpanTag");
}
}
@Test
public void shouldUseTagValueExpression() throws NoSuchMethodException, SecurityException {
Method method = AnnotationMockClass.class.getMethod("getAnnotationForTagValueExpression", String.class);
Annotation annotation = method.getParameterAnnotations()[0][0];
if (annotation instanceof SpanTag) {
String resolvedValue = handler.resolveTagValue((SpanTag) annotation, "test");
assertThat(resolvedValue).isEqualTo("4 characters");
} else {
fail("Annotation was not SleuthSpanTag");
}
}
@Test
public void shouldReturnArgumentToString() throws NoSuchMethodException, SecurityException {
Method method = AnnotationMockClass.class.getMethod("getAnnotationForArgumentToString", Long.class);
Annotation annotation = method.getParameterAnnotations()[0][0];
if (annotation instanceof SpanTag) {
String resolvedValue = handler.resolveTagValue((SpanTag) annotation, 15);
assertThat(resolvedValue).isEqualTo("15");
} else {
fail("Annotation was not SleuthSpanTag");
}
}
protected class AnnotationMockClass {
// tag::resolver_bean[]
@NewSpan
public void getAnnotationForTagValueResolver(@SpanTag(key = "test", resolver = TagValueResolver.class) String test) {
}
// end::resolver_bean[]
// tag::spel[]
@NewSpan
public void getAnnotationForTagValueExpression(@SpanTag(key = "test", expression = "length() + ' characters'") String test) {
}
// end::spel[]
// tag::toString[]
@NewSpan
public void getAnnotationForArgumentToString(@SpanTag("test") Long param) {
}
// end::toString[]
}
@Configuration
@EnableAutoConfiguration
protected static class TestConfiguration {
// tag::custom_resolver[]
@Bean(name = "myCustomTagValueResolver")
public TagValueResolver tagValueResolver() {
return parameter -> "Value from myCustomTagValueResolver";
}
// end::custom_resolver[]
@Bean AlwaysSampler alwaysSampler() {
return new AlwaysSampler();
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2013-2016 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.annotation;
import org.junit.Test;
import static org.assertj.core.api.BDDAssertions.then;
/**
* @author Marcin Grzejszczak
*/
public class SpelTagValueExpressionResolverTests {
@Test
public void should_use_spel_to_resolve_a_value() throws Exception {
SpelTagValueExpressionResolver resolver = new SpelTagValueExpressionResolver();
String resolved = resolver.resolve("length() + 1", "foo");
then(resolved).isEqualTo("4");
}
@Test
public void should_use_to_string_if_expression_is_not_analyzed_properly() throws Exception {
SpelTagValueExpressionResolver resolver = new SpelTagValueExpressionResolver();
String resolved = resolver.resolve("invalid() structure + 1", new Foo());
then(resolved).isEqualTo("BAR");
}
}
class Foo {
@Override public String toString() {
return "BAR";
}
}

View File

@@ -196,6 +196,25 @@ public class ListOfSpansAssert extends AbstractAssert<ListOfSpansAssert, ListOfS
return this;
}
public ListOfSpansAssert hasASpanWithLogEqualTo(String logName) {
isNotNull();
printSpans();
boolean found = false;
for (Span span : this.actual.spans) {
try {
SleuthAssertions.assertThat(span).hasLoggedAnEvent(logName);
found = true;
break;
} catch (AssertionError e) {}
}
if (!found) {
failWithMessage("Expected spans \n <%s> \nto contain at least one span with log name "
+ "equal to <%s>.\n\n", spansToString(), logName);
}
return this;
}
private String spansToString() {
return this.actual.spans.stream().map(span -> "\nSPAN: " + span.toString() + " with name [" + span.getName() + "] " +
"\nwith tags " + span.tags() + "\nwith logs " + span.logs() +