60. 使用注解管理 Span

你可以使用各种注解来管理 span。

60.1 理论基础

有很多很好的理由来管理带有注解的 span,包括:

  • API 不可知意味着与一个 span 协作。使用注解可以让用户添加到不依赖于 span api 的 span 中。这样做可以让 Sleuth 更改其核心 API,以减少对用户代码的影响。
  • 基本 span 操作的缩小接触面积。如果没有这个特性,你必须使用 span api,它有可能被错误使用的生命周期命令。只通过公开范围、标记和日志功能,可以在不意外破坏 span 生命周期的情况下进行协作。
  • 与运行时生成的代码协作。通过使用 Spring Data 和 Feign 等库,可以在运行时生成接口的实现。因此,物体的 span 包装是乏味的。现在,可以在接口和这些接口的参数上提供注解。

60.2 创建新 Span

如果不想手动创建本地 span,可以使用 @NewSpan 注解。此外,我们还提供了 @SpanTag 注解以自动方式添加标记。

现在我们可以考虑一些用法示例。

@NewSpan
void testMethod();

在没有任何参数的情况下注解该方法会导致创建一个新的 span,其名称等于注解的方法名称。

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

如果在注解中提供值(直接或通过设置 name 参数),则创建的 span 将提供的值作为名称。

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

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

你可以将名称和标记组合在一起。让我们集中讨论后者。在这种情况下,注解方法的参数运行时值的值将成为标记的值。在我们的示例中,标记键是 testTag,标记值是 test。

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

你可以在类和接口上放置 @NewSpan 注解。如果重写接口的方法并为 @NewSpan 注解提供不同的值,则最具体的方法会获胜(在这种情况下,会设置 customNameOnTestMethod3)。

60.3 继续使用 Span

如果要将标记和注解添加到现有 span,可以使用 @ContinueSpan 注解,如下例所示:

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

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

(请注意,与 @NewSpan 注解不同,你还可以使用 log 参数添加日志。)

这样,span 就可以继续,并且:

  • 创建名为 testMethod11.before 和 testMethod11.after 的日志项。
  • 如果引发异常,还将创建名为 testMethod11.afterFailure 的日志条目。
  • 将创建一个键为 testTag11 且值为 test 的标记。

60.4 高级标记设置

有三种不同的方法可以将标记添加到 span 中。所有这些都由 SpanTag 注解控制。优先顺序如下:

  1. 尝试使用 TagValueResolver 类型的 bean 和提供的名称。
  2. 如果没有提供 bean 名称,请尝试计算表达式。我们搜索 TagValueExpressionResolver bean。默认实现使用 SPEL 表达式解析。重要信息:只能引用 SPEL 表达式中的属性。由于安全约束,不允许执行方法。
  3. 如果找不到任何要计算的表达式,请返回参数的 toString() 值。

60.4.1 自定义提取程序

以下方法的标记值由 TagValueResolver 接口的实现计算。它的类名必须作为 resolver 属性的值传递。

考虑以下带注解的方法:

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

现在进一步考虑以下 TagValueResolver bean 实现:

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

前面的两个示例导致将标记值设置为 myCustomTagValueResolver 中的值。

60.4.2 解析值的表达式

考虑以下带注解的方法:

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

TagValueExpressionResolver 的任何自定义实现都不会导致对 SPEL 表达式的评估,并且在 span 上设置了一个值为 4 个字符的标记。如果要使用其他表达式解析机制,可以创建自己的 bean 实现。

60.4.3 使用 toString() 方法

考虑以下带注解的方法:

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

以 15 的值运行前面的方法将导致设置字符串值为 "15" 的标记。