1

步骤

安装

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/jaegerhttps://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)
    }
}

skystars
67 声望2 粉丝