go2sky的封装及使用
初始化go2sky
package skyimport ("encoding/json""github.com/SkyAPM/go2sky""github.com/SkyAPM/go2sky/reporter""log""sync"
)var once sync.Oncevar globalTracer *go2sky.Tracerfunc NewGlobalTracer(addr, srvName string, ops ...ReporterOption) {once.Do(func() {globalTracer = NewTracer(addr, srvName, ops...)})
}func NewTracer(addr, srvName string, ops ...ReporterOption) *go2sky.Tracer {r, err := reporter.NewGRPCReporter(addr)if err != nil {log.Printf("new reporter error %v \n", err)return nil}// new warped reportnr := &Report{Reporter: r}for _, op := range ops {op(nr)}tracer, err := go2sky.NewTracer(srvName, go2sky.WithReporter(nr))if err != nil {log.Printf("create tracer error %v \n", err)}return tracer
}// Report warp go2sky.GRPCReporter
type Report struct {go2sky.Reporterdebug bool
}type ReporterOption func(r *Report)func WithDebug(debug bool) ReporterOption {return func(r *Report) {r.debug = debug}
}func (t *Report) Send(spans []go2sky.ReportedSpan) {t.Reporter.Send(spans)if t.debug {bs, _ := json.Marshal(spans)log.Printf("trace span:%s", string(bs))}
}
操作封装
package skyimport ("context""github.com/SkyAPM/go2sky""github.com/SkyAPM/go2sky/propagation"v3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3""time"
)// Trace 封装 go2sky 的 Span,提供了创建入口、出口和本地 Trace 的方法
// 简化使用 go2sky 的 Span 的流程,屏蔽组件未初始化、创建span报错、span判空,提供了更友好的接口
// DefaultTrace 是一个实现了 Trace 接口的结构体,代表一个有效的 Trace
// NonTrace 是一个实现了 Trace 接口的空结构体,代表一个无效的 Trace
// 当go2sky没有初始化时,返回 NonTrace,避免代码中出现空指针异常
type Trace interface {Context() context.Context // 返回携带链路信息的 context.Context,由 go2sky 生成End() // 结束当前的 TraceTag(key, value string) // 设置tag,在ui上可以进行过滤SetComponent(int32) // 设置组件ID,skywalking oap-server的component-libraries.yml文件规范了中间件组件的ID,如dubbo=3SetSpanLayer(v3.SpanLayer) // 设置span的类型,例如 v3.SpanLayer_RPCFrameworkChildLocalTrace(optName string) Trace // 创建local类型的子TraceChildExitTrace(optName, peer string) (Trace, string, string) // 创建exit类型的子Trace,peer是GetTraceId() string // 获取当前Trace的TraceIdError(time.Time, ...string) // 记录错误信息,time.Time是错误发生的时间,...string是错误信息Log(time.Time, ...string) // 记录错误信息,time.Time是错误发生的时间,...string是错误信息IsExit() bool // 判断是否ExitIsEntry() bool // 判断是否EntryIsNon() bool // Trace 是 NonTrace 实例 返回 true
}// nonTrace 判读组件是否可用
func noTracer() bool {return globalTracer == nil
}// CreateInnerEntry 自动创建sw8的入口span
func CreateInnerEntry(ctx context.Context, optName string) Trace {if noTracer() {return nonTrace}// CreateEntrySpan 的回调函数返回""时,底层会自动生成相关的内容s, c, err := globalTracer.CreateEntrySpan(ctx, optName, func(headerKey string) (string, error) {return "", nil})return evalTrace(s, c, err)
}// CreateEntry 创建指定sw8 header入口的trace
func CreateEntry(ctx context.Context, optName string, sw8, sw8correlation string) Trace {if noTracer() {return nonTrace}// sw8 header必须符合规范,否则底层会报错s, c, err := globalTracer.CreateEntrySpan(ctx, optName, func(headerKey string) (string, error) {if headerKey == propagation.Header {return sw8, nil}if headerKey == propagation.HeaderCorrelation {return sw8correlation, nil}return "", nil})return evalTrace(s, c, err)
}// CreateExit 创建出口类型的trace,同时返回sw8、sw8correlation,便于开发者透传给下游服务
func CreateExit(ctx context.Context, optName, peer string) (t Trace, sw8, sw8correlation string) {if noTracer() {return nonTrace, "", ""}s, err := globalTracer.CreateExitSpan(ctx, optName, peer, func(headerKey, headerValue string) error {if headerKey == propagation.Header {sw8 = headerValue}if headerKey == propagation.HeaderCorrelation {sw8correlation = headerValue}return nil})return evalTrace(s, ctx, err), sw8, sw8correlation
}// CreateLocal 创建本地类型的trace
func CreateLocal(ctx context.Context, optName string) Trace {if noTracer() {return nonTrace}s, c, err := globalTracer.CreateLocalSpan(ctx, go2sky.WithOperationName(optName))return evalTrace(s, c, err)
}// evalTrace 评估并返回一个 Trace 对象
func evalTrace(s go2sky.Span, c context.Context, err error) Trace {if noTracer() {return nonTrace}if err != nil {return nonTrace}if s == nil {return nonTrace}id := go2sky.TraceID(c)t := &DefaultTrace{ctx: c,span: s,traceId: id,}return t
}
Trace实现
NonTrace兜底实现
package skyimport ("context""github.com/SkyAPM/go2sky"v3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3""time"
)var nonTrace = &NonTrace{}type NonTrace struct {
}func NewNonTrace() *NonTrace {return &NonTrace{}
}func (n NonTrace) End() {
}func (n NonTrace) Tag(key, value string) {
}func (n NonTrace) SetComponent(i int32) {
}func (n NonTrace) SetSpanLayer(layer v3.SpanLayer) {
}func (n NonTrace) Context() context.Context {return context.Background()
}func (n NonTrace) ChildLocalTrace(optName string) Trace {return n
}func (n NonTrace) ChildExitTrace(optName, peer string) (Trace, string, string) {return n, "", ""
}func (n NonTrace) GetTraceId() string {return go2sky.EmptyTraceID
}func (n NonTrace) IsExit() bool {return false
}func (n NonTrace) IsEntry() bool {return false
}func (n NonTrace) IsNon() bool {return true
}func (n NonTrace) Error(time time.Time, s ...string) {
}func (n NonTrace) Log(t time.Time, s ...string) {
}
DefaultTrace
package skyimport ("context""github.com/SkyAPM/go2sky"v3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3""sync""time"
)type DefaultTrace struct {ctx context.Contextspan go2sky.Spanonce sync.OncetraceId string
}// End 结束当前的 DefaultTrace
func (t *DefaultTrace) End() {if t == nil {return}if t.span != nil {t.once.Do(func() {t.span.End()})}
}func (t *DefaultTrace) Tag(key, value string) {if t.span != nil {t.span.Tag(go2sky.Tag(key), value)}
}func (t *DefaultTrace) SetComponent(i int32) {if t.span != nil {t.span.SetComponent(i)}
}func (t *DefaultTrace) SetSpanLayer(layer v3.SpanLayer) {if t.span != nil {t.span.SetSpanLayer(layer)}
}func (t *DefaultTrace) Context() context.Context {return t.ctx
}func (t *DefaultTrace) ChildLocalTrace(optName string) Trace {return CreateLocal(t.ctx, optName)
}func (t *DefaultTrace) ChildExitTrace(optName, peer string) (Trace, string, string) {return CreateExit(t.ctx, optName, peer)
}func (t *DefaultTrace) GetTraceId() string {return t.traceId
}func (t *DefaultTrace) IsExit() bool {return t.span != nil && t.span.IsExit()
}func (t *DefaultTrace) IsEntry() bool {return t.span != nil && t.span.IsEntry()
}func (t *DefaultTrace) IsNon() bool {return false
}func (t *DefaultTrace) Error(time time.Time, s ...string) {if t.span != nil {t.span.Error(time, s...)}
}func (t *DefaultTrace) Log(time time.Time, s ...string) {if t.span != nil {t.span.Log(time, s...)}
}
测试代码
package mainimport ("context""demo/farmer/trace/sky""fmt""time"
)func main() {sky.NewGlobalTracer("10.202.244.31:11800", "a-chat-server")entryA := sky.CreateInnerEntry(context.Background(), "test")tid := entryA.GetTraceId()fmt.Println("entryA-traceId:", tid)exit, sw8, _ := sky.CreateExit(entryA.Context(), "call goroutine", "nope")go func(sw8 string) {entryB := sky.CreateEntry(context.Background(), "goroutine", sw8, "")tid := entryB.GetTraceId()fmt.Println("entryB-traceId:", tid)entryB.End()}(sw8)exit.End()entryA.End()time.Sleep(5 * time.Second)
}
jaeger ui展示
提示:
- 父子span的end顺序要有保证,如果父span先end,那么父子span会变成同级的关系,因为它们的segment_id不再相同。
- 跨协程要像跨进程一样处理链路追踪