1.说明
opentracing-go软件包是OpenTracing的golang平台的API
jaeger-client-go软件包是为Jaeger实现OpenTracing Go Tracer的工具库
特别注意: 这个库的导入路径是基于github.com/uber.不要尝试导入使用 github.com/jaegertracing,这将不可编译。官方在下个版本将会重新考虑这个问题
正确导入方式:
import "github.com/uber/jaeger-client-go"
错误导入方式:
import "github.com/jaegertracing/jaeger-client-go"
本文为什么提到这两个包,因为一个是golang平台的OpenTracing API,一个是实现了OpenTracing的,结合两者学习,更能明白如何在项目中使用他们。
2.Tracer
opentracing-go中的Tracer是一个简单的接口类型,用于Span的创建和SpanContext的传播。
type Tracer interface {
StartSpan(operationName string, opts ...StartSpanOption) Span
Inject(sm SpanContext, format interface{}, carrier interface{}) error
Extract(format interface{}, carrier interface{}) (SpanContext, error)
}
本部分,特别特别介绍一下StartSpan,StartSpan方法使用给定的operationName和提供的一系列StartSpanOption选项来创建,开启并返回一个新的Span。
type StartSpanOptions struct {
// 零个或者多个对其他Span的因果引用(通过他们的SpanContext)
// 如果为空,开启一个根`Span`(例如:启动一个新的trace)
References []SpanReference
// 该字段会覆盖Span的开始时间,如果StartTime.IsZero(),会设置为当前时间
StartTime time.Time
// 标签可能有零个或多个条目; 该map值的限制与Span.SetTag()相同. 可以为nil.
// 如果指定了,则调用方在StartSpan()调用时移交标签的所有权。
Tags map[string]interface{}
}
type StartSpanOption interface {
Apply(*StartSpanOptions)
}
StartSpanOption是一个接口类型,为什么使用接口类型,因为任何只要实现了Apply(*StartSpanOptions)方法的都可以作为StartSpan方法的参数。这也是opentracing-go中普遍意义上的模式,就像是一个通用意义上的标准,任何实现了这个标准的人,都可以在这里使用。这种模式再后续中还会经常见到。
重点就是这个StartSpanOptions,StartSpan方法提供的所有的StartSpanOption参数都将Apply到这个结构体上。其实也没有什么特别的地方,主要涵盖三个方面内容的东西,一个是Span的开始时间,一个是Span的对其他Span的引用关系列表,一个是多个标签的映射关系。
StartSpanOptions结构体允许Tracer.StartSpan()调用者和实现者采用一种机制来覆盖开始时间戳,指定Span References以及在Span开始时间提供单个或多个Tag。
StartSpan()调用者应该查找此包中可用的StartSpanOption接口和实现者。
第一个StartSpanOption接口实现者:SpanReference,该实现者用于设置StartSpanOptions结构体中的References
type SpanReferenceType int
const (
ChildOfRef SpanReferenceType = iota
FollowsFromRef
)
type SpanReference struct {
Type SpanReferenceType
ReferencedContext SpanContext
}
func (r SpanReference) Apply(o *StartSpanOptions) {
if r.ReferencedContext != nil {
o.References = append(o.References, r)
}
}
func ChildOf(sc SpanContext) SpanReference {
return SpanReference{
Type: ChildOfRef,
ReferencedContext: sc,
}
}
func FollowsFrom(sc SpanContext) SpanReference {
return SpanReference{
Type: FollowsFromRef,
ReferencedContext: sc,
}
}
从上面的方法可以看到,如果SpanReference在创建的时候提供的ReferencedContext为nil也没关系,不会有什么影响。
例如,在下面的代码中
sc, _ := tracer.Extract(someFormat, someCarrier)
span := tracer.StartSpan("operation", opentracing.ChildOf(sc))
如果提供的sc== nil opentracing.ChildOf方法也不会panic
第二个StartSpanOption接口实现者:StartTime,该实现者用于设置StartSpanOptions结构体中的StartTime
type StartTime time.Time
// Apply satisfies the StartSpanOption interface.
func (t StartTime) Apply(o *StartSpanOptions) {
o.StartTime = time.Time(t)
}
StartTime用于显式的设置新Span的开始时间。
第三个StartSpanOption接口实现者:Tags,该实现者用于设置StartSpanOptions结构体中的Tags
type Tags map[string]interface{}
// Apply satisfies the StartSpanOption interface.
func (t Tags) Apply(o *StartSpanOptions) {
if o.Tags == nil {
o.Tags = make(map[string]interface{})
}
for k, v := range t {
o.Tags[k] = v
}
}
第四个StartSpanOption接口实现者:Tag,也用于设置StartSpanOptions结构体中的Tags
type Tag struct {
Key string
Value interface{}
}
// Apply satisfies the StartSpanOption interface.
func (t Tag) Apply(o *StartSpanOptions) {
if o.Tags == nil {
o.Tags = make(map[string]interface{})
}
o.Tags[t.Key] = t.Value
}
// Set applies the tag to an existing Span.
func (t Tag) Set(s Span) {
s.SetTag(t.Key, t.Value)
}
Tag不仅可以作为StartSpanOption用于添加一个tag到新的Span,也可以使用Set方法在一个已经存在的Span上设置tag
Tracer实现者可以将StartSpanOption接口实例的切片转换为一个StartSpanOptions结构体像下面一样
func StartSpan(opName string, opts ...opentracing.StartSpanOption) {
sso := opentracing.StartSpanOptions{}
for _, o := range opts {
o.Apply(&sso)
}
...
}
如果Span没有提供一个SpanReference选项就将变成自己trace跟踪的根。例如:
var tracer opentracing.Tracer = ...
// The root-span case:
sp := tracer.StartSpan("GetFeed")
// The vanilla child span case:
sp := tracer.StartSpan(
"GetFeed",
opentracing.ChildOf(parentSpan.Context()))
// All the bells and whistles:
sp := tracer.StartSpan(
"GetFeed",
opentracing.ChildOf(parentSpan.Context()),
opentracing.Tag{"user_agent", loggedReq.UserAgent},
opentracing.StartTime(loggedReq.Timestamp),
)
有关StartSpan的内容,你可能到现在为止还是一知半解的。没关系,我们来学学jaeger-client-go,看看它是如何实现我们的Tracer接口的中的StartSpan方法的,并如何记录StartTime等这些启动时的信息的。
func (t *Tracer) StartSpan(
operationName string,
options ...opentracing.StartSpanOption,
) opentracing.Span {
sso := opentracing.StartSpanOptions{}
for _, o := range options {
o.Apply(&sso)
}
return t.startSpanWithOptions(operationName, sso)
}
看看嘛,整个代码思路都是上面提及的,所有的options都应用到sso上。然后调用接下来的startSpanWithOptions方法,该方法返回opentracing.Span,至于这块内容,我们后面看完SpanContext和Span之后再看,会更加有感觉。
Inject/Extract方法先大致了解一些,后续再详细了解,因为刚开始,我们的关注点在Tracer是如何开启一个Span的,而注入和提取的内容在后续的时候才会用到。
Inject() 方法接受一个sm SpanContext类型的实例,然后将它注入到carrier载体中传播,
载体carrier的实际类型依赖于format的值。
OpenTracing 定义了一组通用的format值(可以查看BuiltinFormat),并且每一个都有一个预期的载体类型。
其他包可以声明他们自己的format值,就像在golang context.Context中使用的key
func WithValue(parent Context, key, val interface{}) Context
注意:所有opentracing.Tracer的实现都必须支持内建的format.如果对于实现者format不支持或者未知,那么将返回opentracing.ErrUnsupportedFormat
如果format支持,但注入还是失败了,实现者将返回opentracing.ErrInvalidCarrier或者其他实现者指定的错误。
Extract()方法根据提供的format和carrier载体返回一个SpanContext实例
返回值:
- 一个成功提取方法的执行返回一个SpanContext实例,和一个nil错误
- 如果载体
carrier中没有SpanContext提取,Extract()方法返回nil和opentracing.ErrSpanContextNotFound - 如果
format不支持或者无法识别,Extract()方法返回nil或者opentracing.ErrUnsupportedFormat - 如果
carrier对象还有更多基本问题,Extract()方法将返回opentracing.ErrInvalidCarrier,opentracing.ErrSpanContextCorrupted或者 其他实现者特定的错误。
3.Span和SpanContext
opentracing-go中的Span和SpanContext都是接口类型
type SpanContext interface {
ForeachBaggageItem(handler func(k, v string) bool)
}
SpanContext表示必须传播到后继Span和跨过程边界的Span状态 (例如, 一个<trace_id, span_id, sampled>元祖).
其所需方法ForeachBaggageItem授权访问存储在SpanContext上的所有的baggage items,其中方法的参数handler是一个函数类型,将应用在每个baggage键值对上。items的顺序不保证,结果返回值的bool类型表示handler是否要继续迭代后续的baggage items。例如如果handler想要通过模式匹配名称找到一些baggage item,一旦发现该item,它可以返回false,停止进一步的迭代。
这个时候,就可以拿出jaeger-client-go来看看他的实现方案
// TraceID 表示跟踪的唯一128位标识符
type TraceID struct {
High, Low uint64
}
// SpanID 表示一个span的唯一64位标识符
type SpanID uint64
// SpanContext 代表传播的span的身份和状态
type SpanContext struct {
// traceID 代表trace全局唯一的ID
// 通常作为一个随机数生产
traceID TraceID
// spanID表示在其trace中必须唯一的spanID
// 但不必是全局唯一的。
spanID SpanID
// parentID是指父span的ID。
// 如果当前span是一个根span,该值需为0
parentID SpanID
// 分布式上下文包。 是即时的快照。
baggage map[string]string
// 从TextMap载体中提取上下文时,可以将debugID设置为某些相关ID。
debugID string
// samplingState 跨所有span分享
samplingState *samplingState
// 标识span context代表一个远程的parent
remote bool
}
func (c SpanContext) ForeachBaggageItem(handler func(k, v string) bool) {
for k, v := range c.baggage {
if !handler(k, v) {
break
}
}
}
再来看看Span
type Span interface {
Finish()
FinishWithOptions(opts FinishOptions)
Context() SpanContext
SetOperationName(operationName string) Span
SetTag(key string, value interface{}) Span
LogFields(fields ...log.Field)
LogKV(alternatingKeyValues ...interface{})
SetBaggageItem(restrictedKey, value string) Span
BaggageItem(restrictedKey string) string
Tracer() Tracer
// Deprecated: use LogFields or LogKV
LogEvent(event string)
// Deprecated: use LogFields or LogKV
LogEventWithPayload(event string, payload interface{})
// Deprecated: use LogFields or LogKV
Log(data LogData)
}
Span在OpenTracing系统中代表一个活跃的,未完成的span。由Tracer接口来创建。 我们来看一下Span中需要实现的方法
第一个为Finish()方法,该方法用于设置Span的结束时间戳,并设置Span的结束状态。除了对Context()的调用(始终允许)之外,Finish()必须是对任何span实例的最后一次调用,否则将导致未定义的行为。
第二个方法为FinishWithOptions(opts FinishOptions)该方法就像Finish()但是可以明确控制时间戳和日志数据。参数一个FinishOptions类型的数据.
// LogRecord是Span log关联的数据。每个LogRecord实例必须指定至少一个Field
type LogRecord struct {
Timestamp time.Time
Fields []log.Field
}
// FinishOptions 允许Span.FinishWithOptions调用者覆盖结束时间戳和提供日志数据
type FinishOptions struct {
// FinishTime overrides the Span's finish time, or implicitly becomes
// time.Now() if FinishTime.IsZero().
// FinishTime覆盖Span的结束时间,如果FinishTime.IsZero()将会把Span结束时间隐式的设置为当前时间。
// FinishTime必须解析为一个大于等于Span.StartTime的时间戳。
FinishTime time.Time
// LogRecords允许调用者使用单个切片指定许多LogFields()调用的内容。 可能为零。
// LogRecord.Timestamp调用.IsZero()不能返回true,这就意味着必须明确设置其时间戳。
// 并且,他们的值必须大于等于Span的开始时间,且小于等于Span的结束时间。
// 否则FinishWithOptions() 的行为是未知的.
//
// 如果指定,则调用者在FinishWithOptions()调用时移交LogRecords的所有权。
//
// 如果指定,已经弃用的字段必须设置为nil或者为空。
LogRecords []LogRecord
// BulkLogData 已经弃用.
BulkLogData []LogData
}
第三个方法是Context() SpanContext该方法返回的是针对该Span的SpanContext.注意在Span.Finish()方法调用完成后,Context()的返回值依然是有效的。
第四个方法是SetOperationName(operationName string) Span用于设置或者修改操作名称。返回对此Span的引用以进行链接。
第五个方法是SetTag(key string, value interface{}) Span用于将一个标签添加到Span上。如果key已经存在,该操作将覆盖之前的值。value可以是数字类型,字符串,或者布尔类型。其他类型的数据作为value时,在OpenTracing层面上是未知的。如果跟踪系统不知道如何处理一个特别value类型,那么他将忽略掉该Tag但是不会panic,同样的该方法也会返回Span的引用可以进行链式处理。
第六个方法为LogFields(fields ...log.Field)该方法是一个有效的且类型检测的方法来记录Span的key:value日志数据,尽管编程接口比LogKV()更为冗长。下面是一个示例:
span.LogFields(
log.String("event", "soft error"),
log.String("type", "cache timeout"),
log.Int("waited.millis", 1500))
第七个方法为LogKV(alternatingKeyValues ...interface{}),该方法是一种简洁,易读的方式来记录有关Span的key:value记录数据,尽管遗憾的是,这也使LogKV的效率和类型安全性低于LogFields()。示例:
span.LogKV(
"event", "soft error",
"type", "cache timeout",
"waited.millis", 1500)
对于LogKV(与LogFields()相对),参数必须显示为键值对,就像这样span.LogKV(key1, val1, key2, val2, key3, val3, ...)所有key必须是string类型,value可以是字符串,数字类型,布尔类型,Go error实例,或者任意结构体类型。
第八个方法为SetBaggageItem(restrictedKey, value string) Span,用于在Span上设置一个键值对并且其SpanContext也会将其传播到此Span的后代。SetBaggageItem()实现了强大的功能,并提供了全栈opentracing集成(例如,来自移动端app的任意应用程序数据都可以透明地使其完全进入存储系).并带来一些强大的成本:使用此功能 功能要小心。需要特别注意的由两点:1.SetBaggageItem()只会将行baggage items传播到将来的因果关系的关联的Span。2。需要特别谨慎的使用。 因为每个键和值都会复制到关联的Span的每个本地和远程子节点中,这可能会增加大量的网络和cpu开销。
同样,该方法也会返回Span的引用,可用于链式操作
第九个方法为BaggageItem(restrictedKey string) string,该方法用于获取baggage中指定key的值,如果在Span中未找到给定的值将放回空字符串。
第十个方法为Tracer() Tracer,该方法用于访问创建该Span的跟踪器Tracer
后续已经过时的方法就不必关注了。
看看jaeger-client-go中的具体Span实现者
type Span struct {
// referenceCounter用于增加对象的生命周期记录在将其放入到池中之前
referenceCounter int32
sync.RWMutex
tracer *Tracer
// TODO: (breaking change) change to use a pointer
context SpanContext
operationName string
// 如果为true,标识span是当前进程中span树的根span.
firstInProcess bool
// span开始的时间戳,微妙精度。
startTime time.Time
// duration以微秒级的精度返回跨度的持续时间。
// 零值标识持续时间未知
duration time.Duration
// 附加到当前span到标签列表
tags []Tag
// 当前span到微日记
logs []opentracing.LogRecord
// 因为MaxLogsPerSpan删除到log数量
numDroppedLogs int
// 当前span的引用列表references
references []Reference
observer ContribSpanObserver
}
这个时候再回头看看jaeger-client-go中Tracer在创建Span的StartSpan函数中调用的startSpanWithOptions
func (t *Tracer) startSpanWithOptions(
operationName string,
options opentracing.StartSpanOptions,
) opentracing.Span {
if options.StartTime.IsZero() {
options.StartTime = t.timeNow()
}
// Predicate whether the given span context is an empty reference
// or may be used as parent / debug ID / baggage items source
isEmptyReference := func(ctx SpanContext) bool {
return !ctx.IsValid() && !ctx.isDebugIDContainerOnly() && len(ctx.baggage) == 0
}
var references []Reference
var parent SpanContext
var hasParent bool // need this because `parent` is a value, not reference
var ctx SpanContext
var isSelfRef bool
for _, ref := range options.References {
ctxRef, ok := ref.ReferencedContext.(SpanContext)
if !ok {
t.logger.Error(fmt.Sprintf(
"Reference contains invalid type of SpanReference: %s",
reflect.ValueOf(ref.ReferencedContext)))
continue
}
if isEmptyReference(ctxRef) {
continue
}
if ref.Type == selfRefType {
isSelfRef = true
ctx = ctxRef
continue
}
if ctxRef.IsValid() {
// we don't want empty context that contains only debug-id or baggage
references = append(references, Reference{Type: ref.Type, Context: ctxRef})
}
if !hasParent {
parent = ctxRef
hasParent = ref.Type == opentracing.ChildOfRef
}
}
if !hasParent && !isEmptyReference(parent) {
// If ChildOfRef wasn't found but a FollowFromRef exists, use the context from
// the FollowFromRef as the parent
hasParent = true
}
rpcServer := false
if v, ok := options.Tags[ext.SpanKindRPCServer.Key]; ok {
rpcServer = (v == ext.SpanKindRPCServerEnum || v == string(ext.SpanKindRPCServerEnum))
}
var internalTags []Tag
newTrace := false
if !isSelfRef {
if !hasParent || !parent.IsValid() {
newTrace = true
ctx.traceID.Low = t.randomID()
if t.options.gen128Bit {
ctx.traceID.High = t.options.highTraceIDGenerator()
}
ctx.spanID = SpanID(ctx.traceID.Low)
ctx.parentID = 0
ctx.samplingState = &samplingState{
localRootSpan: ctx.spanID,
}
if hasParent && parent.isDebugIDContainerOnly() && t.isDebugAllowed(operationName) {
ctx.samplingState.setDebugAndSampled()
internalTags = append(internalTags, Tag{key: JaegerDebugHeader, value: parent.debugID})
}
} else {
ctx.traceID = parent.traceID
if rpcServer && t.options.zipkinSharedRPCSpan {
// Support Zipkin's one-span-per-RPC model
ctx.spanID = parent.spanID
ctx.parentID = parent.parentID
} else {
ctx.spanID = SpanID(t.randomID())
ctx.parentID = parent.spanID
}
ctx.samplingState = parent.samplingState
if parent.remote {
ctx.samplingState.setFinal()
ctx.samplingState.localRootSpan = ctx.spanID
}
}
if hasParent {
// copy baggage items
if l := len(parent.baggage); l > 0 {
ctx.baggage = make(map[string]string, len(parent.baggage))
for k, v := range parent.baggage {
ctx.baggage[k] = v
}
}
}
}
sp := t.newSpan()
sp.context = ctx
sp.tracer = t
sp.operationName = operationName
sp.startTime = options.StartTime
sp.duration = 0
sp.references = references
sp.firstInProcess = rpcServer || sp.context.parentID == 0
if !sp.isSamplingFinalized() {
decision := t.sampler.OnCreateSpan(sp)
sp.applySamplingDecision(decision, false)
}
sp.observer = t.observer.OnStartSpan(sp, operationName, options)
if tagsTotalLength := len(options.Tags) + len(internalTags); tagsTotalLength > 0 {
if sp.tags == nil || cap(sp.tags) < tagsTotalLength {
sp.tags = make([]Tag, 0, tagsTotalLength)
}
sp.tags = append(sp.tags, internalTags...)
for k, v := range options.Tags {
sp.setTagInternal(k, v, false)
}
}
t.emitNewSpanMetrics(sp, newTrace)
return sp
}
现在再看这个方法的实现,是不是耳目一新。除了最后sp.observer部分,我不相信到现在,你还有看不懂的地方。
4.传播中的carrier载体格式
我们在上一篇文章中提到所有平台都要求OpenTracing实现支持两种载体格式:一种为text map样式,一种为binary样式,这其实是OpenTracing中默认的格式
// BuiltinFormat用于在opentracing包中划定旨在与Tracer.Inject()和Tracer.Extract()方法一起使用的值。
type BuiltinFormat byte
const (
// Binary 将SpanContexts表示为不透明的二进制数据。
//
// 对于 Tracer.Inject(): the carrier 必须是一个 `io.Writer`.
//
// 对于 Tracer.Extract(): the carrier 必须是一个 `io.Reader`.
Binary BuiltinFormat = iota
// TextMap将SpanContexts表示为键:值字符串对。
//
// 与HTTPHeaders不同,TextMap格式不以任何方式限制键或值字符集。
//
// 对于 Tracer.Inject(): the carrier 必须是一个 `TextMapWriter`.
//
// 对于 Tracer.Extract(): the carrier 必须是一个 `TextMapReader`.
TextMap
// HTTPHeaders将SpanContexts表示为HTTP标头字符串对。
//
// 与TextMap不同,HTTPHeaders格式要求键和值按原样作为HTTP标头有效
//(即,字符大小写可能不稳定,键中不允许使用特殊字符,值应转义URL,等等)
//
// 对于 Tracer.Inject(): the carrier 必须是一个 `TextMapWriter`.
//
// 对于 Tracer.Extract(): the carrier 必须是一个 `TextMapReader`.
HTTPHeaders
)
// TextMapWriter 是内建TextMap格式的Inject()载体。 With
// 通过它,调用者可以对SpanContext进行编码,以作为unicode字符串映射中的条目传播。
type TextMapWriter interface {
// 将一个键值对设置进载体carrier中.对同一个key多次调用Set()方法将导致未定义的行为。
// 注意:TextMapWriter的后备存储区可能包含与SpanContext不相关的数据。
// 这样,调用TextMapWriter和TextMapReader接口的Inject()和Extract()实现
// 必须在前缀或其他约定上达成共识,以区分其自己的key:value对。
Set(key, val string)
}
// TextMapReader是TextMap内置格式的Extract()载体。
// 使用它,调用者可以将传播的SpanContext解码为Unicode字符串映射中的条目。
type TextMapReader interface {
// ForeachKey通过重复调用`handler`函数来返回TextMap内容。
// 如果任何对`handler`的调用返回非nil错误,则ForeachKey终止并返回该错误。
// 注意: TextMapReader的后备存储区可能包含与SpanContext不相关的数据。
// 因此,调用TextMapWriter和TextMapReader接口的Inject()和Extract()实现
// 必须在前缀或其他约定上达成共识,以区分自己的key:value对。
//
// “ foreach”回调模式在某些情况下减少了不必要的复制,并且还允许实现者在读取映射时持有锁。
ForeachKey(handler func(key, val string) error) error
}
比如,HTTPHeadersCarrier类型为http.Header.同时实现了TextMapWriter和TextMapReader,就可以作为Inject()和Extract()方法的carrier载体。
type HTTPHeadersCarrier http.Header
// Set conforms to the TextMapWriter interface.
func (c HTTPHeadersCarrier) Set(key, val string) {
h := http.Header(c)
h.Set(key, val)
}
// ForeachKey conforms to the TextMapReader interface.
func (c HTTPHeadersCarrier) ForeachKey(handler func(key, val string) error) error {
for k, vals := range c {
for _, v := range vals {
if err := handler(k, v); err != nil {
return err
}
}
}
return nil
}
客户端示例:
carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)
err := tracer.Inject(
span.Context(),
opentracing.HTTPHeaders,
carrier)
服务端示例:
carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)
clientContext, err := tracer.Extract(opentracing.HTTPHeaders, carrier)
// 假设此处的最终目标是使用服务器端Span跟踪
var serverSpan opentracing.Span
if err == nil {
span = tracer.StartSpan(
rpcMethodName, ext.RPCServerOption(clientContext))
} else {
span = tracer.StartSpan(rpcMethodName)
}
5.NoopTracer
以下是NoopTracer的全部内容,实现了Tracer接口,noopSpan实现了Span接口,noopSpanContext实现了SpanContext接口,只不过这些实现里面,不做什么操作罢了。
type NoopTracer struct{}
type noopSpan struct{}
type noopSpanContext struct{}
var (
defaultNoopSpanContext SpanContext = noopSpanContext{}
defaultNoopSpan Span = noopSpan{}
defaultNoopTracer Tracer = NoopTracer{}
)
const (
emptyString = ""
)
// noopSpanContext:
func (n noopSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {}
// noopSpan:
func (n noopSpan) Context() SpanContext { return defaultNoopSpanContext }
func (n noopSpan) SetBaggageItem(key, val string) Span { return n }
func (n noopSpan) BaggageItem(key string) string { return emptyString }
func (n noopSpan) SetTag(key string, value interface{}) Span { return n }
func (n noopSpan) LogFields(fields ...log.Field) {}
func (n noopSpan) LogKV(keyVals ...interface{}) {}
func (n noopSpan) Finish() {}
func (n noopSpan) FinishWithOptions(opts FinishOptions) {}
func (n noopSpan) SetOperationName(operationName string) Span { return n }
func (n noopSpan) Tracer() Tracer { return defaultNoopTracer }
func (n noopSpan) LogEvent(event string) {}
func (n noopSpan) LogEventWithPayload(event string, payload interface{}) {}
func (n noopSpan) Log(data LogData) {}
// StartSpan belongs to the Tracer interface.
func (n NoopTracer) StartSpan(operationName string, opts ...StartSpanOption) Span {
return defaultNoopSpan
}
// Inject belongs to the Tracer interface.
func (n NoopTracer) Inject(sp SpanContext, format interface{}, carrier interface{}) error {
return nil
}
// Extract belongs to the Tracer interface.
func (n NoopTracer) Extract(format interface{}, carrier interface{}) (SpanContext, error) {
return nil, ErrSpanContextNotFound
}
6.globalTracer
全局的跟踪器默认就是一个NoopTracer.
type registeredTracer struct {
tracer Tracer
isRegistered bool
}
var (
globalTracer = registeredTracer{NoopTracer{}, false}
)
func SetGlobalTracer(tracer Tracer) {
globalTracer = registeredTracer{tracer, true}
}
// GlobalTracer 返回全局单例`Tracer`实现
// 在`SetGlobalTracer()`调用之前,`GlobalTracer()`是一个noop实现,它将丢弃所有传递给它的数据。
func GlobalTracer() Tracer {
return globalTracer.tracer
}
当然了,可以通过调用SetGlobalTracer进行设置,而不是使用默认的NoopTracer,特别注意SetGlobalTracer设置由GlobalTracer()返回的单例opentracing.Tracer。 那些使用GlobalTracer(而不是直接管理opentracing.Tracer实例)的用户应在main()之前尽早调用SetGlobalTracer,然后再调用下面的StartSpan全局函数。 在调用SetGlobalTracer之前,通过StartSpan等全局变量启动的所有Span都是Noop。
所以,我们在实际应用中,在实例化好Tracer之后,都要先SetGlobalTracer,然后再GlobalTracer.
func main() {
t, io, err := tracer.NewTracer(name, "")
if err != nil {
log.Fatal(err)
}
defer io.Close()
opentracing.SetGlobalTracer(t)
....
}
7. tag
上一篇文章中,在讲述Span时,提到了常见的span tag,比如 db.instance指明数据库的主机, http.status_code 标识http返回状态码, 或error,如果由跨度表示的操作失败,则可以将其设置为true。
在opentracing-go的ext目录下,都有定义
var (
//////////////////////////////////////////////////////////////////////
// SpanKind (client/server or producer/consumer)
//////////////////////////////////////////////////////////////////////
// SpanKind hints at relationship between spans, e.g. client/server
SpanKind = spanKindTagName("span.kind")
// SpanKindRPCClient marks a span representing the client-side of an RPC
// or other remote call
SpanKindRPCClientEnum = SpanKindEnum("client")
SpanKindRPCClient = opentracing.Tag{Key: string(SpanKind), Value: SpanKindRPCClientEnum}
// SpanKindRPCServer marks a span representing the server-side of an RPC
// or other remote call
SpanKindRPCServerEnum = SpanKindEnum("server")
SpanKindRPCServer = opentracing.Tag{Key: string(SpanKind), Value: SpanKindRPCServerEnum}
// SpanKindProducer marks a span representing the producer-side of a
// message bus
SpanKindProducerEnum = SpanKindEnum("producer")
SpanKindProducer = opentracing.Tag{Key: string(SpanKind), Value: SpanKindProducerEnum}
// SpanKindConsumer marks a span representing the consumer-side of a
// message bus
SpanKindConsumerEnum = SpanKindEnum("consumer")
SpanKindConsumer = opentracing.Tag{Key: string(SpanKind), Value: SpanKindConsumerEnum}
//////////////////////////////////////////////////////////////////////
// Component name
//////////////////////////////////////////////////////////////////////
// Component is a low-cardinality identifier of the module, library,
// or package that is generating a span.
Component = StringTagName("component")
//////////////////////////////////////////////////////////////////////
// Sampling hint
//////////////////////////////////////////////////////////////////////
// SamplingPriority determines the priority of sampling this Span.
SamplingPriority = Uint16TagName("sampling.priority")
//////////////////////////////////////////////////////////////////////
// Peer tags. These tags can be emitted by either client-side or
// server-side to describe the other side/service in a peer-to-peer
// communications, like an RPC call.
//////////////////////////////////////////////////////////////////////
// PeerService records the service name of the peer.
PeerService = StringTagName("peer.service")
// PeerAddress records the address name of the peer. This may be a "ip:port",
// a bare "hostname", a FQDN or even a database DSN substring
// like "mysql://username@127.0.0.1:3306/dbname"
PeerAddress = StringTagName("peer.address")
// PeerHostname records the host name of the peer
PeerHostname = StringTagName("peer.hostname")
// PeerHostIPv4 records IP v4 host address of the peer
PeerHostIPv4 = IPv4TagName("peer.ipv4")
// PeerHostIPv6 records IP v6 host address of the peer
PeerHostIPv6 = StringTagName("peer.ipv6")
// PeerPort records port number of the peer
PeerPort = Uint16TagName("peer.port")
//////////////////////////////////////////////////////////////////////
// HTTP Tags
//////////////////////////////////////////////////////////////////////
// HTTPUrl should be the URL of the request being handled in this segment
// of the trace, in standard URI format. The protocol is optional.
HTTPUrl = StringTagName("http.url")
// HTTPMethod is the HTTP method of the request, and is case-insensitive.
HTTPMethod = StringTagName("http.method")
// HTTPStatusCode is the numeric HTTP status code (200, 404, etc) of the
// HTTP response.
HTTPStatusCode = Uint16TagName("http.status_code")
//////////////////////////////////////////////////////////////////////
// DB Tags
//////////////////////////////////////////////////////////////////////
// DBInstance is database instance name.
DBInstance = StringTagName("db.instance")
// DBStatement is a database statement for the given database type.
// It can be a query or a prepared statement (i.e., before substitution).
DBStatement = StringTagName("db.statement")
// DBType is a database type. For any SQL database, "sql".
// For others, the lower-case database category, e.g. "redis"
DBType = StringTagName("db.type")
// DBUser is a username for accessing database.
DBUser = StringTagName("db.user")
//////////////////////////////////////////////////////////////////////
// Message Bus Tag
//////////////////////////////////////////////////////////////////////
// MessageBusDestination is an address at which messages can be exchanged
MessageBusDestination = StringTagName("message_bus.destination")
//////////////////////////////////////////////////////////////////////
// Error Tag
//////////////////////////////////////////////////////////////////////
// Error indicates that operation represented by the span resulted in an error.
Error = BoolTagName("error")
)
这些常量定义了推荐的通用标签名称,以更好地在跟踪系统和语言/平台之间进行移植。注释中有说明不同的应用场景。
对于标签值的不同数据类型,都有特定的包装类型,其设置方法都是接收Span和需要设置的value,在Set方法中都是调用span.SetTag()方法。
// StringTagName is a common tag name to be set to a string value
type StringTagName string
// Set adds a string tag to the `span`
func (tag StringTagName) Set(span opentracing.Span, value string) {
span.SetTag(string(tag), value)
}