OpenTelemetry 项目在 Observability Primer 中介绍可观测的定义(Observability)。简单来说就是软件系统要向外暴露足够的信息。暴露的数据不仅反映出系统的运行状态,而且足以用来定位故障原因。有一系列工具可以被用来实现软件系统的可观测。关于 OpenTelemetry、OpenTracing、OpenCensus 的关系见:OpenTelemetry 介绍。
OpenTelemetry 是一系列系统观测相关的 api、sdk 和工具。它定义采集测量数据的方法框架,并提供了 Golang/Java/JavaScript 等十多种语言的实现,以及支持对接几十种第三方系统。开发者用 OpenTelemetry 提供的 API 可以直接生成测量数据,并导入到选用的数据系统中。测量数据目前有 metrics、trace 和 log 三类。
OpenTelemetry 支持将测量数据以多种格式输出,可以按照 OpenTelemetry Protocol(简称 otlp) 格式输出。或者以第三方系统支持的格式输出,比如 Prometheus 使用的 metrics 数据格式,Jaeger 使用的 tracer 数据格式。通过切换 exporter 改变数据的输出格式。
OpenTelemetry 同时提供了一系列配套代码库,适配各种开发框架。在开发框架中引入对应的代码库中方法,就可以生成测量数据。
OpenTelemetry Client Architecture 介绍了整体架构。
signal 的构成:
每种语言单独实现,比如 Go 语言实现包括下面的项目:(otel go)
Semantic Conventions 是一套用 yaml 文档描述的属性,每种实现语言需要根据描述文档生成对应的常量或者枚举。 Go 语言实现的 Semantic Conventions 位于 opentelemetry-go 的 semconv 目录。
此外,还有一系列的 instrumentation libraries。它们是针对特定的开发框架提供的 sdk。 在 OpenTelemetry Registry 可以查找已经支持的框架,比如适用于 grpc 的配套代码:instrumentation/google.golang.org/grpc/。
Tracing、Metricing 和 Logging 数据生成需要分别引用不同的代码包。它们是独立声明的 go package,虽然都位于一个 repo。
api 定义分别是下面的三个 package:
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/log"
还有各自的 sdk 代码包:
sdktrace "go.opentelemetry.io/otel/sdk/trace"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
sdklog "go.opentelemetry.io/otel/sdk/log"
三者的 api 和 sdk 使用方式是类似的:
以 tracing 为例,InitTrace 中创建了 exporter,然后生成 tracerProvider 和 tracer。在 DemoDepth0 中用 tracer.Start() 生成 trace 数据。
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
"go.opentelemetry.io/otel/propagation"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
)
func InitTrace(ctx context.Context, name string) (*sdktrace.TracerProvider, trace.Tracer, error) {
exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
if err != nil {
return nil, nil, err
}
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
)
otel.SetTracerProvider(tracerProvider)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
tracer := otel.GetTracerProvider().Tracer(name)
return tracerProvider, tracer, nil
}
func DemoDepth0(ctx context.Context, tracer trace.Tracer) {
ctx, span := tracer.Start(ctx, "DemoDepth1")
defer span.End()
DemoDepth1(ctx, tracer)
}
OpenTelemetry 通过内置的 Exporter 将数据转换成各类第三方系统能够识别的格式。opentelemetry-go/example 给出了一些例子。 其中 example/otel-collector 使用了一个配套工具 opentelemetry-collector-contrib,该工具可以将 otlp 格式的数据转换成其它格式。
Exporter 的使用方式是相似的:
func TestExporterStdout(t *testing.T) {
ctx := context.Background()
exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
if err != nil {
t.Fatalf("create exporter stdout fail: %s", err.Error())
}
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
)
defer tracerProvider.Shutdown(ctx)
otel.SetTracerProvider(tracerProvider)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
tracer := otel.GetTracerProvider().Tracer("stdout demo")
DemoDepth0(ctx, tracer)
}
使用 tracer 生成 trace 数据:
func DemoDepth0(ctx context.Context, tracer trace.Tracer) {
ctx, span := otel.GetTracerProvider().Tracer("demo").Start(ctx, "DemoDepth1")
defer span.End()
DemoDepth1(ctx, tracer)
}
func DemoDepth1(ctx context.Context, tracer trace.Tracer) {
ctx, span := otel.GetTracerProvider().Tracer("demo").Start(ctx, "DemoDepth2")
defer span.End()
DemoDepth2(ctx, tracer)
}
func DemoDepth2(ctx context.Context, trace trace.Tracer) {
}
例子:instrumentation/google.golang.org/grpc/otelgrpc/example
expoter 的选择以及 provider 的注册需要自行编写,方法同 stdout exporter 的使用。
var (
tracerProvider *sdktrace.TracerProvider
)
func InitTrace(ctx context.Context, name string) error {
exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
if err != nil {
return err
}
tracerProvider = sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
)
otel.SetTracerProvider(tracerProvider)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
return nil
}
func StopTrace(ctx context.Context) error {
return tracerProvider.Shutdown(ctx)
}
client 端创建连接时添加 grpc.WithStatsHandler(otelgrpc.NewClientHandler()) 后,就会自动为所有 rpc 请求添加 trace 信息。
conn, err := grpc.NewClient(ADDR,
grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
)
server 端创建 server 时添加 grpc.StatsHandler(otelgrpc.NewServerHandler()) 后,就会自动从请求头中解析出 trace 信息。如果请求头中没有 trace 信息,就初始生成。
s := grpc.NewServer(
grpc.StatsHandler(otelgrpc.NewServerHandler()),
)