步骤
装置
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)
}
}