步骤
安装
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)
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。