OpenTelemetry Protocol (OTLP) 规范描述了遥测数据从遥测源到中间节点(例如收集器)再到遥测后端之间的编码、传输和投递机制,OTLP 是在 OpenTelemetry 项目范围内设计的通用遥测数据传输协议。
协议详情
OTLP 定义了遥测数据的编码以及用于在客户端和服务端之间交换数据的协议,该规范定义了 OTLP 如何通过 gRPC 和 HTTP 1.1 传输实现,并指定了用于有效负载的 Protocol Buffers 模式。
OTLP 是一种请求/响应式协议:客户端发送请求,服务端作出相应的响应,本文档定义了一种请求和响应类型:Export。
所有服务端组件必须支持以下传输压缩选项:
-
none:没有压缩
-
gzip:gzip压缩
OTLP/gRPC
建立底层 gRPC 传输后,客户端开始使用一元请求发送遥测数据,使用 Export*ServiceRequest 消息(ExportLogsServiceRequest 用于日志,ExportMetricsServiceRequest 用于指标,ExportTraceServiceRequest 用于跟踪),客户端不断向服务器发送一系列请求,并期望收到对每个请求的响应:
注意:该协议关注一对客户端/服务器节点之间传输的可靠性,旨在确保客户端和服务器之间的传输过程中没有数据丢失。许多遥测收集系统都有中间节点,数据必须经过这些中间节点才能到达最终目的地(例如应用程序 -> 代理 -> 收集器 -> 后端),此类系统中的端到端交付保证不在 OTLP 的范围之内。本协议中描述的回执确认发生在单个客户端/服务器对之间,并不包括多跳传递路径中的跨过中间节点的传递。
OTLP/gRPC 并发请求
发送请求后,客户端一直等待,直到从服务器收到响应,在这种情况下,至多只有一个请求在运行中,并且尚未被服务器确认。
当需要简化实现时,以及当客户端和服务器通过非常低延迟的网络连接时,例如当客户端是需要检测的应用程序而服务器是作为本地守护进程(代理)运行的 OpenTelemetry 收集器时,建议使用顺序操作)。
当需要实现高吞吐量的实现应该支持并发一元调用以实现更高的吞吐量,客户端应该发送新请求而不用等待早先发送的请求的响应,实质上创建一个当前正在运行但未确认的请求管道。
并发请求的数量应该是可配置的。
可实现的最大吞吐量为 max_concurrent_requests * max_request_size / (network_latency + server_response_time),例如,如果请求最多可包含 100 个跨度,网络往返延迟为 200 毫秒,服务器响应时间为 300 毫秒,则一个并发请求可实现的最大吞吐量为 100 个跨度/(200 毫秒 + 300 毫秒)或每秒 200 个跨度。很容易看出,在高延迟网络中或当服务器响应时间很高,如果要实现良好的吞吐量时,请求需要非常大或者必须完成大量并发请求。
如果客户端正在关闭(例如,当包含处理数据的进程想要退出时),客户端将选择等待,直到所有挂起的请求均收到确认或特定的超时实现,这保证了遥测数据的传输的可靠性。客户端实现应该公开一个选项来在关机期间打开和关闭等待。
如果客户端无法传递某个请求(例如,等待确认时计时器超时),客户端应该记录数据未传递的事实。
OTLP/gRPC Response
响应必须是适当的消息(请参阅下面的完全成功、部分成功和失败情况下使用的特定消息)。
完全成功
完全成功响应表示遥测数据已被服务器成功接收,如果服务器接收到空请求(不携带任何遥测数据的请求),服务器应该响应成功。
成功时,服务器响应必须是 ExportServiceResponse 消息(跟踪ExportTraceServiceResponse,指标的 ExportMetricsServiceResponse 和日志的 ExportLogsServiceResponse)。
在成功响应的情况下,服务器必须保留 partial_success 字段未设置。
部分成功
如果请求仅被部分接受(例如当服务器仅接受部分数据并拒绝其余数据时),服务器响应必须与完全成功情况下的相同 ExportServiceResponse 消息,另外,服务端必须初始化 partial_success 字段(跟踪的 ExportTracePartialSuccess 消息,度量的 ExportMetricsPartialSuccess 消息和日志的 ExportLogsPartialSuccess 消息),它必须使用它拒绝的跨度/指标点/日志记录的数量来设置相应的 rejected_spans、rejected_data_points 或 rejected_log_records 字段。
服务端应该用人类可读的错误消息填充 error_message 字段,该消息应解释服务器拒绝部分数据的原因,并可能提供有关用户如何解决问题的指导,该协议不会尝试定义错误消息的结构。
即使服务器完全接受请求,服务器也可以使用 partial_success 字段向客户端传达警告/建议,在这种情况下,rejected_<signal> 字段的值必须为 0,并且 error_message 字段必须为非空。
当客户端收到填充了 partial_success 的部分成功响应时,客户端不得重试请求。
失败
当服务器返回错误时,它分为两大类:可重试和不可重试:
-
可重试错误表示遥测数据处理失败,客户端应该记录错误并可能重试导出相同的数据
-
不可重试错误表示遥测数据处理失败,客户端不得重试发送相同的遥测数据,客户端必须丢弃遥测数据。例如,当请求包含错误数据并且服务端无法反序列化或处理时,就会发生这种情况,客户端应该维护此类丢弃数据的计数器
服务端必须使用代码 Unavailable 指示可重试错误,并且可以使用包含 0 RetryDelay 值的 RetryInfo 通过状态提供额外的详细信息。下面一个示例 Go 代码来说明:
// Do this on server side.
st, err := status.New(codes.Unavailable, "Server is unavailable").
WithDetails(&errdetails.RetryInfo{RetryDelay: &duration.Duration{Seconds: 0}})
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 Code | Retryable? |
---|---|
CANCELLED | Yes |
UNKNOWN | No |
INVALID_ARGUMENT | No |
DEADLINE_EXCEEDED | Yes |
NOT_FOUND | No |
ALREADY_EXISTS | No |
PERMISSION_DENIED | No |
UNAUTHENTICATED | No |
RESOURCE_EXHAUSTED | Only if the server can recover (see below) |
FAILED_PRECONDITION | No |
ABORTED | Yes |
OUT_OF_RANGE | Yes |
UNIMPLEMENTED | No |
INTERNAL | No |
UNAVAILABLE | Yes |
DATA_LOSS | Yes |
重试时,客户端应该实施指数退避策略,一个异常是下面解释的限流案例,它提供了关于重试间隔的明确说明。
仅当服务端发信号表明可以从资源耗尽中恢复时,客户端才应将 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 文档中概述的建议:
//描述客户端何时可以重试失败的请求。当错误响应中缺少此信息时,客户可以忽略此处的建议或重试。
//始终建议客户端在重试时使用指数退避。
//客户端应该等到重试之前收到错误响应后的“retry_delay”时间。
//如果重试请求也失败,客户端应使用指数退避方案基于“retry_delay”逐渐增加重试之间的延迟,
//直到达到最大重试次数或达到最大重试延迟上限。
retry_delay 的值由服务端确定并且依赖于实现,服务器应该选择一个足够大的 retry_delay 值,让服务器有时间恢复,但又不会太大而导致客户端在受到限制时丢弃数据。
OTLP/gRPC 服务和 Protobuf 定义
gRPC 服务定义、请求和响应的 Protobuf 定义;请确保检查原型版本和成熟度级别。不同信号的schema可能处于不同的成熟度级别——一些稳定,一些处于测试阶段。
OTLP/gRPC 默认端口
OTLP/gRPC 的默认网络端口是 4317。
OTLP/HTTP
OTLP/HTTP 使用以二进制格式或 JSON 格式编码的 Protobuf 有效负载,无论编码如何,消息的 Protobuf 模式对于此处定义的 OTLP/HTTP 和 OTLP/gRPC 都是相同的。
OTLP/HTTP 使用 HTTP POST 请求将遥测数据从客户端发送到服务端,实现可以使用 HTTP/1.1 或 HTTP/2 传输。如果无法建立 HTTP/2 连接,则使用 HTTP/2 传输的实现应该回退到 HTTP/1.1 传输。
二进制 Protobuf 编码
二进制 Protobuf 编码的有效负载使用 proto3 编码标准,发送二进制 Protobuf 编码有效负载时,客户端和服务器必须设置“Content-Type: application/x-protobuf”请求和响应标头。
JSON Protobuf 编码
JSON Protobuf 编码的有效负载使用 proto3 标准定义的 JSON 映射来实现 Protobuf 和 JSON 之间的映射,与该映射有以下差异:
-
traceId 和 spanId 字节数组表示为不区分大小写的十六进制编码字符串;它们不是标准 Protobuf JSON 映射中定义的 base64 编码。十六进制编码用于所有 OTLP Protobuf 消息中的 traceId 和 spanId 字段,例如 Span、Link、LogRecord 等消息。例如,Span 中的 traceId 字段可以这样表示:{ “traceId”: “5B8EFFF798038103D269B633813FC60C”, … }
-
枚举字段的值必须编码为整数值,与标准 Protobuf JSON 映射不同,后者允许将枚举字段的值编码为整数值或枚举名称字符串,OTLP JSON Protobuf 编码中只允许整数枚举值;不得使用枚举名称字符串,例如,Span 中值为 SPAN_KIND_SERVER 的 kind 字段可以这样表示:{ “kind”: 2, … }。
-
OTLP/JSON 接收器必须忽略具有未知名称的消息字段,并且必须解组消息,就好像未知字段不存在于负载数据中一样,这与 Binary Protobuf unmarshaler 的行为一致,并确保向 OTLP 消息添加新字段不会破坏现有接收器。
-
JSON 对象的键是转换为 lowerCamelCase 的字段名称,原始字段名称不能用作 JSON 对象的键。例如,这是资源的有效 JSON 表示:{ "attributes": {...}, "droppedAttributesCount": 123 },这是无效的表示:{“attributes”:{...},“dropped_attributes_count”:123}。
请注意,根据 Protobuf 规范,JSON 编码有效负载中的 64 位整数被编码为十进制字符串,并且在解码时接受数字或字符串。
发送 JSON Protobuf 编码的负载时,客户端和服务器必须设置“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 消息。
客户端可以 gzip 内容,在这种情况下必须包含“Content-Encoding: gzip”请求标头,如果客户端可以接收到 gzip 编码的响应,则客户端可以包含“Accept-Encoding: gzip”请求标头。
请求的非默认 URL 路径可以在客户端和服务器端配置。
OTLP/HTTP 响应
响应主体必须是适当的序列化 Protobuf 消息(有关在完全成功、部分成功和失败情况下使用的特定消息,请参见下文)。
如果响应主体是二进制编码的 Protobuf 负载,服务端必须设置“Content-Type: application/x-protobuf”标头;如果响应是 JSON 编码的 Protobuf 负载,服务器必须设置“Content-Type: application/json”;服务器必须在响应中使用与在请求中收到的相同的“Content-Type”。
如果请求标头“Accept-Encoding: gzip”出现在请求中,服务器可以对响应进行 gzip 编码并设置“Content-Encoding: gzip”响应标头。
完全成功
成功响应表示遥测数据已被服务器成功接受,如果服务端接收到空请求(不携带任何遥测数据的请求),服务端应该成功响应。
成功时,服务器必须响应 HTTP 200 OK,响应主体必须是 Protobuf 编码的 ExportServiceResponse 消息(用于跟踪的 ExportTraceServiceResponse,用于指标的 ExportMetricsServiceResponse 和用于日志的 ExportLogsServiceResponse)。在成功响应的情况下,服务器必须保留 partial_success 字段未设置。
部分成功
如果请求仅被部分接受(即当服务器仅接受部分数据并拒绝其余数据时),服务器必须以 HTTP 200 OK 响应,响应主体必须是与完全成功案例中相同的 ExportServiceResponse 消息。
此外,服务器必须初始化 partial_success 字段(跟踪的 ExportTracePartialSuccess 消息,度量的 ExportMetricsPartialSuccess 消息和日志的 ExportLogsPartialSuccess 消息),它必须设置相应的 rejected_spans、rejected_data_points 或 rejected_log_records 字段,其中包含它拒绝的跨度/数据点/日志记录的数量。
服务器应该用人类可读的英文错误消息填充 error_message 字段,该消息应解释服务器拒绝部分数据的原因,并可能提供有关用户如何解决问题的指导。该协议不会尝试定义错误消息的结构。
服务器也可以使用 partial_success 字段向客户端传达警告/建议,即使它完全接受了请求,在这种情况下,rejected_<signal> 字段的值必须为 0,并且 error_message 字段必须为非空。
当客户端收到填充了 partial_success 的部分成功响应时,客户端不得重试请求。
失败
如果请求处理失败,服务端必须响应适当的 HTTP 4xx 或 HTTP 5xx 状态码。有关特定失败案例和应使用的 HTTP 状态代码的更多详细信息,请参阅以下部分。
所有 HTTP 4xx 和 HTTP 5xx 响应的响应主体必须是描述问题的 Protobuf 编码状态消息。本规范不使用 Status.code 字段,服务端可以省略 Status.code 字段。客户端不应根据 Status.code 字段更改其行为,但可以记录它以用于故障排除目的。
Status.message 字段应该包含一个面向开发人员的错误消息,如 Status 消息模式中所定义。
服务器可以包含带有附加详细信息的 Status.details 字段。阅读下文了解此字段在每个特定故障案例中可以包含的内容。
服务端应该使用 HTTP 响应状态代码来指示特定错误情况的可重试和不可重试错误。客户端应该将 HTTP 响应状态代码视为可重试或不可重试。收到下表中列出的响应状态代码的请求应该重试,其他 4xx 或 5xx 响应状态代码不得重试。
HTTP response status code |
---|
429 Too Many Requests |
502 Bad Gateway |
503 Service Unavailable |
504 Gateway Timeout |
坏数据
如果由于请求中包含无法解码或无效的数据而导致请求处理失败,并且此类失败是永久性的,那么服务器必须响应 HTTP 400 Bad Request,响应中的 Status.details 字段应该包含一个描述坏数据的 BadRequest。客户端在收到 HTTP 400 Bad Request 响应时不得重试请求。
OTLP/HTTP 限流
如果服务端接收到的请求多于客户端允许的数量或服务端过载,服务端应该以 HTTP 429 请求过多或 HTTP 503 服务不可用来响应,并且可以包含“Retry-After”标头和建议在重试之前的等待时间间隔(以秒为单位)。
客户端应该遵守在“Retry-After”标头中指定的等待间隔(如果存在)。如果客户端收到 HTTP 429 或 HTTP 503 响应并且响应中不存在“Retry-After”标头,则客户端应该在重试之间实施指数退避策略。
所有其他响应
本文档中未明确列出的所有其他 HTTP 响应都应根据 HTTP 规范进行处理。如果服务端在没有返回响应的情况下断开连接,客户端应该重试并发送相同的请求。客户端应该在重试之间实施指数退避策略以避免服务器超过负载。
OTLP/HTTP 连接
如果客户端无法连接到服务端,客户端应该在重试之间使用指数退避策略重试连接,重试之间的间隔必须有随机抖动。
客户端应该在请求之间保持连接。
服务器实现应该在同一端口上接受带有二进制编码的 Protobuf 有效负载的 OTLP/HTTP 和带有 JSON 编码的 Protobuf 有效负载的 OTLP/HTTP 请求,并根据“Content-Type”请求标头将请求多路复用到相应的有效负载解码器。
服务器实现可以在同一端口上接受 OTLP/gRPC 和 OTLP/HTTP 请求,并根据“Content-Type”请求标头将连接多路复用到相应的传输处理程序。
OTLP/HTTP 并发请求
为了获得更高的总吞吐量,客户端可以使用多个并行 HTTP 连接发送请求。在那种情况下,并行连接的最大数量应该是可配置的。
OTLP/HTTP 默认端口
OTLP/HTTP 的默认网络端口是 4318。
实施建议
Multi-Destination Exporting
当一个客户端必须将遥测数据发送到多个目标服务端时,必须考虑一个额外的复杂问题,那就是当其中一台服务器确认数据而另一台服务器(尚未)确认数据时,客户端需要决定如何前进。
在这种情况下,客户端应该为每个目的地实现排队、确认处理和重试逻辑,这可确保服务器不会相互阻塞。队列应该引用共享的、不可变的数据来发送,从而最小化由多个队列引起的内存开销。
这确保所有目标服务端都能接收数据,而不管它们的接收速度如何(在客户端队列大小施加的可用限制内)。
已知限制
请求确认
重复数据
在极端情况下(例如重新连接、网络中断等),如果尚未收到确认,客户端无法知道最近发送的数据是否已交付,客户端通常会选择重新发送此类数据以保证交付,这可能会导致服务器端出现重复数据,这是一个深思熟虑的选择,被认为是遥测数据的正确权衡。
未来版本和互操作性
OTLP 将随着时间的推移而发展和变化,OTLP 的未来版本必须以确保实现不同版本 OTLP 的客户端和服务端可以互操作和交换遥测数据的方式进行设计和实现。旧客户端必须能够与新服务器通信,反之亦然。假设新版本的 OTLP 引入了新功能,这些新功能无法被实现旧版本 OTLP 的节点理解和支持,在这种情况下,协议必须从功能角度回归到最低公分母。
如果可能,必须确保所有未宣布过时的 OTLP 版本之间的互操作性。
OTLP 不使用明确的协议版本编号,OTLP 对不同版本的客户端和服务器的互操作基于以下概念:
-
OTLP(当前和未来版本)定义了一组功能,其中一些是强制性的,而另一些是可选的,客户端和服务器必须实现强制功能,并且可以选择只实现可选功能的一个子集。
-
对于协议的微小更改,鼓励 OTLP 的未来版本和扩展使用 Protobuf 的能力以向后兼容的方式发展消息模式,较新版本的 OTLP 可能会向消息添加新字段,这些字段将被不理解这些字段的客户端和服务器忽略。在许多情况下,仔细设计此类模式更改并正确选择新字段的默认值足以确保不同版本的互操作性,而无需节点明确检测其对等节点具有不同的功能。
-
在未来的 OTEP 中,必须将更重要的更改明确定义为新的可选功能,这种功能应该在建立底层传输后被客户端和服务端实现发现,在未来的 OTEP 中描述,它定义了新的功能,通常可以通过从发现客户端到服务端的请求/响应消息交换来实现。本规范定义的强制性功能是隐含的,不需要发现,支持新的可选功能的实现必须调整其行为以匹配不支持特定能力的对等体的期望。
词汇表
遥测数据交换涉及两方,在本文档中,作为遥测数据来源的一方称为客户端,作为遥测数据目的地的一方称为服务端。
客户端的示例是检测应用程序或遥测收集器的发送端,服务端的示例是遥测后端或遥测收集器的接收端(因此收集器通常既是客户端又是服务端,具体取决于您从哪一侧看)。
客户端和服务端也是一个节点。当提到任何一个时,文档中都会使用该术语。