追踪信息

追踪信息(Traces) 能够给我们展示当一个请求发送到应用程序时发生了什么。无论你的应用程序是一个拥有单个数据库的单体应用程序还是一个复杂的服务网格,追踪信息对于理解请求在应用程序中所经过的完整路径非常重要。

让我们通过三个工作单元来探索一下,它们表示为 Spans

hello span:

{
  "name": "hello",
  "context": {
    "trace_id": "0x5b8aa5a2d2c872e8321cf37308d69df2",
    "span_id": "0x051581bf3cb55c13"
  },
  "parent_id": null,
  "start_time": "2022-04-29T18:52:58.114201Z",
  "end_time": "2022-04-29T18:52:58.114687Z",
  "attributes": {
    "http.route": "some_route1"
  },
  "events": [
    {
      "name": "Guten Tag!",
      "timestamp": "2022-04-29T18:52:58.114561Z",
      "attributes": {
        "event_attributes": 1
      }
    }
  ]
}

这是根跨度(root span),表示整个操作的开始和结束。请注意,它具有一个 trace_id 字段表示追踪信息,但没有 parent_id。这是你知道它是根跨度的方式。

hello-greetings span:

{
  "name": "hello-greetings",
  "context": {
    "trace_id": "0x5b8aa5a2d2c872e8321cf37308d69df2",
    "span_id": "0x5fb397be34d26b51"
  },
  "parent_id": "0x051581bf3cb55c13",
  "start_time": "2022-04-29T18:52:58.114304Z",
  "end_time": "2022-04-29T22:52:58.114561Z",
  "attributes": {
    "http.route": "some_route2"
  },
  "events": [
    {
      "name": "hey there!",
      "timestamp": "2022-04-29T18:52:58.114561Z",
      "attributes": {
        "event_attributes": 1
      }
    },
    {
      "name": "bye now!",
      "timestamp": "2022-04-29T18:52:58.114585Z",
      "attributes": {
        "event_attributes": 1
      }
    }
  ]
}

这个跨度(Span)封装了特定的任务,比如问候语,它的父级是 hello 跨度。请注意,它与根跨度共享相同的 trace_id,表示它是同一追踪的一部分。此外,它有一个 parent_id 字段,与 hello 跨度的 span_id 相匹配。

hello-salutations span:

{
  "name": "hello-salutations",
  "context": {
    "trace_id": "0x5b8aa5a2d2c872e8321cf37308d69df2",
    "span_id": "0x93564f51e1abe1c2"
  },
  "parent_id": "0x051581bf3cb55c13",
  "start_time": "2022-04-29T18:52:58.114492Z",
  "end_time": "2022-04-29T18:52:58.114631Z",
  "attributes": {
    "http.route": "some_route3"
  },
  "events": [
    {
      "name": "hey there!",
      "timestamp": "2022-04-29T18:52:58.114561Z",
      "attributes": {
        "event_attributes": 1
      }
    }
  ]
}

这个跨度表示追踪中的第三个操作,与前一个跨度相同,是 ‘hello’ 跨度的子级。这也使它成为 hello-greetings 跨度的同级。

这三个 JSON 块都具有相同的 trace_id,而 parent_id 字段表示了一个层次结构。这就形成了一个追踪信息!

另外一个你会注意到的是,每个跨度看起来都像是一个结构化日志。这是因为它实际上就是!追踪信息的一种思考方式是它们是一组具有上下文、关联、层次结构等特性的结构化日志。然而,这些 “结构化日志” 可以来自不同的进程、服务、虚拟机、数据中心等。这就是追踪信息能够表示任何系统的端到端视图的原因。

为了理解 OpenTelemetry 中追踪的工作原理,让我们来看一下在我们的代码中进行仪表化所需的一系列组件。

Tracer 提供者

Tracer 提供者(有时称为 TracerProvider)是 Tracer 的工厂。在大多数应用程序中,Tracer 提供者只会初始化一次,并且其生命周期与应用程序的生命周期相匹配。Tracer 提供者的初始化还包括 Resource 和 Exporter 的初始化。这通常是在使用 OpenTelemetry 进行追踪的第一步。在某些语言的 SDK 中,全局的 Tracer 提供者已经为您初始化。

Tracer

Tracer 创建包含有关给定操作(例如服务中的请求)正在发生的更多信息的 spans(跨度)。Tracer 是从 Tracer 提供者创建的。

Trace Exporters

Trace Exporters 将 traces 发送到 consumer(消费者)。这个 consumer 可以是调试和开发时的标准输出,OpenTelemetry Collector,或者您选择的任何开源或供应商后端。

上下文传播

上下文传播是实现分布式追踪的核心概念。有了上下文传播,可以将 spans 相互关联并组装成一个追踪,而不管 spans 是在哪里生成的。我们通过两个子概念来定义上下文传播:Context 和 Propagation。

Context(上下文) 是一个对象,它包含了发送和接收服务之间关联一个 span 所需的信息,并将其与整个追踪关联起来。例如,如果服务 A 调用服务 B,则在上下文中具有标识为 ID 的服务 A 的 span 将用作服务 B 中下一个创建的 span 的父级 span。上下文中的追踪 ID 也将用于服务 B 中下一个创建的 span,表示该 span 是与服务 A 的 span 相同追踪的一部分。

Propagation(传播) 是在服务和进程之间传递上下文的机制。它将上下文对象序列化或反序列化,并提供相关的追踪信息,以将其从一个服务传播到另一个服务中。传播通常由仪表化库处理,对用户来说是透明的,但是如果您需要手动传播上下文,则可以使用传播 API。

OpenTelemetry 支持几种不同的上下文格式。在 OpenTelemetry 追踪中使用的默认格式称为 W3C TraceContext。每个上下文对象都存储在一个 span 中。有关上下文对象和其他可用信息的详细信息,请参见 Span 上下文

通过结合 Context 和 Propagation,您现在可以组装一个 Trace。

有关更多信息,请参见 追踪规范

Spans

Span(跨度) 表示一个工作单元或操作。Spans 是构成追踪信息的基本单元。在 OpenTelemetry 中,它们包括以下信息:

Sample span:

{
  "trace_id": "7bba9f33312b3dbb8b2c2c62bb7abe2d",
  "parent_id": "",
  "span_id": "086e83747d0e381e",
  "name": "/v1/sys/health",
  "start_time": "2021-10-22 16:04:01.209458162 +0000 UTC",
  "end_time": "2021-10-22 16:04:01.209514132 +0000 UTC",
  "status_code": "STATUS_CODE_OK",
  "status_message": "",
  "attributes": {
    "net.transport": "IP.TCP",
    "net.peer.ip": "172.17.0.1",
    "net.peer.port": "51820",
    "net.host.ip": "10.177.2.152",
    "net.host.port": "26040",
    "http.method": "GET",
    "http.target": "/v1/sys/health",
    "http.server_name": "mortar-gateway",
    "http.route": "/v1/sys/health",
    "http.user_agent": "Consul Health Check",
    "http.scheme": "http",
    "http.host": "10.177.2.152:26040",
    "http.flavor": "1.1"
  },
  "events": [
    {
      "name": "",
      "message": "OK",
      "timestamp": "2021-10-22 16:04:01.209512872 +0000 UTC"
    }
  ]
}

Spans 可以嵌套,通过父级 span ID 暗示子操作。这使得 Spans 能够更准确地捕获应用程序中的工作。

Span 上下文

Span 上下文是每个 span 上的不可变对象,包含以下信息:

  • 表示跨度所属的追踪的 Trace ID
  • span 的 Span ID
  • Trace Flags,一个包含有关跟踪的信息的二进制编码
  • Trace State,一个可以携带供应商特定的跟踪信息的键值对列表

由于 Span 上下文包含 Trace ID,因此在创建 Span Links 时会使用它。

属性

属性是包含元数据的键值对,您可以使用它们来注释 Span,以携带有关正在跟踪的操作的信息。

例如,如果一个 Span 跟踪的是在电子商务系统中向用户的购物车添加商品的操作,您可以记录用户的 ID、要添加到购物车的商品的 ID 和购物车的 ID。

属性具有每个语言 SDK 实现的以下规则:

  • 键必须是非空字符串值
  • 值必须是非空字符串、布尔值、浮点值、整数或这些值的数组

此外,还有一些已知的 Semantic Attributes(语义属性),它们是在常见操作中通常存在的元数据的命名约定。尽可能使用语义属性命名非常有帮助,因为可以使常见的元数据在系统中标准化。

跨度事件

跨度事件可以被看作是跨度上的结构化日志消息(或注释),通常用于表示跨度持续时间内有意义的、特定的时间点。

例如,在 Web 浏览器中考虑两种情况:

  1. 跟踪页面加载
  2. 注明页面变为可交互的时刻

跨度最适合用于第一种情况,因为它是一个具有开始和结束的操作。

跨度事件最适合用于跟踪第二种情况,因为它表示一个有意义的、特定的时间点。

跨度链接

跨度链接存在的目的是将一个 span 与一个或多个 span 关联起来,暗示它们之间的因果关系。例如,假设我们有一个分布式系统,其中一些操作通过追踪信息进行跟踪。

作为对某些操作的响应,会异步地排队执行一个附加操作。我们也希望将这个后续操作的追踪与第一个追踪关联起来,但我们无法预测后续操作何时开始,所以我们需要关联这两个追踪,我们将使用一个跨度链接。

您可以将第一个追踪的最后一个 Span 与第二个追踪的第一个 Span 相关联。这样,它们之间就存在因果关系了。

链接是可选的,但它们是将追踪跨度相互关联的一种很好的方式。

跨度状态

一个 span 将附加一个状态。通常,当应用程序代码中存在已知的错误(如异常)时,您将设置一个跨度状态。跨度状态将标记为以下值之一:

  • Unset:未设置
  • Ok:正常
  • Error:错误

当处理一个异常时,跨度状态可以设置为错误。否则,跨度状态处于未设置状态。通过将跨度状态设置为未设置状态,处理跨度的后端现在可以分配最终状态。

跨度类型

当创建一个跨度时,它可以是 ClientServerInternalProducerConsumer 中的一种。跨度类型向跟踪后端提供了一个提示,说明追踪应该如何组装。根据 OpenTelemetry 规范,服务端跨度的父级通常是远程客户端跨度,客户端跨度的子级通常是服务端跨度。类似地,消费者跨度的父级总是制造者,而制造者跨度的子级总是消费者。如果未提供,将假定跨度类型为内部。

有关 SpanKind 的更多信息,请参见 SpanKind(跨度类型)

Client(客户端)

客户端跨度表示同步的出站远程调用,比如出站的 HTTP 请求或数据库调用。请注意,在这里 “同步” 不是指 async/await,而是指其不会被排队以供稍后处理。

Server(服务端)

服务端跨度表示同步的入站远程调用,比如入站的 HTTP 请求或远程过程调用。

Internal(内部)

内部跨度表示不会跨越进程边界的操作。例如,用于函数调用或 Express 中间件的仪表可能使用内部跨度。

Producer(生产者)

生产者跨度表示创建一个作业,该作业可能稍后在异步处理。它可以是一个插入作业队列的远程作业,也可以是由事件侦听器处理的本地作业。

Consumer(消费者)

消费者跨度表示处理由生产者创建的作业,并且可能在生产者跨度结束后很长时间才开始。

最后修改 December 13, 2023: improve glossary translation (46f8201b)