opentelemetry-go 的简单使用

2022年11月20日 2042点热度 1人点赞 0条评论
内容纲要

建立链路

SDK 官方仓库地址:

https://github.com/open-telemetry/opentelemetry-go

设计一个这样的执行流程:

file

Run 先后执行 Run1Run2

    a.Run1(newCtx)
    a.Run2(newCtx)

Run1 中,还执行了另一个函数 Run1-1

使用 opentelemetry SDK 的代码示例如下:

package main

import (
    "context"
    "go.opentelemetry.io/otel"
    "io"
    "log"
)
// 当前 Trace 的名称。
const name = "App"

type App struct {
    r io.Reader
    l *log.Logger
}

func NewApp(r io.Reader, l *log.Logger) *App {
    return &App{r: r, l: l}
}

func (a *App) Run(ctx context.Context) {
    newCtx, span := otel.Tracer(name).Start(ctx, "Run")
    defer span.End()

    a.Run1(newCtx)
    a.Run2(newCtx)
}

func (a *App) Run1(ctx context.Context) {
    newCtx, span := otel.Tracer(name).Start(ctx, "Run1")
    defer span.End()

    a.Run1_1(newCtx)
}

func (a *App) Run1_1(ctx context.Context) {
    _, span := otel.Tracer(name).Start(ctx, "Run1_1")
    defer span.End()
}

func (a *App) Run2(ctx context.Context) {
    _, span := otel.Tracer(name).Start(ctx, "Run2")
    defer span.End()
}

进程中会有多个 Tracer,每个 Tracer 都需要设置一个名称,以便定位信息。

使用 otel.Tracer(name).Start(ctx, "Run") 创建新的 Context 的时候, Trace 信息会被附加到 Context 中。

    newCtx, span := otel.Tracer(name).Start(ctx, "Run")

file

如果一个函数需要调用其他函数,那么就应该传递链路:

    newCtx, span := otel.Tracer(name).Start(ctx, "Run")
    defer span.End()

    a.Run1(newCtx)
    a.Run2(newCtx)

即将 Context 传递给下一个函数。

如果要从当前 Context 获取 Span,可以这样:

span := trace.SpanFromContext(ctx)

导出链路

在上面的例子中,调用函数的过程中,生成了 Trace、Span 信息。

但是还需要导出这些信息才能显示出来。

官方 SDK 或者说中间件支持以下可观测性数据:

Exporter Metrics Traces
Jaeger
OTLP
Prometheus
stdout
Zipkin

在下面的例子中,将使用 stdout 将链路信息导出到文件中。

package main

import (
    "context"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
    "go.opentelemetry.io/otel/sdk/resource"
    "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
    "io"
    "log"
    "os"
)

// 创建 Exporter
func newExporter(w io.Writer) (trace.SpanExporter, error) {
    return stdouttrace.New(
        stdouttrace.WithWriter(w),
        // Use human readable output.
        stdouttrace.WithPrettyPrint(),
        // Do not print timestamps for the demo.
        stdouttrace.WithoutTimestamps(),
    )
}

// 创建表示当前进程的 Resource 对象
func newResource() *resource.Resource {
    r, _ := resource.Merge(
        resource.Default(),
        resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceNameKey.String("example1"),
            semconv.ServiceVersionKey.String("v0.1.0"),
            attribute.String("environment", "demo"), // 添加 attribute
        ),
    )
    return r
}

func main() {
    l := log.New(os.Stdout, "", 0)

    // trace 导出到文件
    f, err := os.Create("traces.txt")
    if err != nil {
        l.Fatal(err)
    }
    defer f.Close()

    // 创建一个 Exporter
    exp, err := newExporter(f)
    if err != nil {
        l.Fatal(err)
    }

    // 创建 TracerProvider
    tp := trace.NewTracerProvider(
        trace.WithBatcher(exp),
        trace.WithResource(newResource()),
    )
    defer func() {
        if err := tp.Shutdown(context.Background()); err != nil {
            l.Fatal(err)
        }
    }()
    otel.SetTracerProvider(tp)

    app := NewApp(os.Stdin, l)
    app.Run(context.Background())
}

首先是为了标识当前程序,需要创建一个 Resource。

// 创建表示当前进程的 Resource 对象
func newResource() *resource.Resource {
    r, _ := resource.Merge(
        resource.Default(),
        resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceNameKey.String("example1"),
            semconv.ServiceVersionKey.String("v0.1.0"),
            attribute.String("environment", "demo"), // 添加 attribute
        ),
    )
    return r
}

创建一个 Exporter,当前使用了文件 IO 。

// 创建 Exporter
func newExporter(w io.Writer) (trace.SpanExporter, error) {
    return stdouttrace.New(
        stdouttrace.WithWriter(w),
        // Use human readable output.
        stdouttrace.WithPrettyPrint(),
        // Do not print timestamps for the demo.
        stdouttrace.WithoutTimestamps(),
    )
}
func newExporter(ctx context.Context)  /* (someExporter.Exporter, error) */ {
  // Your preferred exporter: console, jaeger, zipkin, OTLP, etc.
}

创建 TracerProvider:

    // 创建 trace
    tp := trace.NewTracerProvider(
        trace.WithBatcher(exp), // exporter
        trace.WithResource(newResource()),
    )

设置全局 TracerProvider:

    otel.SetTracerProvider(tp)

启动后,生成的 Trace:

{
    "Name": "Run1_1",
    "SpanContext": {
        "TraceID": "50cf661b85718ec74a4d859f4c1aeef8",
        "SpanID": "7f51afb5a078006e",
        "TraceFlags": "01",
        "TraceState": "",
        "Remote": false
    },
    "Parent": {
        "TraceID": "50cf661b85718ec74a4d859f4c1aeef8",
        "SpanID": "11b82f4e7829ed50",
        "TraceFlags": "01",
        "TraceState": "",
        "Remote": false
    },
    "SpanKind": 1,
    "StartTime": "0001-01-01T00:00:00Z",
    "EndTime": "0001-01-01T00:00:00Z",
    "Attributes": null,
    "Events": null,
    "Links": null,
    "Status": {
        "Code": "Unset",
        "Description": ""
    },
    "DroppedAttributes": 0,
    "DroppedEvents": 0,
    "DroppedLinks": 0,
    "ChildSpanCount": 0,
    "Resource": null,
    "InstrumentationLibrary": {
        "Name": "App",
        "Version": "",
        "SchemaURL": ""
    }
}
{
    "Name": "Run1",
    "SpanContext": {
        "TraceID": "50cf661b85718ec74a4d859f4c1aeef8",
        "SpanID": "11b82f4e7829ed50",
        "TraceFlags": "01",
        "TraceState": "",
        "Remote": false
    },
    "Parent": {
        "TraceID": "50cf661b85718ec74a4d859f4c1aeef8",
        "SpanID": "bd024e7b63a2de62",
        "TraceFlags": "01",
        "TraceState": "",
        "Remote": false
    },
    "SpanKind": 1,
    "StartTime": "0001-01-01T00:00:00Z",
    "EndTime": "0001-01-01T00:00:00Z",
    "Attributes": null,
    "Events": null,
    "Links": null,
    "Status": {
        "Code": "Unset",
        "Description": ""
    },
    "DroppedAttributes": 0,
    "DroppedEvents": 0,
    "DroppedLinks": 0,
    "ChildSpanCount": 1,
    "Resource": null,
    "InstrumentationLibrary": {
        "Name": "App",
        "Version": "",
        "SchemaURL": ""
    }
}
{
    "Name": "Run2",
    "SpanContext": {
        "TraceID": "50cf661b85718ec74a4d859f4c1aeef8",
        "SpanID": "bc9c29add7a9b100",
        "TraceFlags": "01",
        "TraceState": "",
        "Remote": false
    },
    "Parent": {
        "TraceID": "50cf661b85718ec74a4d859f4c1aeef8",
        "SpanID": "bd024e7b63a2de62",
        "TraceFlags": "01",
        "TraceState": "",
        "Remote": false
    },
    "SpanKind": 1,
    "StartTime": "0001-01-01T00:00:00Z",
    "EndTime": "0001-01-01T00:00:00Z",
    "Attributes": null,
    "Events": null,
    "Links": null,
    "Status": {
        "Code": "Unset",
        "Description": ""
    },
    "DroppedAttributes": 0,
    "DroppedEvents": 0,
    "DroppedLinks": 0,
    "ChildSpanCount": 0,
    "Resource": null,
    "InstrumentationLibrary": {
        "Name": "App",
        "Version": "",
        "SchemaURL": ""
    }
}
{
    "Name": "Run",
    "SpanContext": {
        "TraceID": "50cf661b85718ec74a4d859f4c1aeef8",
        "SpanID": "bd024e7b63a2de62",
        "TraceFlags": "01",
        "TraceState": "",
        "Remote": false
    },
    "Parent": {
        "TraceID": "00000000000000000000000000000000",
        "SpanID": "0000000000000000",
        "TraceFlags": "00",
        "TraceState": "",
        "Remote": false
    },
    "SpanKind": 1,
    "StartTime": "0001-01-01T00:00:00Z",
    "EndTime": "0001-01-01T00:00:00Z",
    "Attributes": null,
    "Events": null,
    "Links": null,
    "Status": {
        "Code": "Unset",
        "Description": ""
    },
    "DroppedAttributes": 0,
    "DroppedEvents": 0,
    "DroppedLinks": 0,
    "ChildSpanCount": 2,
    "Resource": null,
    "InstrumentationLibrary": {
        "Name": "App",
        "Version": "",
        "SchemaURL": ""
    }
}

其他常用方法

Span 添加属性

可以向 Span 添加 k/v 信息:

    var myKey = attribute.Key("myCoolAttribute")
    span.SetAttributes(myKey.String("a value"))
func (a *App) Run(ctx context.Context) {
    newCtx, span := otel.Tracer(name).Start(ctx, "Run")
    defer span.End()
    var myKey = attribute.Key("myCoolAttribute")
    span.SetAttributes(myKey.String("a value"))
    a.Run1(newCtx)
    a.Run2(newCtx)
}

添加事件

添加简单的事件信息,表示在当前执行了何种操作。

span.AddEvent("Acquiring lock")
mutex.Lock()
span.AddEvent("Got lock, doing work...")
// do stuff
span.AddEvent("Unlocking")
mutex.Unlock()

Span 添加状态

如果当前函数执行有问题,可以向 Span 添加状态以及记录错误信息。

result, err := operationThatCouldFail()
if err != nil {
    span.SetStatus(codes.Error, "operationThatCouldFail failed")
    span.RecordError(err)
}

痴者工良

高级程序员劝退师

文章评论