概要

Open Tracing 和 OpenCensus 已经合并为 OpenTelemetry ,OpenTelemetry specification 规定了所有实现需要遵循的要求。

OpenTelemetry 客户端架构

面向切面的程序设计(Aspect-Oriented Programming, AOP)

  • 横切关注点(cross-cutting concern)

OpenTelemetry 被设计为一系列独立的观察性(Observability)工具,称为信号(Signal),在共享机制上下文传播(context propagation)的基础上设计。

信号作为横切关注点(cross-cutting concern),被整合到许多库中。

每个信号作为横切关注点的部分都和应用程序独立管理的部分是分离的。每个信号都提供特殊形式的可观察性,例如,tracing、metrics、baggage 就是三种独立的信号。信号共享通用的子系统——上下文传播。

OpenTelemetry 客户端被设计为将信号的作为横切关注点导入的部分与可以独立管理的部分分离,同时也被设计为一个扩展框架。为了实现这些目标,每个信号都包含四种类型的包:API、SDK、语义约定(Semantic Conventions)、贡献包(Contrib)

API

API 包由用于检测的横切公共接口组成,导入到第三方库和应用程序代码的 OpenTelemetry 客户端的任何部分都被认为是 API 的一部分

SDK

SDK 是 OpenTelemetry 项目提供的 API 实现。在一个应用中,由应用程序所有者安装和管理 SDK 。SDK 包含额外的公共接口,不属于 API 包,因为它们不是横切关注点。这些公共接口被定义为构造器和插件。应用程序所有者使用 SDK 构造器,插件作者使用 SDK 插件接口。根据 OpenTelemetry API 编写 OpenTelemetry 仪器的维护者(Instrumentation Author)不能直接引用任何 SDK 包,只能引用 API。

  • 构造器(Constructors):应用程序所有者用来初始化和配置 OpenTelemetry SDK 和 贡献包的公共代码。例如,配置对象、环境变量、生成器。
  • 插件(SDK Plugins):拓展 OpenTelemetry SDK 的库。插件接口如 SpanProcessorExporterSampler

语义约定(Semantic Conventions)

语义约定定义了键值,用于描述应用程序的可观察概念、协议和操作。

贡献包(Contrib Packages)

OpenTelemetry 项目维护了流行开源软件(Open Source Software, OSS)项目的集成,API 集成包括 Web 框架、数据库客户端、消息队列,SDK 集成包含导出至分析工具和存储系统的插件。

OpenTelemetry 规范必需的插件都包含在 SDK 中,例如 OTLP Exporters 和 TraceContext Propagators 。

可选的、与 SDK 分离的插件和工具包被称为贡献包。API Contrib 指的是仅依赖于 API 的包;SDK Contrib 指的是依赖 SDK 的包。

术语 Contrib 特指 OpenTelemetry 项目维护的插件和工具集合,不涉及第三方插件。

版本控制和稳定性(Versioning & Stability)

OpenTelemetry 重视稳定性和向后兼容性(backward compatibility)。

Tracing Signal

分布式追踪是一组事件,由单个逻辑操作触发,并跨应用程序中的各个组件合并。一个分布式追踪包含跨进程、网络和安全边界的事件。

Traces

Trace 由 Span 隐式定义,可以认为是 Span 的有向无环图(Directed Acyclic Graph, DAG)。

Spans

Span 代表了事务中的操作,每个 Span 封装了以下状态:

  • 操作名称
  • 起止时间戳
  • 属性(Attributes):一系列键值对
  • 0 个或多个事件(Events)的集合,每个都是一个元组(时间戳,名称,属性),名称必须是字符串
  • 父 Span 的标识
  • 与 0 个或多个具有因果关系的 Span 链接(Links),通过相关 Span 的 SpanContext
  • 引用 Span 所需的 SpanContext 信息

SpanContext

表示标识 Trace 中的 Span 的所有信息,必须传播到子 Span 和跨进程边界。一个 SpanContext 包含从父 Span 传播到子 Span 的跟踪标识符和选项。

  • TraceId:trace 的标识符。全局唯一,随机生成 16 个字节。TraceId 用于将跨进程的特定 trace 的所有 span 分组在一起。
  • SpanId:span 的标识符。全局唯一,随机生成 8 个字节。当传递给子 Span 时,该标识符将成为子 Span 的父 span id 。
  • TraceFlags:trace 的选项。表示为一字节(位图 bitmap)
    • Sampling bit:表示 trace 是否被采样的比特(掩码 0x1
  • Tracestate:在一个键值对列表中携带特定于追踪系统的上下文。Tracestate 允许不同的供应商传播额外的信息,用它们的遗留的 Id 格式进行互操作。

Span 之间的链接(Links)

一个 Span 必须和 0 个或多个因果关联的其他 Span 链接(由 SpanContext 定义)。链接可以指向一个 Trace 内的 Span 或跨 Trace 。

当 Trace 进入服务的可信边界,并且服务策略要求生成新的 Trace 而不信任传入的 Trace 上下文时,可以用来表示原始 trace 和接下来的 trace 之间的关系。新链接的 Trace 还可以表示一个长时间运行的异步数据处理操作,由传入的许多请求之一发起。

Metric Signal

OpenTelemetry 允许用预定义的聚合和标签集记录原始测量或度量。

使用 OpenTelemetry API 记录原始测量允许最终用户决定应该为这个度量用什么聚合算法,以及定义标签(维度)。它将被用于像 gRPC 的客户端库,记录原始测量 server_latency 和 received_bytes 。因此最终用户将决定应该从这些原始测量数据中收集哪种类型的聚合值,也可能是简单的平均值或精细的直方图计算。

使用 OpenTelemetry API 记录预定义聚合的度量同样重要。它允许收集 CPU 和内存使用,或者是像队列长度这样的简单度量。

记录原始测量

用于记录原始测量的主要类是 MeasureMeasurement 。可以使用 OpenTelemetry API 记录附加上下文的 Measurement 列表。因此,用户可以通过定义来聚合这些 Measurement ,并使用传递上下文来定义结果度量的额外维度。

Measure

Measure 描述库记录的单个值的类型。定义了公开测量方法的库和聚合独立测量到 Metric 中的应用之间的关系。Measure 由名称、描述和一个值的单位标识。

Measurement

Measurement 描述为 Measure 收集的单一值,Measuremrnt 是一个空接口,在 SDK 中定义。

使用预定义聚合记录度量

所有类型的预定义聚合度量的基类称为 Metric ,它定义了基本的度量属性,例如名称和标签。继承 Metric 的类定义自己的聚合类型和单个测量或点的结构。API 定义了以下类型的预定义聚合度量:

  • Counter metric 报告瞬时测量。Counter 值可以上升或保持不变,但不可能下降也不可能为负,Counter 度量值有两种类型:doublelong
  • Gauge metric 报告数字值的瞬时测量。Gauge 值可以上升或下降,也可以为负值。Gauge 度量值有两种类型:doublelong

API 允许构造选定类型的 Metric ,SDK 定义了要导出的 Metric 值的查询方式。

每种类型的 Metric 拥有自己的 API 记录将要聚合的值。API 支持推拉模型(push and pull model)来设置 Metric 值。

度量数据模型和 SDK

基于 metrics.proto 建立度量数据模型(Metrics Data Model),这个数据模型定义了三种语义:API 使用的事件模型(Event model)、SDK 和 OTLP 使用的 in-flight 数据模型、表示导出工具如何解释 in-flight 数据模型时间序列模型(TimeSeries model)。

不同的导出工具有不同的能力(例如,支持的数据模型)和约束(例如,哪些字符允许作为标签键)。所有导出工具都通过 OpenTelemetry SDK 中定义的度量生产者接口(Metric Producer interface)从度量数据模型中消费数据。

因此,Metrics 对数据的约束最小(例如,键中允许哪些字符),处理 Metrics 的代码应该避免对其进行验证和清洗。相反,将数据传递给后端,依赖后端执行验证,并从后端返回错误。

Log Signal

数据模型

数据模型定义了 OpenTelemetry 如何理解日志和事件

Baggage Signal

除了 trace 的传播,OpenTelemetry 还提供了 Baggage 来传播键值对。Baggage 用于索引一个服务中的可观察事件,该服务包含同一事务中先前的服务提供的属性,有助于在事件之间建立因果关系。

虽然 Baggage 可以用作其他横切关注点的原型,但这种机制主要是为了传递 OpenTelemetry 可观测性系统的值。

这些值可以从 Baggage 中消费,并作为度量的附加维度,或日志和跟踪的附加上下文使用。一些例子:

  • web 服务可以从包含发送请求的服务的上下文中获益
  • SaaS 提供商可以包含有关负责该请求的 API 用户或令牌的上下文
  • 确定特定浏览器版本与图像处理服务中的故障相关联

Resources

Resources 获取关于被记录的遥测数据实体信息。例如,Kubernetes 容器公开的度量可以链接到指定集群、名称空间、pod 和容器名称的资源。Resources 可以捕获实体标识的整个层次结构,它可以描述云中的主机和特定的容器或进程中运行的应用程序。

上下文传播(Context Propagation)

所有 OpenTelemetry 横切关注点,例如 trace 和 metric ,共享底层的 Context 机制,用于在分布式追踪中存储状态和访问跨 Span 生命周期的数据。

传播器(Propagators)

OpenTelemetry 使用 Propagators 来序列化和反序列话横切关注点的值,例如 Span (通常只有 SpanContext 的部分)和 Baggage 。不同的 Propagators 类型定义了特定传输和绑定到数据类型的限制。

传播器 API 定义了一个 Propagator 类型

  • TextMapPropagator 将值注入载体并从载体中提取值为文本。

收集器(Collector)

OpenTelemetry 收集器是一套组件,可以从 OpenTelementry 或其他监测/追踪库(Jaeger、Prometheus 等)执行的进程收集 traces、metrics 和其他遥测数据(例如,日志),并进行聚合和智能采样,导出 traces 和 metrics 到一个或多个监控/追踪后端。收集器允许丰富和转换所收集的遥测数据(例如,添加额外的属性或删除个人信息)。

OpenTelemetry 收集器有两种主要的操作模式:代理(与应用程序一起在本地运行的守护进程),收集器(独立运行的服务)。

工具库(Instrumentation Library

被调用为另一个库启用 OpenTelemetry 可观测性的库称为工具库。

API 规范

上下文(Context)

Context是一种传播机制,在 API 边界和逻辑关联的执行单元之间承载执行范围(execution-scoped)的值。横切关注点使用相同的共享 Context 对象访问进程内的数据。

Context 必须是不可变的,写操作必须导致创建一个新的 Context ,包含原始值和更新值。

一个 Context 预期包含以下操作:

生成键

键允许横切关注点控制对其本地状态的访问。它们是唯一的,这样其他可能使用相同上下文的库就不会使用相同的键。建议关注点通过 API 中介数据访问,而不是提供对键的直接访问。

API 必须接受以下参数:

  • 键名。键名的存在是为了调试,它不能唯一地标识键。使用相同名称多次调用 CreateKey 不应该返回相同的值,除非语言约束另有规定。

API 必须返回一个表示新创建的键的不透明对象。

获取值

关注点可以在 Context 表示的当前执行状态中访问它们的本地状态。

API 必须接受以下参数:

  • Context

API 必须返回在 Context 中指定键的值。

设置值

关注点可以在 Context 表示的当前执行状态中记录它们的本地状态。

API 必须接受以下参数:

  • Context
  • 设置的值

API 必须返回包含新值的新的 Context

可选的通用操作

这些操作预计只能通过隐式使用 Context 实现,因此是可选的。这些操作应该只用于实现自动范围切换,并通过 SDK 组件和 OpenTelemetry 工具库定义更高级别的 API。

获取当前 Context

API 必须返回与调用者的当前执行单元相关的 Context

附加到 Context

Context 与调用者的当前执行单元相关联。

API 必须接受以下参数:

  • Context

API 必须返回一个可以用作 Token 的值,可用于恢复先前的 Context

注意,对该操作的每次调用都应该导致对分离 Context 的相应调用。

分离 Context

与调用者的当前执行单元相关的 Context 重置为附加到指定 Context 前的值。该操作用于帮助确保正确的 Context 与调用者的当前执行单元相关联。用户可以依靠它来识别错误的调用顺序,例如,试图分离不是当前实例的 Context 。在这种情况下,操作可以发出信号,警告用户错误的调用顺序,例如,记录错误或返回错误值。

API 必须接受以下参数:

  • 附加到 Context 时返回的 Token

API 可能会返回一个用于检查操作是否成功的值。

传播器(Propagators)

横切关注点使用 Propagator 将其状态发送到下一个进程,Propagator 定义为用于向应用程序交换的消息读写上下文数据的对象。每个关注点为每种支持的 Propagators 类型创建一组 Propagators

Propagators 利用 Context 为每个横切关注点注入和提取数据,例如 trace 和 Baggage

传播通常通过特定于库的请求拦截器和传播器的协作实现,其中拦截器检测传入请求和传出请求,并分别使用传播器的提取和注入操作。

预计编写插装库的用户将利用传播器 API 。

传播器类型

Propagator 类型定义了特定传输的限制,并绑定到数据类型,为了跨进程边界传播带内上下文数据。

Propagators API 目前定义了一个 Propagator 类型:

  • TextMapPropagator 是一种使用字符串键值对的形式向载体注入和提取值的类型。

未来将添加二进制 Propagator

载体(Carrier)

载体是 Propagator 用来读取值和写入值的媒介,每个特定的 Propagator 类型定义了预期的载体类型,例如字符串映射或字节数组。

用于注入的载体预期是可变的。

操作(Operations)

Propagator 必须定义 InjectExtract 操作,以便向载体写入值和从载体读取值。每个 Propagator 类型必须定义特殊的载体类型,并且可以定义格外的参数。

  1. 注入(Inject)

向载体注入值,例如,向 HTTP 请求头注入。

必须的参数:

  • Context 。传播其器必须首先从 Context 检索适当的值,例如 SpanContextBaggage 或其他横切关注点上下文。
  • 保存传播器字段的载体。例如,传出的消息或 HTTP 请求。
  1. 提取(Extract)

从传入的请求中提取值,例如,从 HTTP 请求头提取。

如果无法从载体中解析值,从横切关注点考虑,实现不能抛出异常,也不能在 Context 中存储新的值,这是为了保存先前存在的任何有效值。

必须的参数:

  • Context
  • 保存传播器字段的载体。例如,传入的消息或 HTTP 响应。

返回从作为参数传递的 Context 派生的新的 Context,其中包含提取的值,该值可以是 SpanContextBaggage 或其他横切关注点上下文.

TextMap 传播器

TextMapPropagator 将横切关注点的值作为字符串键值对注入和提取到跨进程边界的带内传输的载体中。

传播数据的载体在客户端(injector)和服务端(extractor)通常是 HTTP 请求。为了增加兼容性,键值对必须只包含根据 RFC 7230 构成有效 HTTP 头部字段的 US-ASCII 字符。

GetterSetter 是可选的帮助组件,分别用于提取和注入,它们被定义为与载体分离的对象,避免了运行时分配(runtime allocations)不需要额外的借接口实现对象来包装载体以访问其内容。

GetterSetter 必须是无状态的,并且允许存储为常量,为了有效避免运行时分配。

字段

预定义的传播器字段,如果载体被重用,应该在调用注入前删除这里的字段。

字段被定义为字符串键,用于标识载体中特定格式的组件。

例如,如果载体是一个单次使用或不可变的请求对象,则不需要清除字段;如果是可变的、可重用的对象,则后续调用应该首先请求这些字段。

用例:

  • 允许预先分配字段,特别是在 gRPC 元数据这样的系统中
  • 允许在迭代器上进行单次传递

返回 TextMapPropagator 将使用的字段列表。

注意,除了返回值之外,一些 Propagator 还可能定义带有变量名的附加字段。要获得一个特定载体对象的完整字段列表,使用 Keys 操作。

TextMap 注入

向载体注入值。必须的参数和注入(Inject)操作相同。

可选的参数:

  • 用于设置传播的键值对的Setter 。传播器可以多次调用它来设置多个键值对。

Setter 参数

Setter 是 Inject 中的一个参数,它将值设置到给定的字段中。

Setter 允许 TextMapPropagator 将传播字段设置到载体中。

实现方法之一是使用 Set 方法的 Setter 类。

Set

用给定的值替换传播的字段。

必需的参数:

  • 保存传播字段的载体。例如,传出消息或 HTTP 请求。
  • 字段的键
  • 字段的值

如果所使用的协议不区分大小写,实现应该保留大小写,否则必须保留大小写。

TextMap 提取

从传入的请求中提取值。必须的参数和提取(Extract)操作相同。

可选的参数:

  • 用于获取每个传播的键的 Getter

返回从作为参数传递的 Context 派生的新的 Context

Getter 参数

Getter 是从给定字段中获取值的 Extract 的参数。

Getter 允许 TextMapPropagator 从载体中读取传播的字段。

实现方法之一是使用 GetKeys 方法的 Getter 类。

Keys

Keys 方法必须返回载体中所有的键的列表。

必须的参数:

  • 传播字段的载体,例如 HTTP 请求。

Keys 方法可以被使用可变键名的 Propagator 调用,以遍历指定载体中的所有键。例如,它可以用来检测所有遵循 Jaeger 传输格式(Jaeger Propagation Format)定义的 uberctx-{user-defined-key} 模式的键。

Get

Get 方法必须返回给定传播的键的第一个值,如果该键不存在则返回 null 。

必需的参数

  • 传播字段的载体,例如 HTTP 请求。
  • 字段的键

Get 方法负责处理大小写。如果 Getter 用于处理 HTTP 请求对象,则 Getter 必须不区分大小写。

注入器和提取器作为单独的接口

可以将 Propagator 类型实现为公开 InjectExtract 方法的单个对象,也可以选择进一步划分为独立的 InjectorEXtractorPropagator 可以通过组合单独的 InjectorEXtractor来实现。

复合传播器(Composite Propagator)

实现必须提供一种工具,将来自不同横切关注点的多个 Propagator 进行分组,以便将其作为一个单独的实体。

复合传播器可以从传播器列表或注入器和提取器列表中构建。生成的复合 Propagator 将按指定的顺序调用 PropagatorInjectorExtractor

每个复合 Propagator 将实现一个特定的 Propagator 类型,例如 TextMapPropagator ,因为不同的 Propagator 类型可能会对不同的数据类型进行操作。

必须有函数来完成以下操作

  • 创建复合传播器
  • 从复合传播器中提取
  • 注入到复合传播器中

创建复合传播器

必需的参数:

  • Propagator 列表或 InjectorExtractor 列表

返回具有指定 Propagator 的新的复合 Propagator

从复合传播器中提取

必需的参数:

  • Context
  • 传播字段的载体

如果 TextMapPropagatorExtract 实现接受可选的 Getter 参数,则以下参数是必需的,否则是可选的

  • 用于获取每个传播的键的 Getter 实例。

注入到复合传播器中

必需的参数:

  • Context
  • 传播字段的载体

如果 TextMapPropagatorInject 实现接受可选的 Setter 参数,则以下参数是必需的,否则是可选的

  • 设置传播键值对的 Setter ,传播器可以多次调用它来设置多个键值对。

全局传播器

OpenTelemetry API 必须提供一种方法来为每种支持的 Propagator 获取传播器。插桩库应该调用传播器来提取和注入所有远程调用的上下文。

OpenTelemetry API 必须使用无操作传播器,除非另行显式配置。上下文传播可以用于各种信号(traces、metrics、logging 等),因此可以为其独立地启用上下文传播。

获取全局传播器

每种支持的 Propagator 类型都必须存在该方法。

返回全局 Propagator ,通常是复合实例。

设置全局传播器

每种支持的 Propagator 类型都必须存在该方法。

设置全局 Propagator 实例。

必需的参数:

  • Propagator ,通常是复合实例。

传播器分发

OpenTelemetry 组织必须维护的传播器的官方列表,必须作为 OpenTelemetry 扩展包分发:

这是一个可以作为 OpenTelemetry 扩展包维护和分发的附加传播器列表:

  • OT Trace OpenTracing 基础 Tracer 使用的传播器格式。不能在生成的传播器名称中使用 OpenTracing ,因为在 OpenTracing 生态系统中没有被广泛使用。

特定于供应商的协议实现的 Propagator 不能作为 OpenTelemetry 核心仓库的一部分进行维护或分发,例如 AWS 的 X-Ray trace header protocol 。

B3 要求

B3 既有单头编码也有多头编码(single and multi-header encodings),它还具有不直接映射到 OpenTelemetry 的语义,例如调试跟踪标志(debug trace flag),并允许来自请求双方的 span 共享相同的 id 。为了最大化 OpenTelemetry 和 Zipkin 实现之间的兼容性,为 B3 上下文传播建立了以下指导原则。

B3 提取

当提取 B3 时,传播器:

  • 必须尝试提取 B3 编码使用的单头或多头格式,弹头版本优先于多头版本
  • 如果接收到调试跟踪标志,则必须保留,并将其与后续请求一起传播。此外,当设置调试标志时。OpenTelemetry 实现必须设置采样跟踪标志。
  • 不能重复使用 X-B3-SpanId 作为服务器端 span 的 id 。

B3 注入

当注入 B3 时,传播器:

  • 必须默认使用弹头格式注入 B3
  • 必须提供配置来改变默认的注入格式为 B3 多头格式
  • 不传播 X-B3-ParentSpanId ,因为 OpenTelemetry 不支持在请求的双方重用相同的 id

行李(Baggage)

追踪(Tracing)

Tracing API 由以下主要的几个类组成:

  • TraceProvider 是 API 的入口点,提供对 Tracer 的访问。
  • Tracer 是负责创建 Span 的类。
  • Span 是追踪操作的 API 。

数据类型

时间(Time)

OpenTelemetry 可以对时间值进行操作,精度可达纳秒级别。

时间戳(Timestamp)

时间戳是自 Unix 纪元以来经过的时间。

  • 最小精度是毫秒
  • 最大精度是纳秒

持续时间(Duration)

两个事件之间经过的时间。

  • 最小精度是毫秒
  • 最大精度是纳秒

TracerProvider

可以使用 TracerProvider 访问 Tracer

在 API 实现中,TraceProvider 应该是保存任何配置的有状态对象。

通常,TraceProvider 被期望从一个中心位置访问,因此 API 应该提供一种设置/注册和访问全局默认 TraceProvider 的方法。

尽管有全局的 TracerProvider,一些应用程序可能想要或必须使用多个 TracerProvider 实例,例如每个实例有不同的配置(比如 SpanProcessor以及从它们获得 Tracer ),或者因为依赖注入框架更容易。因此, TracerProvider 的实现应该允许创建任意数量的 TracerProvider 实例。

TracerProvider 操作

TracerProvider 必须提供以下方法:

  • 获取 Tracer

获取 Tracer

这个 API 继续接受以下参数

  • name(必须):这个名称必须标识工具库(例如,io.opentelemetry.contrib.mongodb)。如果一个应用程序或库有内置的 OpenTelemetry 工具,那么 Instrumented libraryInstrumentation library 都可以引用同一个库。在这种情况下,name 表示应用程序或库中的模块名或组件名。为了防止指定无效的名称(null 或空字符串),必须返回有效的 Tracer 实现回退,而不是返回 null 或抛出异常,其 name 属性应该保持原来的无效值,并且应该记录报告指定值无效的信息。一个实现 OpenTelemetry API 的库如果不支持命名功能(例如,一个与可观察性无关的实现),也可能会忽略这个名称并为所有调用返回一个默认实例。如果应用程序所有者配置 SDK 来阻止由这个库产生的遥测(telemetry),那么 TracerProvider 也可以在这里返回一个无操作的 Tracer 。
  • version(可选):指定工具库版本(例如,1.0.0

该函数是否或在何种条件下返回相同或不同的 Tracer 实例是未指定的。

实现不能要求重复获取具有相同名称+版本的 Tracer 来获取配置更改。这可以通过允许使用过时的配置或确保新配置应用于之前返回的 Tracer 来实现。

注意:例如,这可以通过在 TracerProvider 中存储任何可变配置,并让 Tracer 实现的对象具有获取它们的 TracerProvider 的引用来实现。如果必须存储每个 tracer 的配置(例如禁用某个特定的 tracer),tracer 可以在 TracerProvider 中做一个查找名称+版本的映射,或者 TracerProvider 可以维护一个注册表返回所有的Tracer 并根据配置的改变动态更新。

Context Interaction

此部分定义了与 Context 交互的 Tracing API 中的所有操作。

API 必须提供以下功能来支持与 Context 实例的交互:

  • Context 实例中提取 Span
  • Context 实例注入 Span

上述功能是必须的,因为 API 使用者不应该访问 Tracing API 实现所使用的 Context 键。

如果语言支持隐式传播 Context,API 还应该提供以下功能:

  • 从隐式上下文中获取当前活跃的 Span 。等价于获取隐式上下文,然后从上下文中提取 Span
  • 将当前活跃的 Span 设置到隐式上下文。等价于获取隐式上下文,然后将 Span 注入上下文中。

以上所有功能仅在上下文 API 上操作,可以公开为追踪模块的静态方法,或其类中的静态方法。如果可能的话,这个功能应该在 API 中完全实现。

Tracer

tracer 用于生成 Span

注意 Tracer 通常不负责配置,配置应当由 TracerProvider 负责。

Tracer 操作

Tracer 必须提供以下功能:

  • 创建 Span

SpanContext

SpanContext 表示必须与分布式上下文一起序列化和传播的 Span 部分,SpanContext 是不可变的。

OpenTelemetry 的 SpanContext 符合 W3C TraceContext 规范(W3C TraceContext specification),包含两个标识符:TraceIdSpanId,以及一组常见的 TraceFlags 和特定于系统的 TraceState

  • TraceId 有效的追踪标识符,至少有一个非零字节的 16 字节数组
  • SpanId 有效的 Span 标识符,至少有一个非零字节的 8 字节数组
  • TraceFlags 包含关于 trace 的详细信息。与 TraceState 值不同,TraceFlags 存在于所有 trace 中。本规范的当前版本只支持一个名为 sampled 的标志
  • TraceState 携带特定于供应商的标识数据,表现为键值对列表。TraceState 允许多个追踪系统参与同一个 trace 。在 W3C TraceContext 规范中有完整的描述。

API 必须实现创建 SpanContext 的方法,这些方法应该是创建 SpanContext 的唯一方法,该功能必须完全实现,并且不应该被重写。

获取 TraceId 和 SpanId

API 必须允许以以下形式检索 TraceIdSpanId

  • Hex - 返回小写十六进制编码(hex encoded)的 TraceId (必须是 32 十六进制字符的小写字符串)或 SpanId (必须是 16 十六进制字符的小写字符串)
  • Binary - 返回二进制表示形式的 TraceId (必须是 16 字节数组)或 SpanId (必须是 8 字节数组)

API 不应该公开关于内部存储方式的细节。

IsValid

必须提供一个名为 IsValid 的 API ,该 API 返回一个布尔值,如果 SpanContext 有一个非零的 TraceID 和一个非零的 SpanID ,则该值为真。

IsRemote

必须提供一个名为 IsRemote 的 API,该 API 返回一个布尔值,如果 SpanContext 是从远程父对象传播来的,则该值为真。当通过传播器 API 提取 SpanContext 时,IsRemote 必须返回真,对于任何子 Span 的 SpanContext ,必须返回假。

TraceState

TraceStateSpanContext 的一部分,由不可变的字符串键值对表示,并由 W3C TraceContext 规范正式定义。Tracing API 必须在 TraceState 上至少提供以下操作:

  • 获取给定键的值
  • 添加新的键值对
  • 更新给定键的值
  • 删除键值对

这些操作必须遵循 W3C TraceContext 规范中描述的规则。所有的修改操作必须返回一个新的 TraceState 。根据 W3C TraceContext 规范描述的规则,TraceState 必须在任何时候都是有效的,每个变化的操作必须验证输入参数,如果传递的是无效值,则操作不能返回包含无效数据的 TraceState,并且必须遵循通用错误处理准则(general error handling guidelines)。

注意,由于 SpanContext 是不可变的,所以不可能使用新的 TraceState 更新 SpanContext 。这样的更改只有在 SpanContext 传播或遥测数据导出之前才有意义。在这两种情况下,PropagatorSpanExporter 都可以在将其序列化前创建修改过的 TraceState 副本。

Span

Span 表示 trace 中的单个操作。Span 可以嵌套形成 trace 树。每个 trace 都包含一个根 Span,通常用于描述整个操作,并且可以选择描述一个或多个子 Span 或子操作

Span 封装:

  • Span 名称
  • 唯一标识 Span 的不可变的 SpanContext
  • SpanSpanContext 或 null 表示的父 Span
  • SpanKind
  • 开始时间戳
  • 结束时间戳
  • Attributes
  • 链接到其他 SpanLink 列表
  • 带有时间戳的Event 列表
  • Status

Span 名称简明地标识由 Span 所表示的工作,例如,RPC 方法名、函数名、大型计算中的子任务或子截断的名称。Span 名称应该是最通用的字符串,用于标识 Span 的类别,而不是单个 Span 实例,同时仍然是人类可读的。

Span 的开始和结束时间戳反映了操作消耗的实际时间。例如,如果一个 Span 代表一个请求-响应周期(例如 HTTP 或 RPC ),那么 Span 应该有一个与第一个子操作的开始时间相对应的开始时间,以及最后一个子操作完成时的结束时间。这包括:

  • 从请求中接收数据
  • 解析数据(例如,从二进制或 json 格式)
  • 任何中间件或附加的处理逻辑
  • 业务逻辑
  • 构造响应
  • 发送响应

子 Span (在某些情况下是事件)可能被创建用于表示需要更详细的可观察性的子操作。子 Span 应该度量各个子操作的时间,并且可以添加额外的属性。

Span 的开始时间应该设置为创建 Span 时的当前时间,在创建 Span 之后,应该可以修改它的名称、设置属性(Attribute)、添加事件(Event)、设置状态(State)。在设置了 Span 的结束时间之后,不能更改这些参数。

Span 并不意味着要用来在进程中传播信息。为了防止误用,实现不应该提供对 SpanSpanContext 之外的属性的访问。

供应商可以实现 Span 接口来实现特定于供应商的逻辑。但是,替代的实现也不能允许调用者直接创建 Span ,所有 Span 必须ton过 Tracer 创建。

Span 的创建

创建 Span 的唯一操作必须是使用 Tracer

在使用隐式上下文传播的语言中,默认情况下,Span 的创建不能将新的 Span 设置为当前上下文中活跃的 Span ,但这个功能可以作为单独的操作提供。

API 必须接受以下参数:

  • Span 名称,必须。
  • Context 或新 Span 应该是根 Span 的指向。API 还可以默认隐式地将当前的 Context 作为父对象。这个 API 不能接受 SpanSpanContext 作为父对象,只能接受一个完整的 Context
    • 必须根据从上下文中确定父 Span 所描述的规则确定父 Span 。
  • SpanKind 如果没有指定则默认为 SpanKind.Internal
  • Attributes 属性可用于抽样决策(sampling description),如果未指定,则假定为空集合。
    • 只要可能,用户应该在创建 Span 时设置任何已知的属性,而不是之后调用 SetAttribute
  • Link 链接的有序序列。
  • Start timestamp 默认为当前时间。该参数只应在 Span 创建时设置,如果 API 在 Span 逻辑开始时调用,则 API 用户不能显式设置此参数。

每个 Span 有零个或一个父 Span 以及零个或多个子 Span ,表示因果相关的操作。相关 Span 的树构成了 trace 。如果一个 Span 没有父 Span,那么就称它为根 Span 。每个 trace 都包含一个根 Span ,它是所有其他 Span 的共享祖先。实现必须提供一个选项来创建一个 Span 作为根 Span ,并且必须为创建的每个根 Span 生成新的 TraceId 。对于具有相同父 Span 的 Span ,TraceId 必须与父 Span 相同。另外,子 Span 在默认情况下必须继承父 Span 的所有 TraceState 值。

如果 Span 是在另一个进程中创建的 Span 的子 Span ,则该 Span 具有远程父对象(remote parent)。每个传播器的反序列化必须在父 SpanContext 上将 IsRemote 设置为真,以便创建 Span 时了解父对象是否是远程的。

创建的任何 Span 都必须结束。这是用户的责任,如果用户忘记结束 Span ,API 实现可能会泄露内存或其他资源(例如,迭代所有 Span 的周期性工作的 CPU时间)。

从上下文中确定父 Span

当从 Context 中创建新的 Span 时,Context 可能包含表示当前活跃的实例的 Span ,并将其作为父 Span 。如果 Context 中没有 Span ,则新创建的 Span 将是根 Span

SpanContext 不能直接在 Context 中被设置为活跃的,而必须将其包装到 Span 中(wrapping it into a Span)。例如,执行上下文提取的 Propagator 可能需要。

指定链接

Span 创建期间,用户必须能够记录到其他 Span 的链接。链接的 Span 可以来自相同或不同的 trace 。在 Span 创建后不能添加链接。

Link 在结构上由以下属性定义:

  • 要链接的 SpanSpanContext
  • 零个或多个 Attribute,进一步描述链接

创建 Span 的 API 必须提供:

  • 记录单个 Link 的 API,其属性作为参数传递。该操作可以命名为 AddLink 。这个 API 接受要链接的 SpanSpanContext 和可选的 Attribute ,它们可以作为单独的参数,也可以作为封装它们的不可变对象。实现可能会忽略带有无效 SpanContext 的链接。

链接应该保持设置的顺序。

Span 操作

除了检索 SpanSpanContext 和记录状态的函数外,在 Span 完成后不能调用以下任何函数。

Get Context

Span 接口必须提供:

  • 返回给定 SpanSpanContext 的 API 。即使在 Span 完成之后,也可以使用返回值。返回值在整个 Span 的生命周期内必须相同。该操作的名称可以命名为 GetContext

IsRecording

如果 Span 正在记录信息,比如使用 AddEvent 操作的事件、使用 SetAttributes 设置的属性、使用 SetStatus 设置的状态等,则返回真。

Span 结束之后,它通常变为不记录的,因此 IsRecording 应该为结束的 Span 返回假。注意:流式实现无法预期 Span 是否结束,这是一种预期的情况,在结束 Span 之后不能再改变 IsRecording

IsRecording 不应该接受任何参数。

这个标志应该用于避免在绝对没有记录 Span 的情况下对 Span 的属性或事件进行昂贵的计算。注意,任何子 Span 的记录都是独立于该标志的值指定的(通常基于 SpanContextTraceFlagssampled 标志)。

尽管对整个 trace 进行了采样,但这个标志可能是真。这允许记录和处理单个 Span 的信息,而无需将其发送到后端。

API 用户在插桩代码时应该只访问 IsRecording 属性,除非要在上下文传播器中使用,否则有永远不要访问 sampled 标志。

Set Attributes

Span 必须能够设置与其关联的属性。

Span 接口必须提供:

  • 设置单个 Attribute 的 API,其中属性作为参数传递。该方法可以命名为 SetAttribute 。为了避免额外的分配,一些实现可能为每个可能值类型提供单独的 API 。

Span 接口可以提供:

  • 设置多个 Attribute 的 API ,在单个方法调用中传递属性。

设置与现有属性具有相同键的属性时,应该覆盖现有的属性。

注意,OpenTelemetry 项目记录了某些具有指定语义含义的标准属性(standard attributes)。

注意,采样器只能考虑创构建 Span 过程中已经存在的信息。之后的任何更改,包括新的或更改的属性,都不能改变采样器的决策。

Add Events

Span 必须有添加事件的能力。事件有一个与其被添加到 Span 中的时刻相关联的时间。

Event 在结构上由以下属性定义:

  • 事件的名称。
  • 时间的时间戳。添加事件的时间或用户提供的自定义时间戳。
  • 零个或多个 Attribute ,进一步描述事件。

时间应该保持其被记录的顺序。通常会匹配事件时间戳的顺序,但使用自定义时间戳可能会无序地记录事件。

OpenTelemetry 项目记录了某些具有特定语义含义的标准事件名称和键(standard event names and keys)。

注意,RecordExceptionAddEvent 的一个特殊变体,用于记录异常事件。

Set Status

设置 SpanStatus ,如果使用,将覆盖默认的 Span 状态(Unset

Status 在结构上由以下属性定义:

  • StatusCode 下面列出的值之一。
  • Description 可选,提供 Status 的描述信息。Description 只能与错误的 StatusCode 一起使用。空的 Description 等价于不存在。

StatusCode 可以是以下值之一:

  • Unset - 默认状态
  • Ok - 应用程序开发人员或操作人员已验证该操作成功完成
  • Error - 操作包含错误

Span 接口必须提供:

  • 设置 Status 的 API ,该方法可以命名为 SetStatus 。该 API 接受 StatusCode 和一个可选的 Description ,可以作为单独的参数,也可以封装为不可变对象。对于设置为 UnsetOkStatusCode,必须忽略 Description

状态码应该保持为未设置,除了以下的情况:

  • 当插装库将状态设置为 Error 时,状态码应该被记录并可预测。根据语义约定中定义的规则,应该只将状态码设置为 Error ,对于语义约定未涵盖的操作,插装库应该发布自己的约定,包括状态码。

  • 通常,插装库不应该将状态码设置诶 Ok ,除非显式配置。插装库应该将状态码保留为 Unset 除非出现错误。

  • 应用程序开发人员和操作人员可以将状态码设置为 Ok

分析工具应该阻止可能产生的任何错误来响应 Ok 状态,例如,阻止 404 之类的噪声错误。

只有最后一次调用的值将被记录,实现可以自由地忽略之前的调用。

UpdateName

更新 Span 的名称,在此更新之后,任何基于 Span 名称的采样行为都将取决于实现。

注意,采样器只能考虑创建 Span 过程中已有的信息。之后所做的任何更改,包括更新 Span 的名称,都不能修改采样器的决策。

名称更新的替代方法可能是延后 Span 的创建,即当 Span 开始时使用显式时间戳,来自已知的最终 Span 名称,或者报告具有所需名称的 Span 作为子 Span

必需的参数:

  • 新的 Span 名称,将取代在 Span 开始时传入的任何内容。

End

表示当前 Span 现在(或在可选的指定时间)已经结束的信号。

实现应该忽略所有对 End 和其他任何 Span 方法的后续调用。例如,通过结束,Span 变为不记录的(当 Tracer 是流式事件并且没有与 Span 关联的可变状态时可能会出现异常)。

End 不能对子 Span 产生任何影响,它们可能仍然在运行,可以稍后再结束。

End 不能在任何活跃的 SpanContext 中禁用 Span 。必须仍能够通过包含结束的 Span 的上下文,将其作为父 Span 使用。此外,在 Span 结束后将其放入上下文的机制必须仍然有效。

参数:

  • 可选的时间戳,显式设置结束时间戳。如果省略,则等于当前时间。

该 API 必须是非阻塞的。

Record Exception

为了便于记录异常,应该提供 RecordException 方法,这是 AddEvent 的一种特殊变体,与其要求相同。

方法的签名由语言确定,并且可以适当地重载。该方法必须按照异常语义约定文档(exception semantic conventions)中列出的约定将异常记录为事件。所需的最小参数应该不超过一个异常对象。

如果提供了 RecordException ,该方法必须接受一个可选参数,以提供任何附加的事件属性(与 AddEvent 方法相同)。如果方法已经生成了具有相同的名称的属性,则附加属性优先。

注意:RecordException 可能被视为 AddEvent 的变体,具有额外的特定于异常的参数,所有其他参数都是可选的(因为它们有来自异常语义约定的默认值)。

Span 生命周期

Span 生命周期表示将开始和结束时间戳记录到 Span 对象的过程:

  • 在创建 Span 时记录开始时间
  • 在结束 Span 时记录结束时间

开始和结束时间以及时间的时间戳必须在调用相应的 API 时记录。

将 SpanContext 包装在 Span 中

API 必须提供一个操作,用于将 SpanContext 包装为实现 Span 接口的对象。这样做是为了在进程内传播 Span 等操作中将 SpanContext 暴露为 Span

如果需要新的类型来支持此操作,则不应该尽可能公开它(例如,只公开返回具有 Span 接口类型的函数)。如果需要公开暴露新的类型,则应该将其命名为 NonRecordingSpan

行为定义如下:

  • GetContext 必须返回被包装的 SpanContext
  • IsRecording 必须返回假表示事件、属性和其他没有被记录的元素(例如,正在被删除)

Span 的其余功能必须定义为是无操作的。注意:这包括 End,因此作为例外,不需要(甚至没有帮助)结束这样的 Span 。

这个功能必须在 API 中完全实现,并且不应该被重写。

SpanKind

SpanKind 描述了在一次追踪中 Span 及其父子之间的关系。SpanKind 描述了两种独立的特性,有助于追踪系统的分析过程。

SpanKind 描述的第一个属性反映了 Span 是远程子对象还是父对象。具有远程父对象的 Span 是外部负载的源,具有远程子对象的 Span 反映了非本地系统的依赖关系。

SpanKind 描述的第二个属性反应了子 Span 是否表示同步调用。当子 Span 是同步调用的,通常其父 Span 需要等待它完成。对追踪系统来说,了解这个属性是很有用的,因为同步 Span 可能会导致整个追踪的延迟。异步场景可能是远程的也可能是本地的。

为了使 SpanKind 有意义,调用者应该保证单个 Span 只有一个目的。例如,服务端的 Span 不应该直接用作另一个远程 Span 的父对象。插桩应该在提取和序列化远程调用的 SpanContext 之前创建一个新的 Span 。

以下是可能的 SpanKind

  • SERVER 表示 Span 涵盖同步 RCP 或其他远程请求的服务端处理,该 Span 是远程期待响应的 CLIENT Span 的子 Span 。
  • CLIENT 表示 Span 描述了对某些远程服务的同步请求。该 Span 是远程 SERVER 的父 Span ,并等待其响应
  • PRODUCER 表示 Span 描述了异步请求的父对象。这个父级 Span 应该在对应的子 CONSUMER Span 之前结束,甚至可以在其开始前结束。在使用批处理的消息传递场景中,追踪单个消息需要为每个消息创建新的 PRODUCER Span 。
  • CONSUMER 表示 Span 描述异步 PRODUCER 请求的子对象。
  • INTERNAL 默认值,Span 表示应用程序的内部操作,而不是具有远程父对象或子对象的操作。
SpanKind Synchronous Asynchronous Remote Incoming Remote Outgoing
CLIENT yes yes
SERVER yes yes
PRODUCER yes maybe
CONSUMER yes maybe
INTERNAL

Concurrency

对于支持并发执行的语言,Tracing API 提供了特定的保证和安全。并非所有 API 函数的并发调用都是安全的。

  • TracerProvider - 所有方法都可以安全地并发调用
  • Tracer - 所有方法都可以安全地并发调用
  • Span - 所有方法都可以安全地并发调用
  • Event - 事件是不可变的,可以安全地并发调用
  • Link - 链接时不可变得,可以安全地并发调用

度量(Metrics)

OpenTelemetry 客户端设计原则

本文档定义了一些共同的原则,将帮助设计者构造 OpenTelemetry 客户端,这些客户端易于使用,在所有受支持的语言中都是统一的,同时也为特定语言的表达提供了足够的灵活性。

OpenTelemetry 客户端有望提供开箱即用的全部功能,并允许通过扩展进行创新和实验。

要求

  1. OpenTelemetry API 必须良好定义,并与实现清晰地分离。这允许最终用户使用 API 而不用实现。
  2. 在代码中添加第三方库和框架将只依赖于 OpenTelemetry 客户端的 API 。第三方库和框架的开发人员不关心(也不知道)最终应用程序中使用了 OpenTelemetry 的什么具体实现。
  3. 最终应用程序的开发者通常决定如何配置 OpenTelemetry SDK 和使用什么扩展。他们也可以选择不使用任何 OpenTelemetry 的实现,及时应用程序和/或它的库已经被插装过了。其基本原理是,使用 OpenTelemetry 的第三方库和框架必须在不使用 OpenTelemetry 的应用程序中仍然完全可用(因此这就消除了开发人员对其框架的插装和无插装版本的需要)。
  4. SDK 必须清晰地划分为独立于线路协议(wire protocol-independent)部分,它们实现公共逻辑(例如,批处理、通过进程信息添加标记等)和依赖于协议的遥测数据导出器。遥测数据导出器必须包含最低限度的功能,从而使供应商能够轻松地添加对其特定协议的支持。
  5. SDK 实现必须包括以下导出器
    • OTLP
    • Jaeger
    • Zipkin
    • Primetheus
    • 用于调试和测试的标准输出(或日志),以及作为各种日志代理工具的输入
    • 内存(模拟)导出器,在本地内存中积累遥测数据,并允许检查,对单元测试很有用

注意:其中一些支持多个协议(例如 gRPC、Thrift 等)。要在导出器中实现的协议是待定的。

其他特定于供应商的导出器不应该包括在 OpenTelemetry 客户端中,而应该放在其他地方。

OpenTelemetry 客户端通用设计

箭头表示调用

预期使用方式

OpenTelemetry 客户端由 4 种类型的包组成:API、SDK、语义约定、插件。API 和 SDK 基于信号类型被分成多个包(例如 API-trace、API-metric、SDK-trace、SDK-metric),只要 API 与 SDK 保持分离,就将其视为实现细节。

想要用 OpenTelemetry 进行插装的库、框架和应用程序只依赖于 API 包。这些第三方库的开发者将调用 API 来生成遥测数据。

使用 OpenTelemetry API 插装的第三方库的应用程序可以控制是否安装 SDK 和生成遥测数据。当没有安装 SDK 时,API 调用应该是无操作的,产生最小开销。

为了启用遥测技术,应用程序必须依赖 OpenTelemetry SDK 。应用程序必须配置导出程序和其他插件,以便能够正确地生成遥测数据,并将其传递给选择的分析工具。插件如何启用和配置的细节与语言有关。

API 和最小实现

API 包是自依赖的,在这个意义上,如果最终用户的应用程序或第三方库只依赖 API 包,没有安装完整的 SDK ,那么应用程序的构建和运行仍然不会失败,尽管没有遥测数据被传递给后端。

这种自依赖是通过以下途径实现的。

API 依赖包含 API 的最小实现。当应用程序中没有明确包含其他实现时,就不会收集遥测数据。

从 API 的最小实现返回的值是有效的,不需要调用者执行额外的检查(例如,createSpan 方法不应该失败,应该返回一个有效的非空 Span 对象)。调用者不需要知道和担心最小实现是否有效,这将最小化插装代码中的模板文件和错误处理。

最小实现尽可能导致少的性能损失,使用 OpenTelemetry 的第三方框架和库希望使用 OpenTelemetry 的这些库的开销可以忽略不计。

SDK 实现

SDK 实现是一个单独的(可选的)依赖项。当它被插入时,会替换 API 包中的最小实现(替换机制依赖于实现用的语言)。

SDK 实现了将 API 调用转换为可导出的遥测数据所需的核心功能。

SDK 定义了导出接口,负责将遥测数据发送到后端的特定协议导出器必须实现这个接口。

SDK 还包括可选的帮助导出器,如果需要,可以组合这些导出器以实现额外的功能。

库设计者需要根据这个通用规范(Span Exporter)定义特定于语言的导出器接口。

协议导出器

遥测后端供应商期望实现导出器接口。通过 Export 函数接收的数据应该被序列化,并以特定于供应商的方式发送到后端。

鼓励供应商尽可能使特定于协议的导出器保持简单,并实现理想的附加功能,例如,使用 SDK 提供的帮助程序进行排队和重试。

最终用户应该能够灵活地做出许多决定,包括排队、重试、标记、批处理功能,这些功能对他们的应用程序最有意义。例如,如果应用程序的遥测数据必须传递给无法保证可用性的远程后端,那么最终用户可以选择使用持久的本地队列和 Exporter 在失败时重试发送。与将遥测数据传递到本地运行的代理守护进程的应用程序不同,最终用户可能更喜欢使用更简单的导出配置,而不需要重试或排队。

如果 SDK 的其他导出器是作为单独的库提供的,那么库的名称应该根据命名约定加上 “OpenTelemetry” 和 “Exporter” 。例如:

  • Python 和 Java:opentelemetry-exporter-jaeger
  • Javascript:@opentelemetry/exporter-jeager

资源发现

鼓励云供应商提供检测环境中资源信息的包,这些必须在 SDK 之外实现。

替代实现

最终用户的应用程序可能决定依赖于替代实现。SDK 为实现的灵活性和扩展性提供了许多实现。在开发替代实现之前,请回顾 OpenTelemetry 提供的扩展。

替代实现的一个用例是自动化测试。可以在自动化测试期间插入模拟实现。例如,它可以将所有生成的遥测数据存储在存储器中,并提供检查存储数据的能力。这将允许测试验证是否正确生成了遥测数据。鼓励 OpenTelemetry 客户端的作者提供这样的模拟实现。

注意,使用 SDK 和模拟 Exporter 也可以进行模拟,而不需要替换整个 SDK 。

所选择的模拟方法将取决于测试目标,以及在测试过程中需要截取的具体的遥测数据。

版本标记

API 包和 SDK 包必须使用语义版本编号。API 包版本号和 SDK 包版本号是解耦的,可以是不同的(也可以与其实现的规范版本号不同)。API 包和 SDK 包必须标注自己的版本号。

版本号的解耦允许 OpenTelemetry 客户端作者独立发布 API 包和 SDK 包,而不需要与规范协调和匹配版本号。

因为 API 包和 SDK 包的版本号没有耦合,所以每个 API 和 SDK 包发布时都必须清楚地提到它们实现的规范版本号。此外,如果特定版本的 SDK 包只与特定版本的 API 包兼容,那么 OpenTelemetry 客户端作者也必须发布这个兼容性信息。OpenTelemetry 客户端作者必须在发布说明中包括这些信息。例如,SDK 包发布说明可能会说明:SDK 0.3.4 与API 0.1.0 一起使用,实现了 OpenTelemetry 规范 0.1.0 。

性能和屏阻塞

参阅 Performance and Blocking 了解 API 实现应该满足的性能期望及其策略,以及应该如何记录在负载下的行为。

并发和线程安全

请参考单独的 API 规范,了解 API 实现应该提供哪些并发安全,以及应该如何记录这些安全:

参阅