关于go:OpenTelemetry入门

步骤

装置

OpenTelemetry分为两局部:用于追踪代码的API和SDK
API局部:

go get go.opentelemetry.io/otel \
       go.opentelemetry.io/otel/trace

SDK局部:

go get go.opentelemetry.io/otel/sdk \
   go.opentelemetry.io/otel/exporters/stdout/stdouttrace

设置应用程序的名称

// name is the Tracer name used to identify this instrumentation library.
const name = "fib"

代码监测 – 创立span

//创立指定应用程序的追踪器
otel.Tracer(name)

//创立span

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

示例:

func Create(w http.ResponseWriter, r *http.Request) {
    ctx := context.Background()
    newCtx, span := otel.Tracer(name).Start(ctx, "Create")
    defer span.End()
    time.Sleep(5 * time.Second)
    GetOne(newCtx, r.URL.Query().Get("mobile"))
}
func GetOne(ctx context.Context, nStr string) {
    nCtx, span := otel.Tracer(name).Start(ctx, "GetOne")
    defer span.End()
    SetAge(nCtx, "hello world")
    span.SetAttributes(attribute.String("request.n", nStr))
}
func SetAge(ctx context.Context, val string) {
    _, span := otel.Tracer(name).Start(ctx, "SetAge")
    defer span.End()
    span.SetAttributes(attribute.String("ageTime", val))
}

以上他们的关系:
Create
├── GetOne
    └── SetAge

创立导出器

顾名思义,就是将收集的span数据导出到指定地位。比方文件,或者https://pkg.go.dev/go.opentelemetry.io/otel/exporters/jaeger,https://pkg.go.dev/go.opentelemetry.io/otel/exporters/zipkin,
https://pkg.go.dev/go.opentelemetry.io/otel/exporters/prometheus等风行的开源监测工具。

// newExporter returns a console exporter.
func newExporter(w io.Writer) (trace.SpanExporter, error) {
    return stdouttrace.New(
        stdouttrace.WithWriter(w),
        // json格局输入
        stdouttrace.WithPrettyPrint(),
        // 不打印工夫
        stdouttrace.WithoutTimestamps(),
    )
}

创立服务资源实例

含意:监测数据对于解决服务问题至关重要,然而须要一种办法来辨认是哪个服务,以及监测数据来自哪个服务。

// newResource returns a resource describing this application.
//返回形容该应用程序的实例
func newResource() *resource.Resource {
    r, _ := resource.Merge(
        resource.Default(),
        resource.NewWithAttributes(
            semconv.SchemaURL,
          //服务名称
            semconv.ServiceName("fib"),
          //版本
            semconv.ServiceVersion("v0.1.0"),
          //自定义数据
            attribute.String("environment", "demo"),
        ),
    )
    return r
}

Tracer Provider追踪器

将代码监测、导出器、服务资源实例组合为 Tracer

package main
import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
    "go.opentelemetry.io/otel/sdk/resource"
    "go.opentelemetry.io/otel/sdk/trace"
    "io"
    "log"
    "net/http"
    "os"
    //semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)
func main() {
   //监测数据写入这个文件中
    f, err := os.Create("traces.txt")
    //传给导出器
    exp, _ := newExporter(f)
    tp := trace.NewTracerProvider(
       //将span增加到tracer
        trace.WithBatcher(exp),
       //将服务实例数据增加到 tracer
        trace.WithResource(newResource()),
    )
   //注册为全局追踪程序
    otel.SetTracerProvider(tp)
    http.HandleFunc("/", Handle)
    http.HandleFunc("/course", Create)
    http.ListenAndServe(":8080", nil)
    //fmt.Println(123)
}

监测数据示例

{
    "Name": "SetAge",
    "SpanContext": {
        "TraceID": "78cfbadec4eb7dc6ad17609adfe93bd9",
        "SpanID": "403b5e63c2707277",
        "TraceFlags": "01",
        "TraceState": "",
        "Remote": false
    },
    "Parent": {
        "TraceID": "78cfbadec4eb7dc6ad17609adfe93bd9",
        "SpanID": "254b9faef1cb50f7",
        "TraceFlags": "01",
        "TraceState": "",
        "Remote": false
    },
    "SpanKind": 1,
    "StartTime": "2023-04-27T15:32:48.5329109+08:00",
    "EndTime": "2023-04-27T15:32:48.5329109+08:00",
    "Attributes": [
        {
            "Key": "ageTime",
            "Value": {
                "Type": "STRING",
                "Value": "hello world"
            }
        }
    ],
    "Events": null,
    "Links": null,
    "Status": {
        "Code": "Unset",
        "Description": ""
    },
    "DroppedAttributes": 0,
    "DroppedEvents": 0,
    "DroppedLinks": 0,
    "ChildSpanCount": 0,
    "Resource": null,
    "InstrumentationLibrary": {
        "Name": "temp-test",
        "Version": "",
        "SchemaURL": ""
    }
}
{
    "Name": "GetOne",
    "SpanContext": {
        "TraceID": "78cfbadec4eb7dc6ad17609adfe93bd9",
        "SpanID": "254b9faef1cb50f7",
        "TraceFlags": "01",
        "TraceState": "",
        "Remote": false
    },
    "Parent": {
        "TraceID": "78cfbadec4eb7dc6ad17609adfe93bd9",
        "SpanID": "a4a4949360979126",
        "TraceFlags": "01",
        "TraceState": "",
        "Remote": false
    },
    "SpanKind": 1,
    "StartTime": "2023-04-27T15:32:48.5329109+08:00",
    "EndTime": "2023-04-27T15:32:48.5329109+08:00",
    "Attributes": [
        {
            "Key": "request.n",
            "Value": {
                "Type": "STRING",
                "Value": "13745679876"
            }
        }
    ],
    "Events": null,
    "Links": null,
    "Status": {
        "Code": "Unset",
        "Description": ""
    },
    "DroppedAttributes": 0,
    "DroppedEvents": 0,
    "DroppedLinks": 0,
    "ChildSpanCount": 1,
    "Resource": null,
    "InstrumentationLibrary": {
        "Name": "temp-test",
        "Version": "",
        "SchemaURL": ""
    }
}
{
    "Name": "Create",
    "SpanContext": {
        "TraceID": "78cfbadec4eb7dc6ad17609adfe93bd9",
        "SpanID": "a4a4949360979126",
        "TraceFlags": "01",
        "TraceState": "",
        "Remote": false
    },
    "Parent": {
        "TraceID": "00000000000000000000000000000000",
        "SpanID": "0000000000000000",
        "TraceFlags": "00",
        "TraceState": "",
        "Remote": false
    },
    "SpanKind": 1,
    "StartTime": "2023-04-27T15:32:43.5249414+08:00",
    "EndTime": "2023-04-27T15:32:48.5329109+08:00",
    "Attributes": null,
    "Events": null,
    "Links": null,
    "Status": {
        "Code": "Unset",
        "Description": ""
    },
    "DroppedAttributes": 0,
    "DroppedEvents": 0,
    "DroppedLinks": 0,
    "ChildSpanCount": 1,
    "Resource": null,
    "InstrumentationLibrary": {
        "Name": "temp-test",
        "Version": "",
        "SchemaURL": ""
    }
}

源码剖析

导出器局部

//main.go

f, _ := os.Create("traces.txt")
newExporter(f)

//导出器,就是监测数据导出到哪里,这个例子将导出到traces.txt文件中

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

//所传的多个参数都是Option,Option是个接口(interface),并定义了apply办法
stdouttrace.New(options …Option)
地位:D:\go\pkg\mod\go.opentelemetry.io\otel\exporters\stdout\stdouttrace@v1.14.0\trace.go 32行
Option地位:
D:\go\pkg\mod\go.opentelemetry.io\otel\exporters\stdout\stdouttrace@v1.14.0\config.go 56行

type Option interface {
    apply(config) config
}

//设置导出指标
stdouttrace.WithWriter(w),
// 应用json格局输入.
stdouttrace.WithPrettyPrint(),
// 导出数据不打印日期工夫.
stdouttrace.WithoutTimestamps(),

调用了三个办法,每个办法都返回了一个Option,共返回了三个,别离是:

type writerOption struct 
type prettyPrintOption bool 
type timestampsOption bool

有人可能好奇了,明明办法返回的是这三个type,怎么写 Option 呢,它们的返回参数之所以写Option,是因为这三个type都实现了 Option 定义的 apply接口
地位:
D:\go\pkg\mod\go.opentelemetry.io\otel\exporters\stdout\stdouttrace@v1.14.0\config.go

以下就是这三个办法实现的代码:能够看到,调用的这三个办法,别离定义了一个type,并且都实现Option接口定义的apply办法。

// Option sets the value of an option for a Config.
type Option interface {
    apply(config) config
}
// WithWriter sets the export stream destination.
func WithWriter(w io.Writer) Option {
    return writerOption{w}
}
type writerOption struct {
    W io.Writer
}
func (o writerOption) apply(cfg config) config {
    cfg.Writer = o.W
    return cfg
}
// WithPrettyPrint sets the export stream format to use JSON.
func WithPrettyPrint() Option {
    return prettyPrintOption(true)
}
type prettyPrintOption bool
func (o prettyPrintOption) apply(cfg config) config {
    cfg.PrettyPrint = bool(o)
    return cfg
}
// WithoutTimestamps sets the export stream to not include timestamps.
func WithoutTimestamps() Option {
    return timestampsOption(false)
}
type timestampsOption bool
func (o timestampsOption) apply(cfg config) config {
    cfg.Timestamps = bool(o)
    return cfg
}

再看看 stdouttrace.New() 外面写了什么

func New(options ...Option) (*Exporter, error) {
    cfg, err := newConfig(options...)
    if err != nil {
        return nil, err
    }

    enc := json.NewEncoder(cfg.Writer)
    if cfg.PrettyPrint {
        enc.SetIndent("", "\t")
    }

    return &Exporter{
        encoder:    enc,
        timestamps: cfg.Timestamps,
    }, nil
}

看起来大略意思就是生成一个新的配置,而后调用json.NewEncoder返回的一个给cfg.Writer写入内容的编码器,cfg.Writer就是创立 trace.txt 返回的值,并且设置缩进占用一个制表符,依照缩进的形式格式化后续内容编码的值,最初返回 Exporter 这个构造体的指针。

接下来咱们再具体看看:
//看这行,调用 newConfig 办法
cfg, err := newConfig(options…)
//newConfig实现如下,

看起来大略意思就是生成一个新的配置,而后调用json.NewEncoder返回的一个给cfg.Writer写入内容的编码器,cfg.Writer就是创立 trace.txt 返回的值,并且设置缩进占用一个制表符,依照缩进的形式格式化后续内容编码的值,最初返回 Exporter 这个构造体的指针。

接下来咱们再具体看看:
调用 newConfig 办法
cfg, err := newConfig(options…)
咱们下面说过,调用了三个办法并返回了三个 type,这个三个type每个都实现了 apply 办法,并且对config构造体的参数进行了设置,请往下看
所以
for _, opt := range options
循环遍历,顺次调用它们实现的 apply 办法;

func newConfig(options ...Option) (config, error) {
    cfg := config{
        Writer:      defaultWriter,
        PrettyPrint: defaultPrettyPrint,
        Timestamps:  defaultTimestamps,
    }
//这里,循环遍历,顺次调用apply
    for _, opt := range options {
        cfg = opt.apply(cfg)
    }
    return cfg, nil
}

cfg 也是一个构造体,负责寄存一些配置信息
//导出器的配置选项.

type config struct {
   //写入模式,默认是 os.Stdout(指向规范输入的文件描述符)
    Writer io.Writer
    // 输入格局是否为可读json,默认为false,
    PrettyPrint bool
    // 是否打印工夫戳,默认true
    Timestamps bool
}

创立服务资源实例

先看代码

func newResource() *resource.Resource {
    r, _ := resource.Merge(
        resource.Default(),
        resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceName("fib"),
            semconv.ServiceVersion("v0.1.0"),
            attribute.String("environment", "demo"),
        ),
    )
    return r
}

看代码咱们大抵能够猜到,通过 resource.Merge 将 resource.Default()与 resource.NewWithAttributes() 的后果合并,之后 return
看 resource.Default() 源码

// Default returns an instance of Resource with a default
// "service.name" and OpenTelemetrySDK attributes.
func Default() *Resource {
    defaultResourceOnce.Do(func() {
        var err error
        defaultResource, err = Detect(
            context.Background(),
            defaultServiceNameDetector{},
            fromEnv{},
            telemetrySDK{},
        )
        if err != nil {
            otel.Handle(err)
        }
        // If Detect did not return a valid resource, fall back to emptyResource.
        if defaultResource == nil {
            defaultResource = &emptyResource
        }
    })
    return defaultResource
}

首先调用了 sync.Once的Do函数,这个函数的作用就是不论你调用几次,它都只执行一次,你能够了解为在第一次调用后,后续的调用就抛弃了。咱们看到这个办法中次要的局部就是:

defaultResource, err = Detect(
    context.Background(),
    defaultServiceNameDetector{},
    fromEnv{},
    telemetrySDK{},
)

能够看到 Detect 函数,传的参数,第一个是 context 上下文,其余都是构造体,并且这些构造体,每个都实现了 Detect 办法,我轻易点开了一个构造体看看它实现的 Detect 办法,这里我看的是 fromEnv{} 构造体,看代码:

// fromEnv is a Detector that implements the Detector and collects
// resources from environment.  This Detector is included as a
// builtin.
type fromEnv struct{}

// compile time assertion that FromEnv implements Detector interface.
//编译时断言FromEnv 构造体是否实现了Detector定义的接口
var _ Detector = fromEnv{}

// 从环境变量中获取
func (fromEnv) Detect(context.Context) (*Resource, error) {
    attrs := strings.TrimSpace(os.Getenv(resourceAttrKey))
    svcName := strings.TrimSpace(os.Getenv(svcNameKey))

    if attrs == "" && svcName == "" {
        return Empty(), nil
    }

    var res *Resource

    if svcName != "" {
        res = NewSchemaless(semconv.ServiceName(svcName))
    }

    r2, err := constructOTResources(attrs)

    // Ensure that the resource with the service name from OTEL_SERVICE_NAME
    // takes precedence, if it was defined.
    res, err2 := Merge(r2, res)

    if err == nil {
        err = err2
    } else if err2 != nil {
        err = fmt.Errorf("detecting resources: %s", []string{err.Error(), err2.Error()})
    }

    return res, err
}

能够看到,次要就是对环境变量中获取的参数进行解决。
当初看看 Detect 函数中写的代码

func Detect(ctx context.Context, detectors ...Detector) (*Resource, error) {
    var autoDetectedRes *Resource
    var errInfo []string
    for _, detector := range detectors {
        if detector == nil {
            continue
        }
        res, err := detector.Detect(ctx)
        if err != nil {
            errInfo = append(errInfo, err.Error())
            if !errors.Is(err, ErrPartialResource) {
                continue
            }
        }
        autoDetectedRes, err = Merge(autoDetectedRes, res)
        if err != nil {
            errInfo = append(errInfo, err.Error())
        }
    }

    var aggregatedError error
    if len(errInfo) > 0 {
        aggregatedError = fmt.Errorf("detecting resources: %s", errInfo)
    }
    return autoDetectedRes, aggregatedError
}

后面咱们说了, 调用 Detect 的时候,传的参数除了 context,还有三个构造体,代码中通过 for 循环顺次去执行这三个构造体实现的 Detect 办法,而后通过 Merge 办法将后果和 autoDetectedRes 合并,而后将合并的后果和 error 返回。
当初看一下 resource.NewWithAttributes() 办法的源码

// NewWithAttributes creates a resource from attrs and associates the resource with a
// schema URL. If attrs contains duplicate keys, the last value will be used. If attrs
// contains any invalid items those items will be dropped. The attrs are assumed to be
// in a schema identified by schemaURL.
func NewWithAttributes(schemaURL string, attrs ...attribute.KeyValue) *Resource {
    resource := NewSchemaless(attrs...)
    resource.schemaURL = schemaURL
    return resource
}

能够看到次要是对 *Resource 构造体的参数进行赋值操作,就说到这里,具体深刻的有趣味的小伙伴能够持续追源码理解。

注册全局跟踪

tp := trace.NewTracerProvider(
        trace.WithBatcher(exp),
        trace.WithResource(newResource()),
    )
otel.SetTracerProvider(tp)

剖析 trace.NewTracerProvider()
// 返回一个新配置好的 TracerProvider
// 默认状况下,返回的TracerProvider配置为:
//   - 一个基于 ParentBased(AlwaysSample)的 Sampler
//   - 一个随机数 IDGenerator
//   - the resource.Default() Resource
//   - the default SpanLimits.
//
// 传的参数用于笼罩默认值
// returned TracerProvider appropriately.
func NewTracerProvider(opts ...TracerProviderOption) *TracerProvider {
    o := tracerProviderConfig{
        spanLimits: NewSpanLimits(),
    }
   //这行次要是对 TracerProvider.Sampler进行解决,解决的次要实现就是通过os.LookupEnv获取到环境变量,再依据不同的变量返回不同的构造体
    o = applyTracerProviderEnvConfigs(o)

    for _, opt := range opts {
        o = opt.apply(o)
    }
   //这行代码比拟重要,设置Sampler 、随机数IDGenerator、Resource
    o = ensureValidTracerProviderConfig(o)
   //TracerProvider构造体填充参数值
    tp := &TracerProvider{
        namedTracer: make(map[instrumentation.Scope]*tracer),
        sampler:     o.sampler,
        idGenerator: o.idGenerator,
        spanLimits:  o.spanLimits,
        resource:    o.resource,
    }
    global.Info("TracerProvider created", "config", o)

    spss := spanProcessorStates{}
    //o.processors是在trace.WithBatcher(exp)代码中设置的
    for _, sp := range o.processors {
        spss = append(spss, newSpanProcessorState(sp))
    }
   //给 atomic.Value通过 Store设置值,值为 spss
    tp.spanProcessors.Store(spss)
   //返回TracerProvider构造体
    return tp
}

otel.SetTracerProvider(tp)
// SetTracerProvider registers `tp` as the global trace provider.
func SetTracerProvider(tp trace.TracerProvider) {
    global.SetTracerProvider(tp)
}
这段代码次要是将方才trace.NewTracerProvider()返回的TracerProvider构造体设置为全局的,上面是SetTracerProvider()

// SetTracerProvider is the internal implementation for global.SetTracerProvider.
func SetTracerProvider(tp trace.TracerProvider) {
    current := TracerProvider()

    if _, cOk := current.(*tracerProvider); cOk {
        if _, tpOk := tp.(*tracerProvider); tpOk && current == tp {
            // Do not assign the default delegating TracerProvider to delegate
            // to itself.
            Error(
                errors.New("no delegate configured in tracer provider"),
                "Setting tracer provider to it's current value. No delegate will be configured",
            )
            return
        }
    }

    delegateTraceOnce.Do(func() {
        if def, ok := current.(*tracerProvider); ok {
            def.setDelegate(tp)
        }
    })
    globalTracer.Store(tracerProviderHolder{tp: tp})
}

看最初一行,将atomic.Value通过 Store设置 tracerProviderHolder.tp

追踪监测

这是一段加了链路追踪的代码

func Handle(writer http.ResponseWriter, request *http.Request) {
    if request.RequestURI == "/favicon.ico" {
        return
    }
    _, span := otel.Tracer(name).Start(context.Background(), "Handle")
    defer span.End()
}

外围就是这两行:

span := otel.Tracer(name).Start(context.Background(), "Handle")
    defer span.End()

接下来咱们将代码拆开看一下,它是怎么实现的。
otel.Tracer(name) 先看 Tracer 办法源码
//地位:D:\go\pkg\mod\go.opentelemetry.io\otel@v1.14.0\trace.go

func Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
    return GetTracerProvider().Tracer(name, opts...)
}
再看 GetTracerProvider() 函数
//    tracer := otel.Tracer("example.com/foo")
func GetTracerProvider() trace.TracerProvider {
    return global.TracerProvider()
}

还记得的咱们之前剖析注册全局追踪的源码吗?otel.SetTracerProvider(tp)

func SetTracerProvider(tp trace.TracerProvider) {
    global.SetTracerProvider(tp)
}

所以,GetTracerProvider()办法获取的就是之前SetTracerProvider()设置的,所以GetTracerProvider()办法获取到的就是位于D:\go\pkg\mod\go.opentelemetry.io\otel\sdk@v1.14.0\trace\provider.go文件的 TracerProvider 构造体

type TracerProvider struct {
    mu             sync.Mutex
    namedTracer    map[instrumentation.Scope]*tracer
    spanProcessors atomic.Value
    isShutdown     bool

    // These fields are not protected by the lock mu. They are assumed to be
    // immutable after creation of the TracerProvider.
    sampler     Sampler
    idGenerator IDGenerator
    spanLimits  SpanLimits
    resource    *resource.Resource
}

接下来看 GetTracerProvider().Tracer(name, opts…),次要是搞清楚 Tracer 都做了什么
goland编辑器 ctrl + 鼠标左键 追踪 Tracer 跳转后发现它是个接口

type TracerProvider interface {
    Tracer(name string, options ...TracerOption) Tracer
}

这个时候不要慌,看看是谁调用的 Tracer 这个接口的,是GetTracerProvider(),那GetTracerProvider()最初返回的是什么呢?咱们之前剖析源码的时候得出,最终返回的是 &TracerProvider{},那必定就是这个构造体实现了 Tracer 接口,追过去,果然
// 这个办法是并发平安的.因为它加了互斥锁

func (p *TracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
    c := trace.NewTracerConfig(opts...)
    p.mu.Lock()
    defer p.mu.Unlock()
    if name == "" {
        name = defaultTracerName
    }
    is := instrumentation.Scope{
        Name:      name,
        Version:   c.InstrumentationVersion(),
        SchemaURL: c.SchemaURL(),
    }
    t, ok := p.namedTracer[is]
    if !ok {
        t = &tracer{
            provider:             p,
            instrumentationScope: is,
        }
        p.namedTracer[is] = t
        global.Info("Tracer created", "name", name, "version", c.InstrumentationVersion(), "schemaURL", c.SchemaURL())
    }
    return t
}
能够看到,这个办法加了互斥锁,最初返会了一个 tracer 构造体的指针

otel.Tracer(name).Start(context.Background(), “Handle”)
到这里,otel.Tracer(name)就看完了,接下来看 Start。
start是 Tracer 定义的一个接口
//地位:D:\go\pkg\mod\go.opentelemetry.io\otel\trace@v1.14.0\trace.go

type Tracer interface {
    Start(ctx context.Context, spanName string, opts ...SpanStartOption) (context.Context, Span)
}

后面咱们看源码晓得,otel.Tracer(name) 返回的是

t = &tracer{
    provider:             p,
    instrumentationScope: is,
}

所以看 tracer 这个构造体实现的 Start 办法即可,看代码:
//地位:D:\go\pkg\mod\go.opentelemetry.io\otel\sdk@v1.14.0\trace\tracer.go

// Start starts a Span and returns it along with a context containing it.
//
// The Span is created with the provided name and as a child of any existing
// span context found in the passed context. The created Span will be
// configured appropriately by any SpanOption passed.
func (tr *tracer) Start(ctx context.Context, name string, options ...trace.SpanStartOption) (context.Context, trace.Span) {
    config := trace.NewSpanStartConfig(options...)
    if ctx == nil {
        // Prevent trace.ContextWithSpan from panicking.
        ctx = context.Background()
    }
    // For local spans created by this SDK, track child span count.
    if p := trace.SpanFromContext(ctx); p != nil {
        if sdkSpan, ok := p.(*recordingSpan); ok {
            sdkSpan.addChild()
        }
    }
    s := tr.newSpan(ctx, name, &config)
    if rw, ok := s.(ReadWriteSpan); ok && s.IsRecording() {
        sps := tr.provider.spanProcessors.Load().(spanProcessorStates)
        for _, sp := range sps {
            sp.sp.OnStart(ctx, rw)
        }
    }
    if rtt, ok := s.(runtimeTracer); ok {
        ctx = rtt.runtimeTrace(ctx)
    }
    return trace.ContextWithSpan(ctx, s), s
}

剖析一下 Start 办法的代码
config := trace.NewSpanStartConfig(options…)
这行代码次要是为了原有参数满足不了,须要笼罩这些参数,所以在调用Start()的时候把第三个参数也写上

if ctx == nil {
        // Prevent trace.ContextWithSpan from panicking.
        ctx = context.Background()
}

如果上下文为空,就从新初始化一个上下文

if p := trace.SpanFromContext(ctx); p != nil {
        if sdkSpan, ok := p.(*recordingSpan); ok {
            sdkSpan.addChild()
        }
}

这段代码是为记录以后span下还有多个子span,通过对childSpanCount++实现
剩下的就没什么可说的了,点开看看就明确了

span.End()
回到span := otel.Tracer(name).Start(context.Background(),我晓得最初看一下 Start() 最初返回的是什么

func (tr *tracer) Start(ctx context.Context, name string, options ...trace.SpanStartOption) (context.Context, trace.Span) {
    config := trace.NewSpanStartConfig(options...)
    .......
    s := tr.newSpan(ctx, name, &config)
    ........
    return trace.ContextWithSpan(ctx, s), s
}

能够看到,最初返回的是s,接着看newSpan()

func (tr *tracer) newSpan(ctx context.Context, name string, config *trace.SpanConfig) trace.Span {
    .........
    return tr.newRecordingSpan(psc, sc, name, samplingResult, config)
}

再接着看 newRecordingSpan()

func (tr *tracer) newRecordingSpan(psc, sc trace.SpanContext, name string, sr SamplingResult, config *trace.SpanConfig) *recordingSpan {
    .............
    s := &recordingSpan{
        parent:      psc,
        spanContext: sc,
        spanKind:    trace.ValidateSpanKind(config.SpanKind()),
        name:        name,
        startTime:   startTime,
        events:      newEvictedQueue(tr.provider.spanLimits.EventCountLimit),
        links:       newEvictedQueue(tr.provider.spanLimits.LinkCountLimit),
        tracer:      tr,
    }
    .............
    return s
}

到这里就很分明了,span := otel.Tracer(name).Start(context.Background() 返回的就是 recordingSpan 构造体指针,接着咱们找到 recordingSpan 构造体所在文件,而后看看它所实现的 End() 办法
地位:D:\go\pkg\mod\go.opentelemetry.io\otel\sdk@v1.14.0\trace\span.go

func (s *recordingSpan) End(options ...trace.SpanEndOption) {
    // Do not start by checking if the span is being recorded which requires
    // acquiring a lock. Make a minimal check that the span is not nil.
    if s == nil {
        return
    }

    // Store the end time as soon as possible to avoid artificially increasing
    // the span's duration in case some operation below takes a while.
    et := internal.MonotonicEndTime(s.startTime)

    // Do relative expensive check now that we have an end time and see if we
    // need to do any more processing.
    if !s.IsRecording() {
        return
    }

    config := trace.NewSpanEndConfig(options...)
    if recovered := recover(); recovered != nil {
        // Record but don't stop the panic.
        defer panic(recovered)
        opts := []trace.EventOption{
            trace.WithAttributes(
                semconv.ExceptionType(typeStr(recovered)),
                semconv.ExceptionMessage(fmt.Sprint(recovered)),
            ),
        }

        if config.StackTrace() {
            opts = append(opts, trace.WithAttributes(
                semconv.ExceptionStacktrace(recordStackTrace()),
            ))
        }

        s.addEvent(semconv.ExceptionEventName, opts...)
    }

    if s.executionTracerTaskEnd != nil {
        s.executionTracerTaskEnd()
    }

    s.mu.Lock()
    // Setting endTime to non-zero marks the span as ended and not recording.
    if config.Timestamp().IsZero() {
        s.endTime = et
    } else {
        s.endTime = config.Timestamp()
    }
    s.mu.Unlock()

    sps := s.tracer.provider.spanProcessors.Load().(spanProcessorStates)
    if len(sps) == 0 {
        return
    }
    snap := s.snapshot()
    for _, sp := range sps {
        sp.sp.OnEnd(snap)
    }
}

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理