插桩只是在应用程序中为了提升应用的可观测性而增加的代码。通常,有两种插桩方式,分别是自动插桩和手动插桩。为了能够更好的提升应用程序的可观测性,你应该同时熟悉两个插桩方式。
获取 Tracer
为了能够创建 span,你首先需要获取并初始化一个 tracer。
初始化 tracer
首先,需要安装相关的依赖库:
go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/trace
go get go.opentelemetry.io/otel/sdk
接下来,你可以初始化一个 Exporter、Resource、TracerProvider ,最后得到一个 tracer:
package app
import (
"context"
"fmt"
"log"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
"go.opentelemetry.io/otel/trace"
)
var tracer trace.Tracer
func newExporter(ctx context.Context) /* (someExporter.Exporter, error) */ {
// Your preferred exporter: console, jaeger, zipkin, OTLP, etc.
}
func newTraceProvider(exp sdktrace.SpanExporter) *sdktrace.TracerProvider {
// The service.name attribute is required.
resource := resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("ExampleService"),
)
return sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exp),
sdktrace.WithResource(resource),
)
}
func main() {
ctx := context.Background()
exp, err := newExporter(ctx)
if err != nil {
log.Fatalf("failed to initialize exporter: %v", err)
}
// Create a new tracer provider with a batch span processor and the given exporter.
tp := newTraceProvider(exp)
// Handle shutdown properly so nothing leaks.
defer func() { _ = tp.Shutdown(ctx) }()
otel.SetTracerProvider(tp)
// Finally, set the tracer that can be used for this package.
tracer = tp.Tracer("ExampleService")
}
接下来,你就可以使用 tracer 对象来在你的代码中进行插桩了。
创建 Spans
spans 是通过 tracer 对象创建的。
为了通过 tracer 来创建一个 span,你需要传入一个 context.Context 实例。context 实例通常会来源与请求对象参数等场景,同时,在 context 实例中可能会已经包含了一些插入的父 span 相关的信息:
func httpHandler(w http.ResponseWriter, r *http.Request) {
ctx, span := tracer.Start(r.Context(), "hello-span")
defer span.End()
// do some work to track with hello-span
}
在 Go 语言中,当前 active span 信息都是存储在 context 中的。当你创建一个 span 时,不仅会得到新创建的 span 对象,同时你还会修改对应 Context 上下文中的内容。
当一个 span 对象已经被结束后,那么它就不能再进行修改了。
获取当前的 span
想要获取当前的 span ,你需要从 span 存储的 context 中获取得到:
// This context needs contain the active span you plan to extract.
ctx := context.TODO()
span := trace.SpanFromContext(ctx)
// Do something with the current span, optionally calling `span.End()` if you want it to end
你如果想要向当前跨度中添加信息时,掌握如何获取当前跨度对象是很有用的。
创建嵌套 span
在一个 span 中,你可以创建一个嵌套 Span 用于对应的嵌套操作。
如果当前的 context 中之前已经包含了一个 span,那么创建一个新的 span 时,会自动得到父子关系的嵌套 Span,示例代码如下:
func parentFunction(ctx context.Context) {
ctx, parentSpan := tracer.Start(ctx, "parent")
defer parentSpan.End()
// call the child function and start a nested span in there
childFunction(ctx)
// do more work - when this function ends, parentSpan will complete.
}
func childFunction(ctx context.Context) {
// Create a span to track `childFunction()` - this is a nested span whose parent is `parentSpan`
ctx, childSpan := tracer.Start(ctx, "child")
defer childSpan.End()
// do work here, when this function returns, childSpan will complete.
}
当一个 span 对象已经被结束后,那么它就不能再进行修改了。
span 属性
span 属性是一组键值对的格式,可以被当做元数据添加到你的 span 数据上。同时,它对于聚合、过滤、分组场景都都是有效的。属性可以在 span 创建的过程中添加,也可以在 Span 运行结束之前的整个生命周期过程中进行添加。
// setting attributes at creation...
ctx, span = tracer.Start(ctx, "attributesAtCreation", trace.WithAttributes(attribute.String("hello", "world")))
// ... and after creation
span.SetAttributes(attribute.Bool("isTrue", true), attribute.String("stringAttr", "hi!"))
另外,如下语法格式也是可以的:
var myKey = attribute.Key("myCoolAttribute")
span.SetAttributes(myKey.String("a value"))
语义属性
语义属性是指在 OpenTelemetry 规范中为了能够保证整体的一致性,针对跨语言的框架、通用场景等定义的一系列有标准含义的属性。可以属性都可以在 go.opentelemetry.io/otel/semconv/v1.7.0
包中查询。
事件
事件是指在一个 span 的过程中发生的一些事情,可以用人类可以理解的消息进行表示。例如,在一个函数中可能需要操作一个加锁的对象,那么就可以抽象出来如下三个事件:
- 尝试获取锁
- 获取到锁
- 释放锁
示例代码如下:
span.AddEvent("Acquiring lock")
mutex.Lock()
span.AddEvent("Got lock, doing work...")
// do stuff
span.AddEvent("Unlocking")
mutex.Unlock()
事件的特性之一就是它对应的时间戳为相对当前 span 开始时间的时间偏移量,从而,你可以轻松的判断它们之间的时间差。
同样,Events 上也支持增加属性:
span.AddEvent("Cancelled wait due to external signal", trace.WithAttributes(attribute.Int("pid", 4328), attribute.String("signal", "SIGHUP")))
上下文跨进程传递
Traces 数据可以跨越多个进程。这时就需要一种跨进程传播的机制了,即类似于 Context 的方式将当前 span 的上下文传输到远程服务中。
为了实现上下文的跨进程传递,你首先需要引入 propagator 并在代码中进行注册:
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
)
...
otel.SetTextMapPropagator(propagation.TraceContext{})
Ps: 出于兼容性考虑,OpenTelemetry 也通过 go.opentelemetry.io/contrib/propagators/b3 支持了 B3 Header 格式的传播机制。
配置完上下文传播机制后,你基本就可以通过一些自动插桩库来完全实现跨进程的上下文传递工作了。