关于go:OpenTelemetry入门

40次阅读

共计 18835 个字符,预计需要花费 48 分钟才能阅读完成。

步骤

装置

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

正文完
 0