53. Managing spans with annotations

53.1 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

53.2 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.

@NewSpan
void testMethod();

Annotating the method without any parameter will lead to a creation of a new span whose name will be equal to annotated method name.

@NewSpan("customNameOnTestMethod4")
void testMethod4();

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.

// method declaration
@NewSpan(name = "customNameOnTestMethod5")
void testMethod5(@SpanTag("testTag") String param);

// and method execution
this.testBean.testMethod5("test");

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 .

@NewSpan(name = "customNameOnTestMethod3")
@Override
public void testMethod3() {
}

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).

53.3 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:

// method declaration
@ContinueSpan(log = "testMethod11")
void testMethod11(@SpanTag("testTag11") String param);

// method execution
this.testBean.testMethod11("test");

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

53.4 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 we do not find any expression to evaluate, return the toString() value of the parameter. IMPORTANT You can only reference properties from the SPEL expression. Method execution is not allowed due to security constraints.

  • if one hasn’t provided any expression to evaluate just return a toString() value of the parameter

53.4.1 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:

@NewSpan
public void getAnnotationForTagValueResolver(@SpanTag(key = "test", resolver = TagValueResolver.class) String test) {
}

and such a TagValueResolver bean implementation

@Bean(name = "myCustomTagValueResolver")
public TagValueResolver tagValueResolver() {
	return parameter -> "Value from myCustomTagValueResolver";
}

Will lead to setting of a tag value equal to Value from myCustomTagValueResolver .

53.4.2 Resolving expressions for value

Having such an annotated method:

@NewSpan
public void getAnnotationForTagValueExpression(@SpanTag(key = "test", expression = "'hello' + ' characters'") String test) {
}

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.

53.4.3 Using toString method

Having such an annotated method:

@NewSpan
public void getAnnotationForArgumentToString(@SpanTag("test") Long param) {
}

if executed with a value of 15 will lead to setting of a tag with a String value of "15" .