泽注:这是一个系列,共分成6部分,这是第2部分。翻译自:https://trstringer.com/otel-part2-instrumentation/
这是整个系列博客的第二部分。在上一部分,我们介绍了OpenTelemetry是由什么组成的。本文将更准确的讨论OTel是如何收集遥测数据并追踪数据的。
手工探测(Manual instrumentation)
当我们讨论“探测”,我们实际在讨论收集追踪数据这个行为。有两种方法实现收集:手工和自动(下文会讨论)。正如这个小标题所写,手工探测指的是我们明确地告诉软件要暴露哪些数据。这被认为是更高级和定制的遥测。手工和自动的探测各有各的作用。我们接下来会讲到。
OpenTelemetry将追踪(trace)定义一个特定的请求在代码库中完整调用路径(可能涉及多个服务)。一个追踪包括一个或多个跨度(span),是一个特定请求操作的实例。一个跨度会连接一个父跨度。如果一个跨度是一个调用链的第一个跨度,那么它的父夸度的值将全是0.
注: 本文的样例应用由Go和一些Python语言实现。我将使用Go代码作为主要语言展示,但是同样的原则在OTel支持的其它语言中也是适用的。
我们可以通过API向现有的追踪添加跨度(或开始一个新的追踪)。对于Golang来说,这意味着我们将使用go.opentelemetry.io/otel
模块,它将包括我们开始添加手动探测所需的所有包。我们可以通过调用全局追踪器提供者(tracer provider)来创建一个跨度:
import "go.opentelemetry.io/otel"
// ... other code ...
ctx, span := otel.Tracer("my-telemetry-library").Start(r.Context(), "get_user_cart")
defer span.End()
这里有几件事情需要注意。首先,我们通过首次检索全局追踪器提供者来创建一个新的跨度。我们将在下一篇博文中更深入地讨论什么是追踪器提供者,追踪器提供者是SDK的一个工件,负责决定从进程的哪里,以及如何从进程中获取遥测数据。
你可以通过调用otel.Tracer
,或者通过传递追踪器提供者来使用全局追踪器提供者。这个示例应用程序依赖于全局追踪器提供者。当我们调用otel.Tracer
时,我们传入探测名称(instrumentation name),探测名称通常是探测本身的库名。对于我的应用程序,探测名称设置为 "github.com/trstringer/otel-shopping-cart"。
一旦我们有了跟踪器提供者,我们就调用'Start',并传给它两个参数:上下文(context)和跨度(span)。上下文可以是创建的(例如context.background()
),也可以是从之前的父级上下文中传递过来(本例中我使用的是HTTP请求上下文)。跨度的名字可以是任何字符串,但在这个项目中,我已经标准化地使用了用下划线分隔的描述标识符。
Start
的返回值是上下文,我们可以使用它传递一些内容给需要处理其他上下文相关操作(例如创建子跨度)的调用路径和跨度对象本身。首先,正如你在这个例子中看到的,是调用defer span.End()
,这样我们就可以把这个跨度标记为完成。我们还可以通过"span"对象来增加跨度属性。
还值得注意的是,跨度是用来嵌套的。当一个跨度开始时,通常会调用到另一个代码路径,该路径也会启动另一个跨度。这就是跨度的嵌套关系,准确地说明了请求所经过的代码路径。
属性(Attributes)
在处理调用追踪链时,我们通常希望向追踪链中增加一些相关信息,以便我们在观测该系统时,能回答可能发生的问题。通过增加高质量的信息,我们可以实现这一点。以下代码展示了向跨度增加信息:
span.SetAttributes(attribute.String("user.name", userName))
我们创建了一个字符串属性user.name
,并设置了一个值。让我们来看一下这个属性在跨度中是如何被记录的:
Span #4
Trace ID : d6b58718e2d607f2a881e55200b387d5
Parent ID : ef6c51753d66f227
ID : 95dcb2657f5bca91
Name : get_user_cart
Kind : SPAN_KIND_INTERNAL
Start time : 2022-08-07 16:37:51.184919236 +0000 UTC
End time : 2022-08-07 16:37:51.231164398 +0000 UTC
Status code : STATUS_CODE_UNSET
Status message :
Attributes:
-> user.name: STRING(tlasagna)
太棒了。现在get_user_cart
跨度包含了 user.name
属性。Jaeger上的追踪链路也展示了:
事件(Events)
很多时候,您会想要记录跨度期间发生的一些文本或事件。比如与特定请求相关的日志记录。您可以通过调用 span.AddEvent
来做到这一点:
span.AddEvent(
"Successfully retrieved rows from database",
trace.WithAttributes(attribute.Int("row.count", rowCount)),
)
当事件被记录下来,它会被增加到跨度中。当你也可以通过增加属性的方式增加事件信息,如下跨度:
Span #1
Trace ID : 2d77674bf5bee80afcaf0df064f961ed
Parent ID : 5989852864910844
ID : f47e44dd5e23f016
Name : db_get_cart
Kind : SPAN_KIND_INTERNAL
Start time : 2022-08-07 18:37:39.167046809 +0000 UTC
End time : 2022-08-07 18:37:39.168098188 +0000 UTC
Status code : STATUS_CODE_UNSET
Status message :
Events:
SpanEvent #0
-> Name: Successfully retrieved rows from database
-> Timestamp: 2022-08-07 18:37:39.16803511 +0000 UTC
-> DroppedAttributesCount: 0
-> Attributes:
-> row.count: INT(2)
自动探测(Automatic instrumentation)
前面的例子展示了当手工记录一个跨度时,如何有意图和明确的实现。不过,OpenTelemetry真正强大的功能之一是对自动探测的广泛支持。什么时候自动探测会有帮助?这里有几种情况:
• OTel 新手,只想尽快看到遥测数据展示;
• 尝试在现有的遗留代码库上引入 OTel;
• 自动探测的“默认”数据暂时已经足够,不需要任何特定遥测数据的组件或服务。
在我的购物车程序中,我在Python服务(价格服务)中使用了自动探测了两样东西:
• Flask服务
• MySQL连接
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.mysql import MySQLInstrumentor
app = Flask(__name__)
FlaskInstrumentor().instrument_app(app)
MySQLInstrumentor().instrument()
自动探测的疯狂在于,这样就可以了!现在你得到了关于Flask的路由数据,以及MySQL的查询数据,而不需要任何额外工作和代码。以下是自动探测到Flask的跨度:
可以从中看出大量与请求相关的信息,如 http.target
, net.peer.ip
, http.method
等。
自动探测出来的MySQL相关的信息也很有用:
这很棒。零代码更改就可以自动获得的一个重要信息:查询耗时。最重要的是,我可以看到运行的查询和谁运行的它。耗时长的查询的信息非常有助于排查应用程序数据库端的故障。这些好处只来自于那一行代码所实现的MySQL自动探测。
总结
探测(Instrumentation)是OpenTelemetry的核心,即我们该如何收集遥测数据,无论选择手工探测,还是通过现在的自动化探测库进行自动探测。在下一篇博客中,我们将看到OTel SDK如何处理数据。
本系列列表: