0%

OpenTelemetry入门

今天早些时候,OpenTelemetry正式进入Beta版本阶段,这标志着OpenTelemetry的基本模型已经正式确定,可以开始将OpenTelemetry集成到应用程序和客户端库中,以捕获应用程序级指标和分布式跟踪。

OpenTelemetry介绍

本章节主要介绍什么是OpenTelemetry,如果你已经了解,则可以跳过本节。

对于软件而言,可观测性是非常重要的指标之一。OpenTelemetry项目提供了一组特定于语言的API,SDK,代理和其他组件,可用于从应用程序中收集分布式跟踪,metrics和相关应用Metadata。借助OpenTelemetry,开发人员几乎可以从零开始从应用程序中捕获关键的可观察性数据。OpenTelemetry不仅仅是可以作为分布式追踪工具,也可以作为metrics收集的相关工具。支持的后端存储包括:Prometheus,Jaeger,Zipkin,Azure Monitor,Dynatrace,Google Cloud Monitoring + Trace,Honeycomb,Lightstep,New Relic和Splunk等。

OpenTelemetry项目是作为OpenTracing和OpenCensus的融合项目,进入CNCF sandbox项目。对于OpenTelemetry项目的一个目标就是,兼容OpenTracing和OpenCensus。

OpenTelemetry实战

我们这里演示最常用的追踪功能开始演示。OpenTelemetry支持多种后端,这里为了简化功能演示,我们最开始选择最为简单的标准输出作为后端输出,例子均采用Go语言实现。

假设我们已创建一个名叫demo的基于Go Module的项目。接下来创建对应的main.go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main

import (
“context”
“log”

go.opentelemetry.io/otel/api/global”
go.opentelemetry.io/otel/exporters/trace/stdout”
sdktrace “go.opentelemetry.io/otel/sdk/trace”
)

func initTracer() {
exporter, err := stdout.NewExporter(stdout.Options{PrettyPrint: true})
if err != nil {
log.Fatal(err)
}
tp, err := sdktrace.NewProvider(sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
sdktrace.WithSyncer(exporter))
if err != nil {
log.Fatal(err)
}
// 设置全局
global.SetTraceProvider(tp)
}

func main() {
initTracer()
tracer := global.Tracer(“4async.com/demo”)

tracer.WithSpan(context.Background(), “foo”,
func(ctx context.Context) error {
tracer.WithSpan(ctx, “bar”,
func(ctx context.Context) error {
tracer.WithSpan(ctx, “baz”,
func(ctx context.Context) error {
return nil
},
)
return nil
},
)
return nil
},
)
}

运行上面程序之后,我们可以获取到标准输出中输出了对应的tracing信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
{
"SpanContext": {
"TraceID": "9495716100dbdd51e77edda2d39d8f28",
"SpanID": "c9e68a06fbe116e0",
"TraceFlags": 1
},
"ParentSpanID": "e3e88597058566b3",
"SpanKind": 1,
"Name": "baz",
"StartTime": "2020-03-31T10:53:55.497818+08:00",
"EndTime": "2020-03-31T10:53:55.497821142+08:00",
"Attributes": null,
"MessageEvents": null,
"Links": null,
"StatusCode": 0,
"StatusMessage": "",
"HasRemoteParent": false,
"DroppedAttributeCount": 0,
"DroppedMessageEventCount": 0,
"DroppedLinkCount": 0,
"ChildSpanCount": 0,
"Resource": null
}
{
"SpanContext": {
"TraceID": "9495716100dbdd51e77edda2d39d8f28",
"SpanID": "e3e88597058566b3",
"TraceFlags": 1
},
"ParentSpanID": "db446b6a4e579c04",
"SpanKind": 1,
"Name": "bar",
"StartTime": "2020-03-31T10:53:55.497817+08:00",
"EndTime": "2020-03-31T10:53:55.498188044+08:00",
"Attributes": null,
"MessageEvents": null,
"Links": null,
"StatusCode": 0,
"StatusMessage": "",
"HasRemoteParent": false,
"DroppedAttributeCount": 0,
"DroppedMessageEventCount": 0,
"DroppedLinkCount": 0,
"ChildSpanCount": 1,
"Resource": null
}
{
"SpanContext": {
"TraceID": "9495716100dbdd51e77edda2d39d8f28",
"SpanID": "db446b6a4e579c04",
"TraceFlags": 1
},
"ParentSpanID": "0000000000000000",
"SpanKind": 1,
"Name": "foo",
"StartTime": "2020-03-31T10:53:55.497813+08:00",
"EndTime": "2020-03-31T10:53:55.498301313+08:00",
"Attributes": null,
"MessageEvents": null,
"Links": null,
"StatusCode": 0,
"StatusMessage": "",
"HasRemoteParent": false,
"DroppedAttributeCount": 0,
"DroppedMessageEventCount": 0,
"DroppedLinkCount": 0,
"ChildSpanCount": 1,
"Resource": null
}

这里我们借助TraceIDSpanIDParentSpanID就可以快速梳理调用链路。

当然,实际上在实际项目中不可能使用这种方式进行追踪,一般来说我们会有专门的系统存储和展示追踪链路信息。因此可以通过修改上面程序中对应的go.opentelemetry.io/otel/exporters/trace/stdout内容快速替换成你需要的对应的后端存储,比如我们在线上使用的Jaeger后端,则引用go.opentelemetry.io/otel/exporters/trace/jaeger,修改initTracer()方法快速替换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// initTracer creates a new trace provider instance and registers it as global trace provider.
func initTracer() func() {
// Create and install Jaeger export pipeline
_, flush, err := jaeger.NewExportPipeline(
jaeger.WithCollectorEndpoint(“http://localhost:14268/api/traces”),
jaeger.WithProcess(jaeger.Process{
ServiceName: “trace-demo”,
Tags: []core.KeyValue{
key.String(“exporter”, “jaeger”),
key.Float64(“float”, 312.23),
},
}),
jaeger.RegisterAsGlobal(),
jaeger.WithSDK(&sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
)
if err != nil {
log.Fatal(err)
}

return func() {
flush()
}
}

至于其他后端支持,你可以从pkg.go.dev中获取到所有的支持后端列表。