OpenTracing 兼容性

状态: 稳定

摘要

OpenTelemetry 项目旨在提供与 OpenTracing 项目的向后兼容性,以便于仪器化代码库的迁移。

此功能将作为一个桥接层提供,使用 OpenTelemetry API 来实现 OpenTracing API。此层不得依赖任何 SDK 的实现细节。

更具体地说,意图是允许使用 OpenTelemetry 记录 OpenTracing 仪器。此 Shim 层不得公开任何上游 OpenTelemetry API。

此功能必须定义在自己的 OpenTracing Shim 层中,而不是在 OpenTracing 或 OpenTelemetry API 或 SDK 中。

除非在 Set TagLog 部分中描述的错误映射,否则不应执行语义约定映射。

不建议在同一代码库中同时使用 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-shimTracer,以及当前的 shim 库版本。
  • OpenTelemetry Propagator,用于执行 OpenTracing TextMapHTTPHeaders 格式的注入和提取。如果未指定,Shim 中将不存储任何 Propagator 值,并且全局 OpenTelemetry TextMap propagators 将用于 OpenTracing TextMapHTTPHeaders 格式。

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_fromchild_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 SpanBaggage

  • TextMapHttpHeaders 格式必须使用其显式指定的 TextMapPropagator(如果存在),否则使用全局 TextMapPropagator

即使在没有有效 SpanContext 的情况下,也必须注入任何非空的 Baggage

如果指定的 Format 未识别,可能会引发错误,具体取决于 OpenTracing 语言 API(例如,Go 和 Python 会,而 Java 可能不会)。

Extract

参数

  • 一个 Format 描述符。
  • 一个 carrier。

使用在构造时配置的显式注册或全局 OpenTelemetry Propagator,提取底层的 OpenTelemetry SpanBaggage

  • TextMapHttpHeaders 格式必须使用其显式指定的 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 SpanBaggage 值来实现,借助 SpanContext Shim 对象。

Log 操作必须使用 OpenTelemetry SpanAdd Events 操作来实现。

Set Tag 操作必须使用 OpenTelemetry SpanSet 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 Eventname 参数必须是键为 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 必须是不可变的,并且必须包含关联的 SpanContextBaggage 值。

获取 Baggage Items

返回一个字典、集合或迭代器(取决于 OpenTracing API 对特定语言的要求),该对象由关联的 OpenTelemetry Baggage 值支持。

ScopeManager Shim

对于实现 ScopeManager 接口的 OpenTracing 语言,其操作必须使用 OpenTelemetry Context Propagation API 来获取和设置活动的 Context 实例。

激活 Span

参数

  • 一个 Span

Span Shim 及其底层的 SpanBaggage 存储在一个新的 Context 中,然后将其设置为当前活动的实例。

如果指定的 Span 为 null,则必须将其设置为包装无效 SpanContextNonRecordableSpan,以表示没有活动的 SpanBaggage

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 SpanSpan Shim。

如果当前 OpenTelemetry SpanSpanContext 无效且当前 Baggage 为空,此操作必须立即返回 null,以表示没有活动的 SpanBaggage

如果当前 OpenTelemetry SpanSpanContext 无效但当前 Baggage 不为空,此操作必须返回一个新的 Span Shim,其中包含一个无操作的 OpenTelemetry Span 和非空的 Baggage

如果当前 Context 中存在匹配的 OpenTelemetry SpanSpan Shim 对象,则必须返回 Span Shim。否则,必须返回一个包含当前 OpenTelemetry SpanBaggage 的新 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;
    }
    ...
  }
  ...
}