OTLP 规范 1.9.0

状态:

  • 痕迹、指标和日志信号的稳定版本。
  • 配置文件的开发版本。

OpenTelemetry 协议 (OTLP) 规范描述了遥测数据在遥测源、中间节点(如收集器)和遥测后端之间的编码、传输和传递机制。

OTLP 是在 OpenTelemetry 项目范围内设计的一种通用遥测数据传输协议。

协议详情

OTLP 定义了遥测数据的编码以及用于在客户端和服务器之间交换数据的协议。

本规范定义了 OTLP 如何在 gRPC 和 HTTP 传输上实现,并指定了用于有效负载的 Protocol Buffers schema

OTLP 是一种请求/响应式协议:客户端发送请求,服务器返回相应的响应。本文档定义了一种请求和响应类型:Export

所有服务器组件都必须支持以下传输压缩选项

  • 无压缩,表示为 none
  • Gzip 压缩,表示为 gzip

OTLP/gRPC

建立底层 gRPC 传输后,客户端开始使用单元请求发送遥测数据,使用 Export*ServiceRequest 消息(日志为 ExportLogsServiceRequest,指标为 ExportMetricsServiceRequest,跟踪为 ExportTraceServiceRequest,配置文件为 ExportProfilesServiceRequest)。客户端持续向服务器发送一系列请求,并期望收到每个请求的响应。

Request-Response

注意:此协议关注的是一对客户端/服务器节点之间的传输可靠性,旨在确保数据在客户端和服务器之间传输时不会丢失。许多遥测收集系统都有中间节点,数据必须经过这些节点才能到达最终目的地(例如:应用程序 -> 代理 -> 收集器 -> 后端)。此类系统中端到端的交付保证超出了 OTLP 的范围。本协议中描述的确认发生在单个客户端/服务器对之间,并不跨越多跳传输路径中的中间节点。

OTLP/gRPC 并发请求

发送请求后,客户端可以等待直到收到服务器的响应。在这种情况下,最多只会有一个未被服务器确认的请求在传输中。

Unary

当需要实现简单性,并且客户端和服务器通过低延迟网络连接时(例如,客户端是仪器化应用程序,服务器是作为本地守护进程(代理)运行的 OpenTelemetry Collector),建议采用顺序操作。

需要实现高吞吐量的实现应支持并发单元调用以实现更高的吞吐量。客户端应在不等待先前发送请求的响应的情况下发送新请求,从而实质上创建了当前未确认请求的管道。

Concurrent

并发请求的数量应可配置。

可实现的最大吞吐量为 max_concurrent_requests * max_request_size / (network_latency + server_response_time)。例如,如果一个请求最多可以包含 100 个 span,网络往返延迟为 200 毫秒,服务器响应时间为 300 毫秒,那么使用一个并发请求可实现的最大吞吐量是 100 spans / (200ms+300ms),即每秒 200 个 span。可以很容易地看出,在高延迟网络或服务器响应时间很高的情况下,为了实现良好的吞吐量,请求需要非常大,或者必须进行大量的并发请求。

如果客户端正在关闭(例如,当包含的进程想要退出时),客户端将选择性地等待直到收到所有待处理的确认,或者直到特定于实现的超时到期。这确保了遥测数据的可靠传输。客户端实现应提供一个选项来开启和关闭关闭期间的等待。

如果客户端无法发送某个请求(例如,在等待确认时计时器到期),客户端应记录数据未发送的事实。

OTLP/gRPC 响应

响应必须是适当的消息(有关在 完全成功部分成功失败 情况中使用的具体消息,请参见下文)。

完全成功

成功响应表示遥测数据已由服务器成功接受。

如果服务器收到一个空请求(不包含任何遥测数据的请求),服务器应响应成功。

成功时,服务器响应必须是 Export<signal>ServiceResponse 消息(跟踪为 ExportTraceServiceResponse,指标为 ExportMetricsServiceResponse,日志为 ExportLogsServiceResponse,配置文件为 ExportProfilesServiceResponse)。

在成功响应的情况下,服务器必须将 partial_success 字段留空。

部分成功

如果请求仅被部分接受(即,服务器仅接受部分数据并拒绝其余部分),则服务器响应必须与 完全成功 情况下的 Export<signal>ServiceResponse 消息相同。

此外,服务器必须初始化 partial_success 字段(跟踪为 ExportTracePartialSuccess 消息,指标为 ExportMetricsPartialSuccess 消息,日志为 ExportLogsPartialSuccess 消息,配置文件为 ExportProfilesPartialSuccess),并且必须使用其拒绝的 span/数据点/日志记录/配置文件数量来设置相应的 rejected_spansrejected_data_pointsrejected_log_recordsrejected_profiles 字段。

服务器应在 error_message 字段中填充可读的英文错误消息。该消息应解释服务器为何拒绝部分数据,并可能提供如何解决问题的指导。该协议不试图定义错误消息的结构。

服务器也可以使用 partial_success 字段来向客户端传达警告/建议,即使服务器完全接受该请求。在这种情况下,rejected_<signal> 字段必须为 0,并且 error_message 字段必须非空。

客户端在收到部分成功响应且 partial_success 已填充时,不得重试请求。

失败

当服务器返回错误时,它分为两大类:可重试和不可重试。

  • 可重试错误表示遥测数据处理失败,客户端应记录该错误并可以重试导出相同的数据。例如,当服务器暂时无法处理数据时,可能会发生这种情况。

  • 不可重试错误表示遥测数据处理失败,客户端不得重试发送相同的遥测数据。客户端必须丢弃遥测数据。例如,当请求包含无效数据且无法由服务器反序列化或处理时,可能会发生这种情况。客户端应维护一个此类丢弃数据的计数器。

服务器应使用 Unavailable 代码指示可重试错误,并可通过使用 RetryInfo 通过状态提供附加的 详细信息。以下是示例 Go 代码:

  // Do this on server side.
  st, err := status.New(codes.Unavailable, "Server is unavailable").
    WithDetails(&errdetails.RetryInfo{RetryDelay: &duration.Duration{Seconds: 5}})
  if err != nil {
    log.Fatal(err)
  }

  return st.Err()

为指示不可重试错误,建议服务器使用 InvalidArgument 代码,并可通过使用 BadRequest 通过状态提供附加的 详细信息。如果更合适,可以使用其他 gRPC 状态码。以下是示例 Go 代码片段:

  // Do this on the server side.
  st, err := status.New(codes.InvalidArgument, "Invalid Argument").
    WithDetails(&errdetails.BadRequest{})
  if err != nil {
    log.Fatal(err)
  }

  return st.Err()

服务器可以使用其他 gRPC 码来指示可重试和不可重试错误,如果这些其他 gRPC 码更适合某个特定的错误情况。客户端应根据下表解释 gRPC 状态码为可重试还是不可重试。

gRPC 码可重试?
CANCELLED
UNKNOWN
INVALID_ARGUMENT
DEADLINE_EXCEEDED
NOT_FOUND
ALREADY_EXISTS
PERMISSION_DENIED
UNAUTHENTICATED
RESOURCE_EXHAUSTED仅当服务器能够恢复时(见下文)
FAILED_PRECONDITION
ABORTED
OUT_OF_RANGE
UNIMPLEMENTED
INTERNAL
UNAVAILABLE
DATA_LOSS

重试时,客户端应实现指数退避策略。节流情况(如下所述)是一个例外,它提供了关于重试间隔的明确说明。

客户端应将 RESOURCE_EXHAUSTED 码解释为可重试,前提是服务器发出信号表明可以从资源耗尽中恢复。服务器通过返回包含 RetryInfo状态来发出此信号。在这种情况下,服务器和客户端的行为与 OTLP/gRPC 节流 部分所述完全相同。如果没有返回此类状态,则 RESOURCE_EXHAUSTED 码应被视为不可重试。

OTLP/gRPC 节流

OTLP 允许背压信号。

如果服务器无法跟上从客户端接收数据的速度,它应该将该事实通知客户端。然后,客户端必须自行节流,以避免使服务器过载。

为了在使用 gRPC 传输时发出背压信号,服务器应返回一个错误码为 Unavailable 的错误,并可通过使用 RetryInfo 通过状态提供附加的 详细信息。以下是示例 Go 代码片段:

  // Do this on the server side.
  st, err := status.New(codes.Unavailable, "Server is unavailable").
    WithDetails(&errdetails.RetryInfo{RetryDelay: &duration.Duration{Seconds: 30}})
  if err != nil {
    log.Fatal(err)
  }

  return st.Err()

  ...

  // Do this on the client side.
  st := status.Convert(err)
  for _, detail := range st.Details() {
    switch t := detail.(type) {
    case *errdetails.RetryInfo:
      if t.RetryDelay.Seconds > 0 || t.RetryDelay.Nanos > 0 {
        // Wait before retrying.
      }
    }
  }

当客户端收到此信号时,它应遵循 RetryInfo 文档中概述的建议。

// Describes when the clients can retry a failed request. Clients could ignore
// the recommendation here or retry when this information is missing from the error
// responses.
//
// It's always recommended that clients should use exponential backoff when
// retrying.
//
// Clients should wait until `retry_delay` amount of time has passed since
// receiving the error response before retrying.  If retrying requests also
// fail, clients should use an exponential backoff scheme to increase gradually
// the delay between retries based on `retry_delay` until either a maximum
// number of retries has been reached, or a maximum retry delay cap has been
// reached.

retry_delay 的值由服务器确定,并且依赖于实现。服务器应选择一个足够大的 retry_delay 值,以便服务器有时间恢复,但又不能太大导致客户端在节流期间丢弃数据。

OTLP/gRPC 服务和 Protobuf 定义

gRPC 服务定义 在此

请求和响应的 Protobuf 定义 在此

请务必检查 proto 版本和 成熟度级别。不同信号的 schema 可能处于不同的成熟度级别 - 有些是稳定的,有些是 beta 版。

OTLP/gRPC 默认端口

OTLP/gRPC 的默认网络端口是 4317。

OTLP/HTTP

OTLP/HTTP 使用 Protobuf 有效负载,这些有效负载使用 二进制格式JSON 格式进行编码。无论使用何种编码,消息的 Protobuf schema 对于 OTLP/HTTP 和 OTLP/gRPC 都是相同的,如 此处定义

OTLP/HTTP 使用 HTTP POST 请求将遥测数据从客户端发送到服务器。实现可以(MAY)使用 HTTP/1.1 或 HTTP/2 传输。使用 HTTP/2 传输的实现如果无法建立 HTTP/2 连接,应(SHOULD)回退到 HTTP/1.1 传输。

二进制 Protobuf 编码

二进制 Protobuf 编码的有效负载使用 proto3 编码标准

客户端和服务器在发送二进制 Protobuf 编码的有效负载时,必须(MUST)设置“Content-Type: application/x-protobuf”请求和响应头。

JSON Protobuf 编码

JSON Protobuf 编码的有效负载使用 proto3 JSON 映射标准来映射 Protobuf 和 JSON,但有以下偏离该标准的行为:

  • traceIdspanId 字节数组表示为 不区分大小写的十六进制编码字符串;它们不像标准 Protobuf JSON 映射中定义的那样进行 base64 编码。十六进制编码用于所有 OTLP Protobuf 消息中的 traceIdspanId 字段,例如,SpanLinkLogRecord 等消息。例如,Span 中的 traceId 字段可以表示为:{ “traceId”: “5B8EFFF798038103D269B633813FC60C”, … }

  • 枚举字段的值必须(MUST)编码为整数值。与允许枚举字段值编码为整数值或枚举名称字符串的标准 Protobuf JSON 映射不同,OTLP JSON Protobuf 编码仅允许整数枚举值;不得(MUST NOT)使用枚举名称字符串。例如,Span 中值为 SPAN_KIND_SERVER 的 kind 字段可以表示为:{ “kind”: 2, … }

  • OTLP/JSON 接收器必须(MUST)忽略名称未知的消息字段,并且必须(MUST)像未知字段不存在于有效负载中一样进行消息反序列化。这与二进制 Protobuf 解码器的行为一致,并确保向 OTLP 消息添加新字段不会破坏现有接收器。

  • JSON 对象的键是转换为 lowerCamelCase 的字段名。原始字段名不能用作 JSON 对象的键。例如,这是 Resource 的有效 JSON 表示:{ "attributes": {...}, "droppedAttributesCount": 123 },而这不是有效表示:{ "attributes": {...}, "dropped_attributes_count": 123 }

请注意,根据 Protobuf 规范,JSON 编码的有效负载中的 64 位整数以十进制字符串形式编码,解码时接受数字或字符串。

客户端和服务器在发送 JSON Protobuf 编码的有效负载时,必须(MUST)设置“Content-Type: application/json”请求和响应头。

有关 JSON 有效负载示例,请参阅:OTLP JSON 请求示例

OTLP/HTTP 请求

遥测数据通过 HTTP POST 请求发送。POST 请求的主体是二进制编码的 Protobuf 格式或 JSON 编码的 Protobuf 格式的有效负载。

包含跟踪数据的请求的默认 URL 路径为 /v1/traces(例如,连接到“example.com”服务器时的完整 URL 将是 https://example.com/v1/traces)。请求体是 Protobuf 编码的 ExportTraceServiceRequest 消息。

包含指标数据的请求的默认 URL 路径为 /v1/metrics,请求体是 Protobuf 编码的 ExportMetricsServiceRequest 消息。

包含日志数据的请求的默认 URL 路径为 /v1/logs,请求体是 Protobuf 编码的 ExportLogsServiceRequest 消息。

包含配置文件数据的请求的默认 URL 路径为 /v1development/profiles,请求体是 Protobuf 编码的 ExportProfilesServiceRequest 消息。

客户端可以(MAY)对内容进行 gzip 压缩,在这种情况下,必须(MUST)包含“Content-Encoding: gzip”请求头。如果客户端可以接收 gzip 编码的响应,则可以(MAY)包含“Accept-Encoding: gzip”请求头。

请求的非默认 URL 路径可以(MAY)在客户端和服务器端配置。

OTLP/HTTP 响应

响应体必须(MUST)是适当的序列化 Protobuf 消息(有关在 完全成功部分成功失败 情况中使用的具体消息,请参见下文)。

如果响应体是二进制编码的 Protobuf 有效负载,服务器必须(MUST)设置“Content-Type: application/x-protobuf”头。如果响应是 JSON 编码的 Protobuf 有效负载,服务器必须(MUST)设置“Content-Type: application/json”头。服务器在响应中必须(MUST)使用与请求中收到的相同的“Content-Type”。

如果请求头“Accept-Encoding: gzip”存在于请求中,服务器可以(MAY)对响应进行 gzip 编码,并设置“Content-Encoding: gzip”响应头。

完全成功

成功响应表示遥测数据已由服务器成功接受。

如果服务器收到一个空请求(不包含任何遥测数据的请求),服务器应响应成功。

成功时,服务器必须(MUST)响应 HTTP 200 OK。响应体必须(MUST)是 Protobuf 编码的 Export<signal>ServiceResponse 消息(跟踪为 ExportTraceServiceResponse,指标为 ExportMetricsServiceResponse,日志为 ExportLogsServiceResponse,配置文件为 ExportProfilesServiceResponse)。

在成功响应的情况下,服务器必须将 partial_success 字段留空。

部分成功

如果请求仅被部分接受(即,服务器仅接受部分数据并拒绝其余部分),则服务器必须(MUST)响应 HTTP 200 OK。响应体必须(MUST)与 完全成功 情况下的 Export<signal>ServiceResponse 消息相同。

此外,服务器必须(MUST)初始化 partial_success 字段(跟踪为 ExportTracePartialSuccess 消息,指标为 ExportMetricsPartialSuccess 消息,日志为 ExportLogsPartialSuccess 消息,配置文件为 ExportProfilesPartialSuccess),并且必须(MUST)使用其拒绝的 span/数据点/日志记录/配置文件数量来设置相应的 rejected_spansrejected_data_pointsrejected_log_recordsrejected_profiles 字段。

服务器应在 error_message 字段中填充可读的英文错误消息。该消息应解释服务器为何拒绝部分数据,并可能提供如何解决问题的指导。该协议不试图定义错误消息的结构。

服务器也可以(MAY)使用 partial_success 字段来向客户端传达警告/建议,即使它完全接受该请求。在这种情况下,rejected_<signal> 字段必须(MUST)为 0,并且 error_message 字段必须(MUST)非空。

客户端在收到部分成功响应且 partial_success 已填充时,不得重试请求。

失败

如果请求处理失败,服务器必须(MUST)响应适当的 HTTP 4xxHTTP 5xx 状态码。有关特定失败情况和应使用的 HTTP 状态码的更多详细信息,请参阅以下各节。

所有 HTTP 4xxHTTP 5xx 响应的响应体必须(MUST)是 Protobuf 编码的 Status 消息,该消息描述了问题。

此规范不使用 Status.code 字段,并且服务器可以(MAY)省略 Status.code 字段。不期望客户端根据 Status.code 字段更改其行为,但可以(MAY)为其记录以进行故障排除。

Status.message 字段应(SHOULD)包含一个开发者可读的错误消息,如 Status 消息 schema 中定义的。

服务器可以(MAY)包含 Status.details 字段以提供附加详细信息。有关此字段在每种特定失败情况下的内容,请阅读下文。

服务器应(SHOULD)使用 HTTP 响应状态码来指示特定错误情况下的可重试和不可重试错误。客户端应(SHOULD)遵守 HTTP 响应状态码作为可重试或不可重试。

可重试响应代码

收到下表中列出的响应状态码的请求应(SHOULD)被重试。所有其他 4xx5xx 响应状态码不得(MUST NOT)重试。

HTTP 响应状态码
429 Too Many Requests
502 Bad Gateway
503 Service Unavailable
504 Gateway Timeout
无效数据

如果请求处理失败,因为请求包含无法解码或无效的数据,并且这种失败是永久性的,那么服务器必须(MUST)响应 HTTP 400 Bad Request。响应中的 Status.details 字段应(SHOULD)包含一个 BadRequest,描述了无效数据。

当客户端收到 HTTP 400 Bad Request 响应时,不得(MUST NOT)重试请求。

OTLP/HTTP 节流

如果服务器收到的请求超过了客户端允许的范围,或者服务器过载,服务器应响应 HTTP 429 Too Many RequestsHTTP 503 Service Unavailable,并可包含 “Retry-After” 头部,其中包含建议的重试间隔(秒)。

如果响应中存在“Retry-After”头部,客户端应遵守其中指定的等待间隔。如果客户端收到可重试的错误代码(参见上表)且响应中没有“Retry-After”头部,则客户端应在重试之间实施指数退避策略。

所有其他响应

本文档中未明确列出的所有其他 HTTP 响应,应根据 HTTP 规范进行处理。

如果服务器在未返回响应的情况下断开连接,客户端应重试并发送相同的请求。客户端应在重试之间实施指数退避策略,以避免使服务器过载。

OTLP/HTTP 连接

如果客户端无法连接到服务器,客户端应使用指数退避策略在重试之间重试连接。重试间隔必须包含随机抖动。

客户端应在请求之间保持连接活动。

服务器实现应在同一端口上接受 OTLP/HTTP(带二进制编码 Protobuf 有效负载)和 OTLP/HTTP(带 JSON 编码 Protobuf 有效负载)请求,并根据“Content-Type”请求头部将请求多路复用到相应的有效负载解码器。

服务器实现可选择在同一端口上接受 OTLP/gRPC 和 OTLP/HTTP 请求,并根据“Content-Type”请求头部将连接多路复用到相应的传输处理程序。

OTLP/HTTP 并发请求

为了实现更高的总吞吐量,客户端可以发送使用多个并行 HTTP 连接的请求。在这种情况下,最大并行连接数应可配置。

OTLP/HTTP 默认端口

OTLP/HTTP 的默认网络端口是 4318。

实现建议

多目的地导出

当一个客户端必须将遥测数据发送到多个目标服务器时,必须考虑一个额外的复杂性。当其中一个服务器确认了数据而另一个服务器(尚未)确认时,客户端需要决定如何进行。

在这种情况下,客户端应为每个目的地实现队列、确认处理和重试逻辑。这确保了服务器之间不会相互阻塞。队列应引用共享的、不可变的数据进行发送,从而最大限度地减少因拥有多个队列而造成的内存开销。

Multi-Destination Exporting

这确保了所有目标服务器都能接收到数据,而不管它们接收的速度如何(在客户端队列大小所施加的可用限制内)。

空遥测信封

在某些情况下,可能会出现没有内容的遥测信封。例如,一个 ResourceMetrics 中没有 ScopeMetrics,一个 ResourceMetrics 中没有 Metrics,或者 Logs 或 Spans 的等效情况。这可能发生的一种方式是过滤规则删除了所有包含的数据点,但也有其他方式。

实际上,这种空的信封通常会被现有实现丢弃。鉴于此,发送者不应创建空的信封(OTLP 有效负载包含零个 spans、零个 metric points 或零个 log records),接收者可以忽略空的信封,并且实现接收和发送(转发)OTLP 有效负载的节点可以丢弃空的信封。

已知限制

请求确认

重复数据

在边缘情况(例如,重新连接、网络中断等)下,如果尚未收到确认,客户端无法知道最近发送的数据是否已送达。客户端通常会选择重新发送这些数据以确保送达,这可能导致服务器端出现重复数据。这是一个有意的选择,被认为是遥测数据的正确权衡。

未来版本和互操作性

OTLP 将随着时间的推移而发展和改变。OTLP 的未来版本必须以一种确保实现不同版本的 OTLP 的客户端和服务器能够互操作和交换遥测数据的方式进行设计和实现。旧客户端必须能够与新服务器通信,反之亦然。如果 OTLP 的新版本引入了实现旧版本 OTLP 的节点无法理解和支持的新功能。在这种情况下,协议必须在功能上回退到最低的公分母。

在可能的情况下,必须确保所有未声明为废弃的 OTLP 版本之间的互操作性。

OTLP 不使用显式的协议版本号。OTLP 不同版本客户端和服务器的互操作性基于以下概念:

  1. OTLP(当前和未来版本)定义了一组能力,其中一些是强制性的,而另一些是可选的。客户端和服务器必须实现强制性能力,并且可以选择仅实现可选能力的一个子集。

  2. 对于协议的次要更改,鼓励 OTLP 的未来版本和扩展使用 Protobuf 的能力以向后兼容的方式演进消息模式。新版本的 OTLP 可能会向消息添加新字段,这些字段将被不理解这些字段的客户端和服务器忽略。在许多情况下,仔细设计此类模式更改以及正确选择新字段的默认值就足以确保不同版本之间的互操作性,而无需节点显式检测到其对等节点具有不同的能力。

  3. 更重大的更改必须在未来的 OTEP 中明确定义为新的可选能力。在建立底层传输后,客户端和服务器实现应发现此类能力。确切的发现机制应在定义新能力的未来 OTEP 中描述,并且通常可以通过客户端到服务器的发现请求/响应消息交换来实现。本规范定义的强制性能力是隐含的,不需要发现。支持新可选能力的实现必须调整其行为以匹配不支持特定能力的不对等节点。

术语表

遥测数据交换涉及 2 方。在本文档中,遥测数据的来源方称为 Client,遥测数据的目标方称为 Server

Client-Server

Client 的示例是仪器化应用程序或遥测收集器的发送端,Server 的示例是遥测后端或遥测收集器的接收端(因此 Collector 通常既是 Client 也是 Server,取决于你看待的角度)。

Client 和 Server 也是 Node。本文档在提及其中任何一个时都使用此术语。

参考