初始化go2sky
package sky
import (
"encoding/json"
"github.com/SkyAPM/go2sky"
"github.com/SkyAPM/go2sky/reporter"
"log"
"sync"
)
var once sync.Once
var globalTracer *go2sky.Tracer
func 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 report
nr := &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.Reporter
debug 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 sky
import (
"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() // 结束当前的 Trace
Tag(key, value string) // 设置tag,在ui上可以进行过滤
SetComponent(int32) // 设置组件ID,skywalking oap-server的component-libraries.yml文件规范了中间件组件的ID,如dubbo=3
SetSpanLayer(v3.SpanLayer) // 设置span的类型,例如 v3.SpanLayer_RPCFramework
ChildLocalTrace(optName string) Trace // 创建local类型的子Trace
ChildExitTrace(optName, peer string) (Trace, string, string) // 创建exit类型的子Trace,peer是
GetTraceId() string // 获取当前Trace的TraceId
Error(time.Time, ...string) // 记录错误信息,time.Time是错误发生的时间,...string是错误信息
Log(time.Time, ...string) // 记录错误信息,time.Time是错误发生的时间,...string是错误信息
IsExit() bool // 判断是否Exit
IsEntry() bool // 判断是否Entry
IsNon() 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 sky
import (
"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 sky
import (
"context"
"github.com/SkyAPM/go2sky"
v3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
"sync"
"time"
)
type DefaultTrace struct {
ctx context.Context
span go2sky.Span
once sync.Once
traceId 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 main
import (
"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不再相同。
- 跨协程要像跨进程一样处理链路追踪