- 使用
Instrumenter
API- 使用
shouldStart()
来检查当前操作是否生成了任何遥测数据。 - 开始使用
start()
启动检测操作 - 使用
end()
结束检测操作 - 使用
InstrumenterBuilder
创建一个新的Instrumenter
- 使用
SpanNameExtractor
命名span - 使用
AttributesExtractor
给span和metric数据添加属性 - 使用
SpanStatusExtractor
设置span状态 - 使用
SpanLinksExtractor
添加span 关联 - 使用
ErrorCauseExtractor
丢弃包装异常类型 - 使用
TimeExtractor
自定义操作开始时间和结束时间 - 通过实现
RequestMetrics
和RequestListener
注册 metrics - 使用
ContextCustomizer
对Context
提供丰富的操作 - 禁用检测
- 最后,通过
SpanKindExtractor
设置span类型并获取一个新的Instrumenter
!
- 使用
- 使用
使用 Instrumenter
API
Instrumenter
封装用于收集遥测,从收集的数据,以起始和结束跨度,使用度量仪器记录的值的整个逻辑。Instrumenter
公共API只包含三个方法:shouldStart()
,start()
和end()
。该类旨在装饰检测库代码的实际调用;shouldStart()
和start()
方法在请求处理开始的时候调用。end()
方法必须在请求处理结束和响应到达或者因错误而失败时调用。该Instrumenter
是参数化的泛型类REQUEST
和RESPONSE
类型。它们代表着操作的输入和输出。Instrumenter
可以配置各种提取器,可以增强或修改遥测数据。
使用 shouldStart()
来检查当前操作是否生成了任何遥测数据。
第一个方法,需要在任何其他Instrumenter
方法之前调用shouldStart()
.它确定是否应该对操作进行遥测检测。该Instrumenter
框架实现了几个抑制规则,以防止生成重复的遥测数据;例如,相同的 HTTP 服务器请求总是产生单个 HTTPSERVER
跨度。
该shouldStart()
方法接受当前的 OpenTelemetryContext
和检测的库REQUEST
类型。参考以下示例:
Response decoratedMethod(Request request) {
Context parentContext = Context.current();
if (!instrumenter.shouldStart(parentContext, request)) {
return actualMethod(request);
}
// ...
}
如果该shouldStart()
方法返回false
,则剩余的Instrumenter
方法不应该被调用。
开始使用start()
启动检测操作
当shouldStart()
返回时true
,您可以使用start()
来启动检测操作。
该start()
方法开始收集有关正在调用的检测库函数的遥测数据。它启动Span
并开始记录指标(如果在使用的Instrumenter
实例中注册了任何指标)。
该start()
方法接受当前的 OpenTelemetryContext
和检测的库REQUEST
类型,并返回新的 OpenTelemetry Context
,它应该在检测操作结束之前变为当前状态。参考以下示例:
Response decoratedMethod(Request request) {
// ...
Context context = instrumenter.start(parentContext, request);
try (Scope scope = context.makeCurrent()) {
// ...
}
}
新启动一个context
作为当前的上下文,并在其内部scope
调用实际的库方法。
使用 end()
结束检测操作
end()
当检测操作完成时调用该方法。始终在 之后调用此方法非常重要start()
。start()
不稍后调用end()
将导致不准确或错误的遥测和上下文泄漏。该end()
方法结束当前跨度并完成记录指标(如果在Instrumenter
实例中注册了任何指标)。
该end()
方法接受几个参数:
start()
方法返回一个OpenTelemetry的Context
。REQUEST
开始处理的实例。- 可选地,
RESPONSE
结束处理的实例 - 可能是null
在未收到或发生错误的情况下。 - (可选)使用
Throwable
操作引发的错误。
参考以下示例:
Response decoratedMethod(Request request) {
Context parentContext = Context.current();
if (!instrumenter.shouldStart(parentContext, request)) {
return actualMethod(request);
}
Context context = instrumenter.start(parentContext, request);
try (Scope scope = context.makeCurrent()) {
Response response = actualMethod(request);
instrumenter.end(context, request, response, null);
return response;
} catch (Throwable error) {
instrumenter.end(context, request, null, error);
throw error;
}
}
在代码示例中start()
方法返回的值context
被传递给end()
方法。end()
无论装饰的结果如何,无论actualMethod()
是有效响应还是错误,始终调用该方法。
使用InstrumenterBuilder
创建一个新的 Instrumenter
一个Instrumenter
可以通过调用它的静态获得builder()
方法和使用返回的InstrumenterBuilder
配置捕获遥测和应用自定义设置。该builder()
方法接受三个参数:
- 一个
OpenTelemetry
实例,用于获取Tracer
和Meter
对象。 - 检测名称,指示检测库名称,而不是 检测库名称。此处传递的值应唯一标识检测库,以便在故障排除期间可以确定遥测数据的来源。在OpenTelemetry 规范中阅读有关检测库的更多信息。
SpanNameExtractor
确定span名称。
一个Instrumenter
可以由几个较小的组件构建。以下小节描述了可用于自定义Instrumenter
.
使用 SpanNameExtractor
命名span
SpanNameExtractor
是一个简单的函数式接口,它接受REQUEST
类型并返回跨度名称。有关跨度命名的更详细指南,请查看Span
规范 和跟踪语义约定。
参考以下示例:
class MySpanNameExtractor implements SpanNameExtractor<Request> {
@Override
public String extract(Request request) {
return request.getOperationName();
}
}
示例SpanNameExtractor
实现采用请求类型提供的拟合值并将其用作span名称。请注意,这SpanNameExtractor
是一个@FunctionalInterface
: 而不是将它作为一个单独的类来实现,您可以只传递Request::getOperationName
给该builder()
方法。
使用AttributesExtractor
给span和metric数据添加属性
AttributesExtractor
负责在处理开始和结束时提取span和metric属性。它包含两种方法:
onStart()
当检测操作开始时调用该方法。它接受两个参数:一个AttributesBuilder
实例和传入的REQUEST
实例。onEnd()
当检测操作结束时调用该方法。它接受与相同的两个参数onStart()
以及一个可选RESPONSE
和一个可选的Throwable
错误。
这两种方法的目的都是从接收到的请求(以及响应或错误)中提取感兴趣的属性并将它们设置到构建器中。一般来说,最好在onStart()
填充attributes,因为这些attributes可用于Sampler
.
参考以下示例:
class MyAttributesExtractor implements AttributesExtractor<Request, Response> {
private static final AttributeKey<String> NAME = stringKey("mylib.name");
private static final AttributeKey<Long> COUNT = longKey("mylib.count");
@Override
public void onStart(AttributesBuilder attributes, Request request) {
set(attributes, NAME, request.getName());
}
@Override
public void onEnd(
AttributesBuilder attributes,
Request request,
@Nullable Response response,
@Nullable Throwable error) {
if (response != null) {
set(attributes, COUNT, response.getCount());
}
}
}
上面的AttributesExtractor
示例实现设置了两个属性:一个从请求中提取,一个从响应中提取。建议将AttributeKey
实例保留为静态final常量并重用它们。每次设置属性时创建一个新键都有引入不必要开销的风险。
您可以使用或方法AttributesExtractor
向中添加。InstrumenterBuilder``addAttributesExtractor()``addAttributesExtractors()
您也可以使用addAttributesExtractor()
或者addAttributesExtractors()
向InstrumenterBuilder
添加一个AttributesExtractor
。
使用 SpanStatusExtractor
设置span状态
默认情况下,当Throwable
发生错误的时候设置span状态为StatusCode.ERROR
,其他情况使用StatusCode.UNSET
。设置自定义SpanStatusExtractor
允许自定义此行为。
该SpanStatusExtractor
接口只有一个方法extract()
接受REQUEST
,一个可选RESPONSE
和一个可选的Throwable
错误。它应该返回一个StatusCode
将在span包装检测操作结束时设置的。
参考以下示例:
class MySpanStatusExtractor implements SpanStatusExtractor<Request, Response> {
@Override
public StatusCode extract(
Request request,
@Nullable Response response,
@Nullable Throwable error) {
if (response != null) {
return response.isSuccessful() ? StatusCode.OK : StatusCode.ERROR;
}
return SpanStatusExtractor.getDefault().extract(request, response, error);
}
}
上面SpanStatusExtractor
的示例根据操作结果返回一个自定义StatusCode
,编码在response类中。如果响应不存在(例如,由于错误),使用SpanStatusExtractor.getDefault()
方法返回默认的行为。
您可以使用setSpanStatusExtractor()
方法在InstrumenterBuilder
设置SpanStatusExtractor
。
使用 SpanLinksExtractor
添加span 关联
该SpanLinksExtractor
接口可用于在检测操作开始时添加到其他span的链接。它有一个extract()
接收以下参数的方法:
- 一个
SpanLinkBuilder
可以用来添加的链接。 - 通过
Instrumenter.start()
传递父Context
。 - 通过
Instrumenter.start()
传递REQUEST
。
您可以在此处阅读有关跨度链接及其用例的更多信息。
参考以下示例:
class MySpanLinksExtractor implements SpanLinksExtractor<Request> {
@Override
public void extract(SpanLinksBuilder spanLinks, Context parentContext, Request request) {
for (RelatedOperation op : request.getRelatedOperations()) {
spanLinks.addLink(op.getSpanContext());
}
}
}
在SpanLinksExtractor
示例中,请求对象很方便的使用getRelatedOperations()
方法查找所有的span并链接到一起。当这样的函数不存在时,您需要从请求头或者元数据中提取需要构造的信息来构造一个SpanContext
。
您可以使用addSpanLinksExtractor()
方法在InstrumenterBuilder
设置SpanLinksExtractor
。
使用ErrorCauseExtractor
丢弃包装异常类型
发生错误时,根本原因可能隐藏在几个“包装器”异常类型后面,例如来自 Java 标准库的ExecutionException
或 CompletionException
。默认情况下,来自 JDK 的已知包装器异常类型将从捕获的错误中删除。要删除其他包装器异常,例如检测库提供的异常,您可以实现ErrorCauseExtractor
,它具有以下功能:
- 它只有
extractCause()
一种方法负责剥离不必要的异常层并提取导致操作失败的实际错误。 - 它接受一个
Throwable
并返回一个Throwable
。
参考以下示例:
class MyErrorCauseExtractor implements ErrorCauseExtractor {
@Override
public Throwable extractCause(Throwable error) {
if (error instanceof MyLibWrapperException && error.getCause() != null) {
error = error.getCause();
}
return ErrorCauseExtractor.jdk().extractCause(error);
}
}
在ErrorCauseExtractor
示例中,实现检查错误是否是MyLibWrapperException
实例并获取原因,在这种情况下它会解开它。error.getCause() != null
检查非常重要:如果提取器没有验证这两个条件,就可能意外地删除整个异常,使检测丢失一个异常错误,从而从根本上改变了捕获的遥测。接下来,提取器返回到jdk()
删除已知 JDK 包装器异常类型的默认实现。
您可以使用setErrorCauseExtractor()
方法在InstrumenterBuilder
设置ErrorCauseExtractor
。
使用TimeExtractor
自定义操作开始时间和结束时间
在某些情况下,检测库提供了一种方法来检索操作开始和结束的准确时间戳。TimeExtractor
接口可用于将此信息提供给 OpenTelemetry trace和metric数据。
extractStartTime()
只能从请求中提取时间戳。extractEndTime()
接受请求、可选响应和可选Throwable
错误。
参考以下示例:
class MyTimeExtractor implements TimeExtractor<Request, Response> {
@Override
public Instant extractStartTime(Request request) {
return request.startTimestamp();
}
@Override
public Instant extractEndTime(Request request, @Nullable Response response, @Nullable Throwable error) {
if (response != null) {
return response.endTimestamp();
}
return request.clock().now();
}
}
上面的示例实现使用请求来检索开始时间戳。如果可用,则响应用于计算结束时间;如果它丢失(例如,发生错误时),则使用相同的时间源来计算当前时间戳。
您可以在InstrumenterBuilder
使用setTimeExtractor()
方法中设置时间提取器。
通过实现RequestMetrics
和RequestListener
注册 metrics
如果您需要向Instrumenter
中添加指标,您可以实现RequestMetrics
和RequestListener
接口。RequestMetrics
只是一个工厂接口RequestListener
- 它接收一个 OpenTelemetryMeter
并返回一个新的监听器。
在RequestListener
包含两个方法:
start()
在检测操作开始时执行。它返回一个Context
-如果有需要的话,它可以使用内部存储指标状态并传递到end()
。end()
在检测操作结束时执行。
这两种方法都接收一个Context
,它的一个实例Attributes
包含在检测操作开始或结束时计算的属性,以及可用于准确计算持续时间的开始和结束纳秒时间戳。
参考以下示例:
class MyRequestMetrics implements RequestListener {
static RequestMetrics get() {
return MyRequestMetrics::new;
}
private final LongUpDownCounter activeRequests;
MyRequestMetrics(Meter meter) {
activeRequests = meter
.upDownCounterBuilder("mylib.active_requests")
.setUnit("requests")
.build();
}
@Override
public Context start(Context context, Attributes startAttributes, long startNanos) {
activeRequests.add(1, startAttributes);
return context.with(new MyMetricsState(startAttributes));
}
@Override
public void end(Context context, Attributes endAttributes, long endNanos) {
MyMetricsState state = MyMetricsState.get(context);
activeRequests.add(1, state.startAttributes());
}
}
上面实例列出了通过静态方法get()
实现一个RequestMetrics
工厂接口。监听器使用计数器实现了当前正在请求的数量。请注意,start()
和end()
方法之间的状态是使用MyMetricsState
类(一个非常简单的数据类,未在上面的示例中列出)共享的,使用Context
.
上面列出的示例类RequestMetrics
在静态get()
方法中实现了工厂接口。监听器实现使用计数器来测量当前正在传输的请求数。请注意,start()
和end()
方法之间的状态是使用MyMetricsState
类(一个非常简单的数据类,未在上面的示例中列出)共享的,使用Context
进行传递。
您可以添加RequestMetrics
到InstrumenterBuilder
usingaddRequestMetrics()
方法。
您可以在InstrumenterBuilder
使用addRequestMetrics()
方法来添加RequestMetrics
。
使用ContextCustomizer
对Context
提供丰富的操作
在一些罕见的情况下,需要Context
在从Instrumenter#start()
方法返回之前填充它。该ContextCustomizer
接口可用于实现这一点。它暴露了一个start()
方法并Context
、 REQUEST
和Attributes
参数,在操作开始时提取的方法,并返回一个修改后的Context
.
参考以下示例:
class MyContextCustomizer implements ContextCustomizer<Request> {
@Override
public Context start(Context context, Request request, Attributes startAttributes) {
return context.with(new InProcessingAttributesHolder());
}
}
示例ContextCustomizer
上方插入一个额外的InProcessingAttributesHolder
到Context
并在Instrumenter#start()
方法之前将其从返回。在InProcessingAttributesHolder
类,正如它的名字所暗示的,可以用来跟踪不可用的要求开始或结束的属性-例如,如果用仪器库只在加工过程中暴露的重要信息。可以从当前查找持有者类,Context
并在检测操作开始和结束之间填充该信息。它可以稍后作为RESPONSE
类型(或其一部分)传递给Instrumenter#end()
方法,以便配置AttributesExtractor
可以处理收集的信息并将其转换为遥测属性。
您可以在InstrumenterBuilder
使用addContextCustomizer()
方法来添加ContextCustomizer
。
禁用检测
在极少数情况下,完全禁用构造Instrumenter
可能很有用,例如,基于配置属性。InstrumenterBuilder
暴露了setDisabled()
方法:通过true
将转向新创建Instrumenter
成无操作实例。
最后,通过 SpanKindExtractor
设置span类型并获取一个新的 Instrumenter
!
使用InstrumenterBuilder
以下的一种方法来使Instrumenter
创建结束:
newInstrumenter()
:返回的Instrumenter
将始终以 kind 开头INTERNAL
。newInstrumenter(SpanKindExtractor)
:返回的Instrumenter
将始终以传递的SpanKindExtractor
.newClientInstrumenter(TextMapSetter)
:返回的Instrumenter
将始终启动CLIENT
跨度并将操作上下文传播到传出请求中。newServerInstrumenter(TextMapGetter)
:返回的Instrumenter
将始终启动SERVER
跨度,并将从传入请求中提取父跨度上下文。newProducerInstrumenter(TextMapSetter)
:返回的Instrumenter
将始终启动PRODUCER
跨度并将操作上下文传播到传出请求中。newConsumerInstrumenter(TextMapGetter)
:返回的Instrumenter
将始终启动SERVER
跨度,并将从传入请求中提取父跨度上下文。
最后4个变种用来创建非INTERNAL
span,它接收TextMapSetter
或TextMapGetter
实现作为参数。这些是正确实现服务之间的上下文传播所必需的。如果您想了解如何使用和实现这些接口,请阅读OpenTelemetry Java 文档。
SpanKindExtractor
由上面列表中的第二个变种接收的接口是一个简单的接口,它接收一个REQUEST
并返回 SpanKind
,该接口应在启动此操作的span时使用。
参考以下示例:
class MySpanKindExtractor implements SpanKindExtractor<Request> {
@Override
public SpanKind extract(Request request) {
return request.shouldSynchronouslyWaitForResponse() ? SpanKind.CLIENT : SpanKind.PRODUCER;
}
}
实例SpanKindExtractor
根据请求处理决定是否使用PRODUCER
或CLIENT
。此示例反映了一个真实场景:您可能会在消息传递库检测中找到类似的代码,因为根据OpenTelemetry 消息传递语义约定 ,如果发送消息是完全同步的并等待响应,则span 类型设置为CLIENT
。