OpenTracing 兼容性
状态: 稳定。
摘要
OpenTelemetry 项目旨在提供与 OpenTracing 项目的向后兼容性,以便于仪器化代码库的迁移。
此功能将作为一个桥接层提供,使用 OpenTelemetry API 来实现 OpenTracing API。此层不得依赖任何 SDK 的实现细节。
更具体地说,意图是允许使用 OpenTelemetry 记录 OpenTracing 仪器。此 Shim 层不得公开任何上游 OpenTelemetry API。
此功能必须定义在自己的 OpenTracing Shim 层中,而不是在 OpenTracing 或 OpenTelemetry API 或 SDK 中。
除非在 Set Tag 和 Log 部分中描述的错误映射,否则不应执行语义约定映射。
不建议在同一代码库中同时使用 OpenTracing Shim 和 OpenTelemetry API,原因如下
- 如果 OpenTracing 仪器化代码消耗 baggage,因为
Baggage本身可能无法正确传播。请参阅 Span Shim 和 SpanContext Shim 的关系。 - 对于在 OpenTelemetry 中具有隐式进程内传播支持但在 OpenTracing 中没有(例如 Javascript)的语言,因为它会破坏预期的传播语义,并可能导致不正确的
Context使用和不正确的跟踪。请参阅 隐式和显式支持不匹配。
语言版本支持
鼓励用户在使用 Shim 层之前检查和更新其语言和运行时组件,因为 OpenTelemetry API 和 SDK 可能比它们的 OpenTracing 对应物有更高的版本要求。
有关详细信息,请参阅 从 OpenTracing 迁移 的 语言版本支持 部分。
创建 OpenTracing Tracer Shim
此操作用于创建一个新的 OpenTracing Tracer。
此操作必须接受以下参数
- 一个 OpenTelemetry
TracerProvider。此操作必须使用此TracerProvider来获取一个名为opentracing-shim的Tracer,以及当前的 shim 库版本。 - OpenTelemetry
Propagator,用于执行 OpenTracingTextMap和HTTPHeaders格式的注入和提取。如果未指定,Shim 中将不存储任何Propagator值,并且全局 OpenTelemetryTextMappropagators 将用于 OpenTracingTextMap和HTTPHeaders格式。
API 必须返回一个 OpenTracing Tracer。
// Create a Tracer Shim relying on the global propagators.
createTracerShim(tracerProvider);
// Create a Tracer Shim using:
// 1) TraceContext propagator for TextMap
// 2) Jaeger propagator for HttPHeaders.
createTracerShim(tracerProvider, OTPropagatorsBuilder()
.setTextMap(W3CTraceContextPropagator.getInstance())
.setHttpHeaders(JaegerPropagator.getInstance())
.build());
请参阅 OpenTracing Propagation 格式。
Tracer Shim
启动新 Span
参数
- 操作名称,一个字符串。
- 可选的 Span references 列表。
- 可选的 tags 列表。
- 可选的显式开始时间戳,一个数值。
对于实现了 ScopeManager 接口的 OpenTracing 语言,还定义了以下参数
- 一个可选的布尔值,指定当前
Span是否应被忽略为自动父级。
如果指定了 Span 引用列表,则使用列表中的第一个Child Of类型的 SpanContext 作为父级,否则使用第一个 SpanContext 作为父级。列表中的所有值都必须作为 Link 添加,并将引用类型值作为 Link 属性,即 opentracing.ref_type 设置为 follows_from 或 child_of。
如果指定了 Span 引用列表,则它们的 Baggage 值的并集必须用作新创建 Span 的初始 Baggage。如果存在重复键,则使用哪个 Baggage 值是不确定的。如果未指定此类引用列表,则当前 Baggage 必须用作新创建 Span 的初始值。
如果指定了初始标签集,则必须在 OpenTelemetry Span 创建时设置值,而不是在 Span 创建后设置。这样做是为了使任何 Span 创建前的钩子都可以访问这些值,例如,引用 SDK 会执行 采样 步骤,该步骤会查询 Span 信息,包括初始标签/属性,以决定是否采样。
如果指定了初始标签集并且包含 OpenTracing error 标签,在 OpenTelemetry Span 创建后,Shim 层必须执行与 Set Tag 操作中所述相同的错误处理。
如果指定了显式开始时间戳,则必须进行转换以匹配 OpenTracing 和 OpenTelemetry 的单位。
API 必须返回一个 OpenTracing Span。
Inject
参数
- 一个
SpanContext。 - 一个
Format描述符。 - 一个 carrier。
使用在构造时配置的显式注册或全局 OpenTelemetry Propagator,注入底层的 OpenTelemetry Span 和 Baggage。
TextMap和HttpHeaders格式必须使用其显式指定的TextMapPropagator(如果存在),否则使用全局TextMapPropagator。
即使在没有有效 SpanContext 的情况下,也必须注入任何非空的 Baggage。
如果指定的 Format 未识别,可能会引发错误,具体取决于 OpenTracing 语言 API(例如,Go 和 Python 会,而 Java 可能不会)。
Extract
参数
- 一个
Format描述符。 - 一个 carrier。
使用在构造时配置的显式注册或全局 OpenTelemetry Propagator,提取底层的 OpenTelemetry Span 和 Baggage。
TextMap和HttpHeaders格式必须使用其显式指定的TextMapPropagator(如果存在),否则使用全局TextMapPropagator。
如果满足以下任一条件,该操作必须返回一个 SpanContext Shim 实例,其中包含提取的值
SpanContext有效。SpanContext已采样。SpanContext包含非空的提取的Baggage。
否则,操作必须返回 null 或空值。
if (!extractedSpanContext.isValid()
&& !extractedSpanContext.isSampled()
&& extractedBaggage.isEmpty()) {
return null;
}
return SpanContextShim(extractedSpanContext, extractedBaggage);
如果 Format 未识别或无法提取任何值,可能会引发错误,具体取决于 OpenTracing 语言 API(例如,Go 和 Python 会,而 Java 可能不会)。
注意:无效但已采样的 SpanContext 实例被返回,作为对 jaeger-debug-id headers 的支持,该标头用于强制传播调试信息。
Close
可选操作。如果此操作在特定的 OpenTracing 语言中实现,则必须关闭底层的 TracerProvider(如果它实现“可关闭”接口或方法);否则,它必须被定义为无操作。
Shim 层必须保护免受关闭底层 TracerProvider 时引发的错误或异常。
注意:建议用户不要对每个 TracerProvider 调用此操作一次以上,因为它可能产生意外的副作用、限制或竞争条件,例如,单个 Shim Tracer 被关闭多次,或多个 Shim Tracer 被调用关闭操作。
Span Shim 和 SpanContext Shim 的关系
根据 OpenTracing 规范,OpenTracing SpanContext Shim 必须包含 Baggage 数据,并且必须是不可变的。
反过来,OpenTracing Span Shim 必须包含一个 SpanContext Shim。在更新其关联的 baggage 时,OpenTracing Span 必须将其 OpenTracing SpanContext Shim 设置为包含更新的 Baggage 的新实例。
这是上述对象的简单图形表示
Span Shim
+- OpenTelemetry Span (read-only)
+- SpanContext Shim
+- OpenTelemetry SpanContext (read-only)
+- OpenTelemetry Baggage (read-only)
OpenTracing Shim 利用上述对象层次结构,通过进程内和进程间传播 OpenTelemetry Span 及其关联的 Baggage 来正确执行。
由于 OpenTelemetry 不知晓此关联,因此如果在同一代码库中同时使用 OpenTelemetry API 和 OpenTracing Shim,相关的 Baggage 可能无法正确传播,如下面的示例所示
// methodOne consumes the OpenTelemetry API.
void methodOne(Span span) {
try (Scope scope = span.makeCurrent()) {
methodTwo();
}
}
// methodTwo consumes the OpenTracing Shim.
void methodTwo() {
io.opentracing.Span span = io.opentracing.util.GlobalTracer.get()
.activeSpan();
// Correctly set in the underlying io.opentelemetry.api.trace.Span
span.setTag("tag", "value");
// Value is set in the Shim layer -- it may not be later propagated
// as OpenTelemetry is not aware of the Baggage associated
// to this Span.
span.setBaggageItem("baggage", "item");
}
访问关联 Baggage 的操作必须是安全的,可以并发调用。
Span Shim
OpenTracing Span 操作必须使用底层的 OpenTelemetry Span 和 Baggage 值来实现,借助 SpanContext Shim 对象。
Log 操作必须使用 OpenTelemetry Span 的 Add Events 操作来实现。
Set Tag 操作必须使用 OpenTelemetry Span 的 Set Attributes 操作来实现。
获取 Context
返回当前的 SpanContext Shim。
此操作必须是安全的,可以并发调用。
获取 Baggage Item
参数
- baggage 键,一个字符串。
返回当前 SpanContext Shim 的 OpenTelemetry Baggage 中指定键的值,如果不存在则返回 null。
此操作必须是安全的,可以并发调用。
String getBaggageItem(String key) {
synchronized(this) {
// Get the current SpanContext's Baggage.
io.opentelemetry.baggage.Baggage baggage = this.spanContextShim.getBaggage();
// Return the value for key.
return baggage.getEntryValue(key);
}
}
设置 Baggage Item
参数
- baggage 键,一个字符串。
- baggage 值,一个字符串。
创建一个新的 SpanContext Shim,其中包含一个包含指定的 Baggage 键/值对的新 OpenTelemetry Baggage,并将其设置为此 Span Shim 的当前实例。
此操作必须是安全的,可以并发调用。
void setBaggageItem(String key, String value) {
synchronized(this) {
// Add value/key to the existing Baggage.
Baggage newBaggage = this.spanContextShim.getBaggage().toBuilder()
.put(key, value)
.build();
// Create a new SpanContext with the updated Baggage.
SpanContextShim newSpanContextShim = this.spanContextShim
.newWithBaggage(newBaggage);
// Update our SpanContext instance.
this.spanContextShim = newSpanContextShim;
}
}
设置 Tag
参数
- tag 键,一个字符串。
- tag 值,必须是字符串、布尔值或数值类型。
使用指定的键/值对在底层的 OpenTelemetry Span 上调用 Set Attribute。
error tag 必须 映射 到 StatusCode。
true映射到Error。false映射到Ok。- 未设置值则映射到
Unset。
如果指定值类型的类型不受 OTel API 支持,则该值必须转换为字符串。
日志
参数
- 一组键/值对,其中键必须是字符串,值可以是任何类型。
使用指定的键/值对在底层的 OpenTelemetry Span 上调用 Add Events。
Add Event 的 name 参数必须是键为 event 的值,否则回退使用 log 字面量字符串。
如果键值对包含 event=error 条目,则值必须 映射 到一个 Event,并遵循 Exception semantic conventions 文档中概述的约定。
- 如果存在键为
error.object的条目且值为语言特定的错误对象,则调用RecordException(e),并将剩余的键/值对作为额外的事件属性。 - 否则,调用
AddEvent,并将name设置为exception,同时将指定的键/值对作为额外的事件属性,包括以下键/值对的映射error.kind映射到exception.type。message映射到exception.message。stack映射到exception.stacktrace。
如果指定了显式时间戳,则必须进行转换以匹配 OpenTracing 和 OpenTelemetry 的单位。
Finish
在底层的 OpenTelemetry Span 上调用 End。
如果指定了显式时间戳,则必须进行转换以匹配 OpenTracing 和 OpenTelemetry 的单位。
SpanContext Shim
SpanContext Shim 必须是不可变的,并且必须包含关联的 SpanContext 和 Baggage 值。
获取 Baggage Items
返回一个字典、集合或迭代器(取决于 OpenTracing API 对特定语言的要求),该对象由关联的 OpenTelemetry Baggage 值支持。
ScopeManager Shim
对于实现 ScopeManager 接口的 OpenTracing 语言,其操作必须使用 OpenTelemetry Context Propagation API 来获取和设置活动的 Context 实例。
激活 Span
参数
- 一个
Span。
将 Span Shim 及其底层的 Span 和 Baggage 存储在一个新的 Context 中,然后将其设置为当前活动的实例。
如果指定的 Span 为 null,则必须将其设置为包装无效 SpanContext 的 NonRecordableSpan,以表示没有活动的 Span 或 Baggage。
Scope activate(Span span) {
if (span == null) {
span = new SpanShim(io.opentelemetry.api.trace.Span.getInvalid());
}
SpanShim spanShim = (SpanShim)span;
// Put the associated Span and Baggage in a new Context.
Context context = Context.current()
.withValue(spanShim)
.withValue(spanShim.getSpan())
.withValue(spanShim.getBaggage());
// Set context as the current instance.
return context.makeCurrent();
}
未采样的 OpenTelemetry Span 可以被完美激活,因为它们具有有效的 SpanContext(尽管 sampled 标志设置为 false)。
// The underlying OpenTelemetry TracerProvider's Sampler
// decided to NOT sample this Span, hence
// io.opentelemetry.api.trace.Span.getSpanContext().isSampled() == false.
Span span = tracer.buildSpan("operationName").start();
try (Scope scope = tracer.scopeManager().activate(span)) {
// tracer.scopeManager().activeSpan() == span
}
获取活动的 Span
返回一个包装当前活动的 OpenTelemetry Span 的 Span Shim。
如果当前 OpenTelemetry Span 的 SpanContext 无效且当前 Baggage 为空,此操作必须立即返回 null,以表示没有活动的 Span 或 Baggage。
如果当前 OpenTelemetry Span 的 SpanContext 无效但当前 Baggage 不为空,此操作必须返回一个新的 Span Shim,其中包含一个无操作的 OpenTelemetry Span 和非空的 Baggage。
如果当前 Context 中存在匹配的 OpenTelemetry Span 和 Span Shim 对象,则必须返回 Span Shim。否则,必须返回一个包含当前 OpenTelemetry Span 和 Baggage 的新 Span Shim。
Span active() {
io.opentelemetry.api.trace.Span span = Span.fromContext(Context.current());
io.opentelemetry.api.baggage.Baggage baggage = Baggage.fromContext(Context.current());
SpanShim spanShim = SpanShim.fromContext(Context.current());
// There is no actual currently active Span.
if (!span.getSpanContext().isValid()) {
// Immediately return null if there is no Baggage.
if (baggage.isEmpty()) {
return null;
}
// Else return a no-op Span with the Baggage.
return SpanShim(baggage);
}
// Span was activated through the Shim layer, re-use it.
if (spanShim != null && spanShim.getSpan() == span) {
return spanShim;
}
// Span was NOT activated through the Shim layer,
// do a best effort with the current values.
new SpanShim(span, baggage);
}
Span References
如 OpenTracing Specification 所定义,Span 可以引用零个或多个因果相关的其他 SpanContext。引用信息本身由 SpanContext 和引用类型组成。
OpenTracing 定义了两种类型的引用
- Child Of:父
Span在某种程度上依赖于子Span。 - Follows From:父
Span不以任何方式依赖于其子Span的结果。
OpenTelemetry 没有为这些引用定义严格的等效语义。这些引用类型不得与 Link 功能混淆。但是,这些信息以 opentracing.ref_type 属性的形式保留。
进程内 Propagation 异常
隐式和显式支持不匹配
对于在 OpenTelemetry 中具有隐式进程内传播支持但在 OpenTracing 中没有(即没有 ScopeManager 支持)的语言,Shim 必须仅在其操作中使用显式上下文传播(例如,在启动新 Span 时)。这是为了轻松符合 OpenTracing API 的仅显式传播语义。
// Tracer Shim
startSpan(name: string, options: SpanOptions = {}): Span {
const otelSpanOptions = ...;
if (!options.childOf && !options.references) {
// Do NOT get nor set the current Context/Span,
// as it is part of the implicit propagation support.
otelSpanOptions.root = true;
}
...
}
在同一代码库中同时使用 OpenTracing Shim 和 OpenTelemetry API 可能会导致跟踪使用不正确的父 Span,因为隐式/显式传播期望不同。在这种情况下,Shim可能通过显式设置提供与 OpenTelemetry 隐式进程内传播的开发中集成,并警告用户可能会使用不正确的父值。
// Tracer Shim
startSpan(name: string, options: SpanOptions = {}): Span {
const otelSpanOptions = ...;
if (!options.childOf && !options.references) {
if (otShimOptions.supportImplicitPropagation) {
// Allow OpenTelemetry to consume the current Context
// to fetch the parent Span.
otelSpanOptions.root = false;
}
...
}
...
}