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 是一套命名约定,目的为了将观测数据中的名称统一,定义了各种场景里 metrics 名称、通用的属性名称等。在项目 Semantic Conventions 中用 yaml 文件的方式维护,页面 OpenTelemetry Semantic Conventions 1.25.0 中进行了分类展示。
每种实现语言需要根据描述文档生成对应的常量或者枚举
Go 的实现中对于通用的属性提供了function,可以直接用来创举属性的键值对,比如下面的 semconv.ServerAddress()。metrics 名称等则是用常量定义的。
counter.Add(ctx, 1, metric.WithAttributes(
semconv.ServerAddress(serverAddr),
semconv.ClientAddress(clientAddr),
semconv.RPCMethod(method),
))
otel 还包括一系列的 instrumentation libraries。它们是针对特定的开发框架提供的 sdk,可以在 OpenTelemetry Registry 中查找相关项目。
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()),
)