- Created by 郭强, last modified on Oct 08, 2022
一、基本介绍
在链路跟踪中,TraceID
作为在各个服务间传递的唯一标识,用于串联服务间请求关联关系,是非常重要的一项数据。TraceID
是通过Context
参数传递的,如果使用框架的glog
日志组件,那么在日志打印中将会自动读取TraceID
记录到日志内容中。因此也建议大家使用框架的glog
日志组件来打印日志,便于完美地支持链路跟踪特性。
二、TraceID的注入
1、客户端
如果使用GoFrame
框架的Client
,那么所有的请求将会自带TraceID
的注入。GoFrame
的TraceID
使用的是OpenTelemetry
规范,是由十六进制字符组成的的32
字节字符串。
强烈建议大家统一使用gclient
组件,不仅功能全面而且自带链路跟踪能力。
2、服务端
如果使用GoFrame
框架的Server
,如果请求带有TraceID
,那么将会自动承接到Context
中;否则,将会自动注入标准的TraceID
,并传递给后续逻辑。
三、TraceID的获取
1、客户端
如果使用GoFrame
框架的Client
,这里有三种方式。
1)自动生成TraceID(推荐)
通过gctx.New/WithCtx
方法创建一个带有TraceID
的Context
,该Context
参数用于传递给请求方法。随后可以通过gctx.CtxId
方法获取整个链路的TraceID
。相关方法:
// New creates and returns a context which contains context id. func New() context.Context // WithCtx creates and returns a context containing context id upon given parent context `ctx`. func WithCtx(ctx context.Context) context.Context
使用WithCtx
方法时,如果给定的ctx
参数本身已经带有TraceID
,那么它将会直接使用该TraceID
,并不会新建。
2)客户端自定义TraceID
这里还有个高级的用法,客户端可以自定义TraceID
,使用gtrace.WithTraceID
方法。方法定义如下:
// WithTraceID injects custom trace id into context to propagate. func WithTraceID(ctx context.Context, traceID string) (context.Context, error)
3)读取Response Header
如果是请求 GoFrame
框架的 Server
,那么在返回请求的 Header
中将会增加 Trace-Id
字段,供客户端读取。
2、服务端
如果使用GoFrame
框架的Server
,直接通过gctx.CtxId
方法即可获取TraceID
。相关方法:
// CtxId retrieves and returns the context id from context. func CtxId(ctx context.Context) string
四、使用示例
1、HTTP Response Header TraceID
package main import ( "context" "time" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/os/gctx" ) func main() { s := g.Server() s.BindHandler("/", func(r *ghttp.Request) { traceID := gctx.CtxId(r.Context()) g.Log().Info(r.Context(), "handler") r.Response.Write(traceID) }) s.SetPort(8199) go s.Start() time.Sleep(time.Second) req, err := g.Client().Get(context.Background(), "http://127.0.0.1:8199/") if err != nil { panic(err) } defer req.Close() req.RawDump() }
执行后,终端输出:
ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE ----------|--------|-------|-----------------------------------------------------------------|-------------------- :8199 | ALL | / | main.main.func1 | ----------|--------|-------|-----------------------------------------------------------------|-------------------- :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE ----------|--------|-------|-----------------------------------------------------------------|-------------------- 2022-06-06 21:14:37.245 [INFO] pid[55899]: http server started listening on [:8199] 2022-06-06 21:14:38.247 [INFO] {908d2027560af616e218e912d2ac3972} handler +---------------------------------------------+ | REQUEST | +---------------------------------------------+ GET / HTTP/1.1 Host: 127.0.0.1:8199 User-Agent: GClient v2.1.0-rc4 at TXQIANGGUO-MB0 Traceparent: 00-908d2027560af616e218e912d2ac3972-1e291041b9afa718-01 Accept-Encoding: gzip +---------------------------------------------+ | RESPONSE | +---------------------------------------------+ HTTP/1.1 200 OK Connection: close Content-Length: 32 Content-Type: text/plain; charset=utf-8 Date: Mon, 06 Jun 2022 13:14:38 GMT Server: GoFrame HTTP Server Trace-Id: 908d2027560af616e218e912d2ac3972 908d2027560af616e218e912d2ac3972
2、客户端注入TraceID
package main import ( "time" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/os/gctx" ) func main() { s := g.Server() s.BindHandler("/", func(r *ghttp.Request) { traceID := gctx.CtxId(r.Context()) g.Log().Info(r.Context(), "handler") r.Response.Write(traceID) }) s.SetPort(8199) go s.Start() time.Sleep(time.Second) ctx := gctx.New() g.Log().Info(ctx, "request starts") content := g.Client().GetContent(ctx, "http://127.0.0.1:8199/") g.Log().Infof(ctx, "response: %s", content) }
执行后,终端输出:
2022-06-06 21:17:17.447 [INFO] pid[56070]: http server started listening on [:8199] ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE ----------|--------|-------|-----------------------------------------------------------------|-------------------- :8199 | ALL | / | main.main.func1 | ----------|--------|-------|-----------------------------------------------------------------|-------------------- :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE ----------|--------|-------|-----------------------------------------------------------------|-------------------- 2022-06-06 21:17:18.450 [INFO] {e843f0737b0af616d8ed185d46ba65c5} request starts 2022-06-06 21:17:18.451 [INFO] {e843f0737b0af616d8ed185d46ba65c5} handler 2022-06-06 21:17:18.451 [INFO] {e843f0737b0af616d8ed185d46ba65c5} response: e843f0737b0af616d8ed185d46ba65c5
3、客户端自定义TraceID
package main import ( "context" "time" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/net/gtrace" "github.com/gogf/gf/v2/os/gctx" "github.com/gogf/gf/v2/text/gstr" ) func main() { s := g.Server() s.BindHandler("/", func(r *ghttp.Request) { traceID := gctx.CtxId(r.Context()) g.Log().Info(r.Context(), "handler") r.Response.Write(traceID) }) s.SetPort(8199) go s.Start() time.Sleep(time.Second) ctx, _ := gtrace.WithTraceID(context.Background(), gstr.Repeat("a", 32)) g.Log().Info(ctx, "request starts") content := g.Client().GetContent(ctx, "http://127.0.0.1:8199/") g.Log().Infof(ctx, "response: %s", content) }
执行后,终端输出:
ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE ----------|--------|-------|-----------------------------------------------------------------|-------------------- :8199 | ALL | / | main.main.func1 | ----------|--------|-------|-----------------------------------------------------------------|-------------------- :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE ----------|--------|-------|-----------------------------------------------------------------|-------------------- 2022-06-06 21:40:03.897 [INFO] pid[58435]: http server started listening on [:8199] 2022-06-06 21:40:04.900 [INFO] {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} request starts 2022-06-06 21:40:04.901 [INFO] {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} handler 2022-06-06 21:40:04.901 [INFO] {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} response: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
五、常见问题
1、如果没有使用GoFrame
框架的Client/Server
,如何获取链路的TraceID
?
可以参考GoFrame
框架的Client/Server
的链路跟踪实现,并自行实现一遍,这块可能需要一定成本。
如果使用的第三方Client/Server
组件,请参考第三方组件相关介绍。
2、企业内部服务没有使用标准的OpenTelemetry
规范,但是每个请求都带RequestID
参数,形如 33612a70-990a-11ea-87fe-fd68517e7a2d
,如何和TraceID
结合起来?
根据我的分析,你这个RequestID
的格式和TraceID
规范非常切合,使用的是UUID
字符串,而UUID
可直接转换为TraceID
。建议在自己的Server
内部第一层中间件中将RequestID
转换为TraceID
,通过自定义TraceID
的方式注入到Context
中,并将该Context
传递给后续业务逻辑。
我来给你写个例子吧:
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/net/gtrace" "github.com/gogf/gf/v2/os/gctx" ) func main() { // 内部服务 internalServer := g.Server("internalServer") internalServer.BindHandler("/", func(r *ghttp.Request) { traceID := gctx.CtxId(r.Context()) g.Log().Info(r.Context(), "internalServer handler") r.Response.Write(traceID) }) internalServer.SetPort(8199) go internalServer.Start() // 外部服务,访问以测试 // http://127.0.0.1:8299/?RequestID=33612a70-990a-11ea-87fe-fd68517e7a2d userSideServer := g.Server("userSideServer") userSideServer.Use(func(r *ghttp.Request) { requestID := r.Get("RequestID").String() if requestID != "" { newCtx, err := gtrace.WithUUID(r.Context(), requestID) if err != nil { panic(err) } r.SetCtx(newCtx) } r.Middleware.Next() }) userSideServer.BindHandler("/", func(r *ghttp.Request) { ctx := r.Context() g.Log().Info(ctx, "request internalServer starts") content := g.Client().GetContent(ctx, "http://127.0.0.1:8199/") g.Log().Infof(ctx, "internalServer response: %s", content) r.Response.Write(content) }) userSideServer.SetPort(8299) userSideServer.Run() }
为了演示服务间的链路跟踪能力,这个示例代码中运行了两个HTTP服务,一个内部服务,提供功能逻辑;一个外部服务,供外部的接口调用,它的功能是调用内部服务来实现的。执行后,我们访问:http://127.0.0.1:8299/?RequestID=33612a70-990a-11ea-87fe-fd68517e7a2d
随后查看终端输出:
2022-06-07 14:51:21.957 [INFO] openapi specification is disabled 2022-06-07 14:51:21.958 [INTE] ghttp_server.go:78 78198: graceful reload feature is disabled SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE -----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- internalServer | default | :8199 | ALL | / | main.main.func1 | -----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- internalServer | default | :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE -----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- 2022-06-07 14:51:21.959 [INFO] pid[78198]: http server started listening on [:8199] 2022-06-07 14:51:21.965 [INFO] openapi specification is disabled SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE -----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- userSideServer | default | :8299 | ALL | / | main.main.func3 | -----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- userSideServer | default | :8299 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE -----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- userSideServer | default | :8299 | ALL | /* | main.main.func2 | GLOBAL MIDDLEWARE -----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- 2022-06-07 14:51:21.965 [INFO] pid[78198]: http server started listening on [:8299] 2022-06-07 14:53:05.322 [INFO] {33612a70990a11ea87fefd68517e7a2d} request internalServer starts 2022-06-07 14:53:05.323 [INFO] {33612a70990a11ea87fefd68517e7a2d} internalServer handler 2022-06-07 14:53:05.323 [INFO] {33612a70990a11ea87fefd68517e7a2d} internalServer response: 33612a70990a11ea87fefd68517e7a2d
我们发现,RequestID
作为TraceID
贯通了整个服务间的链路了呢!
- No labels
2 Comments
zs
郭强 大佬好,
shizhu
郭强 jaeger显示的不是具体的方法或路由,看了下CTX那里会默认添加一个SPAN,请问有什么办法可以不用初始化的这个SPAN吗?
还有下面这个路由的替换问题!