Previously, OpenTelemetry officially entered the Beta phase, which marked the formalization of the basic OpenTelemetry model to begin integrating OpenTelemetry into applications and client libraries to capture application-level metrics and distributed traces.

Introduction to OpenTelemetry

This section focuses on what OpenTelemetry is, or you can skip this section if you already know it.

Observability is one of the very important metrics for software. the OpenTelemetry project provides a set of language-specific APIs, SDKs, agents and other components that can be used to collect distributed traces, metrics and related application Metadata from applications. with OpenTelemetry, developers can start from almost zero to OpenTelemetry can be used not only as a distributed tracking tool, but also as a related tool for metrics collection. Supported backend stores include Prometheus, Jaeger, Zipkin, Azure Monitor, Dynatrace, Google Cloud Monitoring + Trace, Honeycomb, Lightstep, New Relic, and Splunk, among others.

The OpenTelemetry project was created as a convergence of OpenTracing and OpenCensus into the CNCF sandbox project. One of the goals for the OpenTelemetry project is that it is compatible with OpenTracing and OpenCensus.

OpenTelemetry in action

OpenTelemetry supports a variety of backends, so to simplify the demonstration, we will start with the simplest standard output as the backend output, and use Go language for the examples.

Suppose we have created a Go Module-based project called demo. Next, create the corresponding 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
		},
	)
}

After running the above program, we can get the corresponding tracing information output in the standard output.

 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
}

Here we can quickly sort out the call links with the help of TraceID, SpanID and ParentSpanID.

Of course, in practice it is not possible to trace in this way in a real project, and we usually have a special system for storing and displaying trace information. So you can quickly replace the go.opentelemetry.io/otel/exporters/trace/stdout content with the corresponding backend storage you need by modifying the corresponding go.opentelemetry.io/otel/exporters/trace/stdout content in the above program, for example, if we use the Jaeger backend online, then refer to go.opentelemetry.io/ otel/exporters/trace/jaeger and modify the initTracer() method to quickly replace it.

 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()
	}
}

As for other backend support, you can get a list of all supported backends from pkg.go.dev.