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:
committed by
GitHub
parent
b7659ede11
commit
0f29735c11
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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 "";
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 "";
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 there’s 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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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=\
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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() +
|
||||
|
||||
Reference in New Issue
Block a user