- Created by 郭强, last modified on Feb 09, 2022
单进程示例
单进程的链路跟踪即进程内方法之间的调用链关系。这种场景的跟踪没有涉及到分布式跟踪,比较简单,以该示例作为我们入门的一个例子吧。示例代码地址:https://github.com/gogf/gf/tree/master/example/trace/inprocess
Root Span
root span
即链路中第一个span
对象。在这里的单进程场景中,往往需要手动创建一个。随后在方法内部创建的span
都会作为它的子级span
。
在分布式架构的服务间通信场景中,往往不需要开发者手动创建root span
,而是由客户端/服务端请求的拦截器来自动创建。
创建tracer
,生成root span
:
func main() { var ctx = gctx.New() tp, err := jaeger.Init(ServiceName, JaegerUdpEndpoint) if err != nil { g.Log().Fatal(ctx, err) } defer tp.Shutdown(ctx) ctx, span := gtrace.NewSpan(ctx, "main") defer span.End() // Trace 1. user1 := GetUser(ctx, 1) g.Dump(user1) // Trace 2. user100 := GetUser(ctx, 100) g.Dump(user100) }
上述代码创建了一个root span
,并将该span
通过context
传递给GetUser
方法,以便在GetUser
方法中将追踪链继续延续下去。
方法间Span创建
// GetUser retrieves and returns hard coded user data for demonstration. func GetUser(ctx context.Context, id int) g.Map { ctx, span := gtrace.NewSpan(ctx, "GetUser") defer span.End() m := g.Map{} gutil.MapMerge( m, GetInfo(ctx, id), GetDetail(ctx, id), GetScores(ctx, id), ) return m } // GetInfo retrieves and returns hard coded user info for demonstration. func GetInfo(ctx context.Context, id int) g.Map { ctx, span := gtrace.NewSpan(ctx, "GetInfo") defer span.End() if id == 100 { return g.Map{ "id": 100, "name": "john", "gender": 1, } } return nil } // GetDetail retrieves and returns hard coded user detail for demonstration. func GetDetail(ctx context.Context, id int) g.Map { ctx, span := gtrace.NewSpan(ctx, "GetDetail") defer span.End() if id == 100 { return g.Map{ "site": "https://goframe.org", "email": "john@goframe.org", } } return nil } // GetScores retrieves and returns hard coded user scores for demonstration. func GetScores(ctx context.Context, id int) g.Map { ctx, span := gtrace.NewSpan(ctx, "GetScores") defer span.End() if id == 100 { return g.Map{ "math": 100, "english": 60, "chinese": 50, } } return nil }
该示例代码展示了多层级方法间的链路信息传递,即是把ctx
上下文变量作为第一个方法参数传递即可。在方法内部,我们通过的固定语法来创建/开始一个Span
:
ctx, span := gtrace.NewSpan(ctx, "xxx") defer span.End()
并通过defer
的方式调用span.End
来结束一个Span
,这样可以很好地记录Span
生命周期(开始和结束)信息,这些信息都将会展示到链路跟踪系统中。其中gtrace.NewSpan
方法的第二个参数spanName
我们直接给定方法的名称即可,这样在链路展示中比较有识别性。
效果查看
执行完上面的程序后,终端输出:
打开Jaeger UI
: http://localhost:16686/search,可以看到链路追踪的结果:
点击详情可以查看具体信息,包括span
的调用顺序、调用关系,执行时间轴,以及记录一些Attributes和Events
信息,极大的方便我们定位系统中的异常和发现性能瓶颈。:
其中的tracing-inprocess
是我们tracer
的名称,该名称往往是服务名称,由于我们这里只有一个进程和一个tracer
,因此这里只看得到一个服务名称。其中的main
为我们创建的root span
名称,其他的span
为基于该root span
创建的子级span
。由于我们在程序中调用了两次GetUser
方法,因此这里也展示了两次GetUser
方法的调用。每一次GetUser
调用的内部又分别去调用了GetIndo、GetDetail、GetScores
三个方法,方法间的调用层级关系展示得非常清晰明了,并且每个方法的调用时长都可以看得到。
关于其中每个span
记录的Tags
和Process
信息其实对应了OpenTelemetry
中的Attributes
和Events
信息,这些信息我们放到后续章节去详细介绍。
- No labels
8 Comments
智刚
有与阿里云打通的示例吗?
Baob.wu
我司都是Py,Java使用官方Jaeger SDK,而官方sdk的propagation都是Uber’s original headers.而在gtrace使用了
https://www.w3.org/TR/trace-context/标准.
希望后面支持自定义该标准.导致使用goframe内置又不能别的我司别的语言traceid打通.如果强用官方sdk,tracerid在框架内又使用不了的窘境
郭强
这个Header对你业务影响大吗?如果大的话可以提个PR对外放开配置修改。
Justin
ctx context.Context 每次都需要传给【被调用】的函数吗? 不能在底层自动获取吗? 比如,像java的ThreadLocal 那样。多谢
Justin
在代码中,显示的传递context.Context,对代码入侵太重了。这样的话,只要是需要【链路跟踪】的地方,全部要传递context,
代码示例不是很全面,建议举几个【函数调用】的示例,协程调传递的示例。
郭强
Golang
本身没有ThreadLocal
,你会发现显示传递ctx
是Golang Stype
,这种通用做法,你会习惯的。R.E
Jaeger.init()注册并没有生效,Jaeger里没有记录,程序控制台也没有链路追踪的日志打印,用的是2.1.0-rc3,是什么原因?@郭强
火特
如果Jaeger里没有记录,检查一下JaegerUdpEndpoint是否配置正确可以访问,我按示例配置的时候也出现过类似的问题。
用的wsl2部署的jaegertracing/all-in-one:latest镜像,在Windows11的GoLand中调试时,如果使用JaegerUdpEndpoint = "localhost:6831" 也是获取不到数据,改为JaegerUdpEndpoint = "172.20.2.224:6831"就可以了。
我猜测是由于wsl2没有将udp端口映射到Windows11宿主机上导致(wsl2一般会TCP端口会自动映射到win)。