一、基本介绍

在链路跟踪中, TraceID 作为在各个服务间传递的唯一标识,用于串联服务间请求关联关系,是非常重要的一项数据。 TraceID 是通过 Context 参数传递的,如果使用框架的 glog 日志组件,那么在日志打印中将会自动读取 TraceID 记录到日志内容中。因此也建议大家使用框架的 glog 日志组件来打印日志,便于完美地支持链路跟踪特性。

二、TraceID的注入

1、客户端

如果使用 GoFrame 框架的 Client,那么所有的请求将会自带 TraceID 的注入。 GoFrameTraceID 使用的是 OpenTelemetry 规范,是由十六进制字符组成的的 32 字节字符串。

链路跟踪-TraceID注入和获取 - 图1提示

强烈建议大家统一使用 gclient 组件,不仅功能全面而且自带链路跟踪能力。

2、服务端

如果使用 GoFrame 框架的 Server,如果请求带有 TraceID,那么将会自动承接到 Context 中;否则,将会自动注入标准的 TraceID,并传递给后续逻辑。

三、TraceID的获取

1、客户端

如果使用 GoFrame 框架的 Client,这里有三种方式。

1)自动生成TraceID(推荐)

通过 gctx.New/WithCtx 方法创建一个带有 TraceIDContext,该 Context 参数用于传递给请求方法。随后可以通过 gctx.CtxId 方法获取整个链路的 TraceID。相关方法:

  1. // New creates and returns a context which contains context id.
  2. func New() context.Context
  3. // WithCtx creates and returns a context containing context id upon given parent context `ctx`.
  4. func WithCtx(ctx context.Context) context.Context

使用 WithCtx 方法时,如果给定的 ctx 参数本身已经带有 TraceID,那么它将会直接使用该 TraceID,并不会新建。

2)客户端自定义TraceID

这里还有个高级的用法,客户端可以自定义 TraceID,使用 gtrace.WithTraceID 方法。方法定义如下:

  1. // WithTraceID injects custom trace id into context to propagate.
  2. func WithTraceID(ctx context.Context, traceID string) (context.Context, error)

3)读取Response Header

如果是请求 GoFrame 框架的 Server,那么在返回请求的 Header 中将会增加 Trace-Id 字段,供客户端读取。

2、服务端

如果使用 GoFrame 框架的 Server,直接通过 gctx.CtxId 方法即可获取 TraceID。相关方法:

  1. // CtxId retrieves and returns the context id from context.
  2. func CtxId(ctx context.Context) string

四、使用示例

1、HTTP Response Header TraceID

  1. package main
  2. import (
  3. "context"
  4. "time"
  5. "github.com/gogf/gf/v2/frame/g"
  6. "github.com/gogf/gf/v2/net/ghttp"
  7. "github.com/gogf/gf/v2/os/gctx"
  8. )
  9. func main() {
  10. s := g.Server()
  11. s.BindHandler("/", func(r *ghttp.Request) {
  12. traceID := gctx.CtxId(r.Context())
  13. g.Log().Info(r.Context(), "handler")
  14. r.Response.Write(traceID)
  15. })
  16. s.SetPort(8199)
  17. go s.Start()
  18. time.Sleep(time.Second)
  19. req, err := g.Client().Get(context.Background(), "http://127.0.0.1:8199/")
  20. if err != nil {
  21. panic(err)
  22. }
  23. defer req.Close()
  24. req.RawDump()
  25. }

执行后,终端输出:

  1. ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
  2. ----------|--------|-------|-----------------------------------------------------------------|--------------------
  3. :8199 | ALL | / | main.main.func1 |
  4. ----------|--------|-------|-----------------------------------------------------------------|--------------------
  5. :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE
  6. ----------|--------|-------|-----------------------------------------------------------------|--------------------
  7. 2022-06-06 21:14:37.245 [INFO] pid[55899]: http server started listening on [:8199]
  8. 2022-06-06 21:14:38.247 [INFO] {908d2027560af616e218e912d2ac3972} handler
  9. +---------------------------------------------+
  10. | REQUEST |
  11. +---------------------------------------------+
  12. GET / HTTP/1.1
  13. Host: 127.0.0.1:8199
  14. User-Agent: GClient v2.1.0-rc4 at TXQIANGGUO-MB0
  15. Traceparent: 00-908d2027560af616e218e912d2ac3972-1e291041b9afa718-01
  16. Accept-Encoding: gzip
  17. +---------------------------------------------+
  18. | RESPONSE |
  19. +---------------------------------------------+
  20. HTTP/1.1 200 OK
  21. Connection: close
  22. Content-Length: 32
  23. Content-Type: text/plain; charset=utf-8
  24. Date: Mon, 06 Jun 2022 13:14:38 GMT
  25. Server: GoFrame HTTP Server
  26. Trace-Id: 908d2027560af616e218e912d2ac3972
  27. 908d2027560af616e218e912d2ac3972

2、客户端注入 TraceID

  1. package main
  2. import (
  3. "time"
  4. "github.com/gogf/gf/v2/frame/g"
  5. "github.com/gogf/gf/v2/net/ghttp"
  6. "github.com/gogf/gf/v2/os/gctx"
  7. )
  8. func main() {
  9. s := g.Server()
  10. s.BindHandler("/", func(r *ghttp.Request) {
  11. traceID := gctx.CtxId(r.Context())
  12. g.Log().Info(r.Context(), "handler")
  13. r.Response.Write(traceID)
  14. })
  15. s.SetPort(8199)
  16. go s.Start()
  17. time.Sleep(time.Second)
  18. ctx := gctx.New()
  19. g.Log().Info(ctx, "request starts")
  20. content := g.Client().GetContent(ctx, "http://127.0.0.1:8199/")
  21. g.Log().Infof(ctx, "response: %s", content)
  22. }

执行后,终端输出:

  1. 2022-06-06 21:17:17.447 [INFO] pid[56070]: http server started listening on [:8199]
  2. ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
  3. ----------|--------|-------|-----------------------------------------------------------------|--------------------
  4. :8199 | ALL | / | main.main.func1 |
  5. ----------|--------|-------|-----------------------------------------------------------------|--------------------
  6. :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE
  7. ----------|--------|-------|-----------------------------------------------------------------|--------------------
  8. 2022-06-06 21:17:18.450 [INFO] {e843f0737b0af616d8ed185d46ba65c5} request starts
  9. 2022-06-06 21:17:18.451 [INFO] {e843f0737b0af616d8ed185d46ba65c5} handler
  10. 2022-06-06 21:17:18.451 [INFO] {e843f0737b0af616d8ed185d46ba65c5} response: e843f0737b0af616d8ed185d46ba65c5

3、客户端自定义 TraceID

  1. package main
  2. import (
  3. "context"
  4. "time"
  5. "github.com/gogf/gf/v2/frame/g"
  6. "github.com/gogf/gf/v2/net/ghttp"
  7. "github.com/gogf/gf/v2/net/gtrace"
  8. "github.com/gogf/gf/v2/os/gctx"
  9. "github.com/gogf/gf/v2/text/gstr"
  10. )
  11. func main() {
  12. s := g.Server()
  13. s.BindHandler("/", func(r *ghttp.Request) {
  14. traceID := gctx.CtxId(r.Context())
  15. g.Log().Info(r.Context(), "handler")
  16. r.Response.Write(traceID)
  17. })
  18. s.SetPort(8199)
  19. go s.Start()
  20. time.Sleep(time.Second)
  21. ctx, _ := gtrace.WithTraceID(context.Background(), gstr.Repeat("a", 32))
  22. g.Log().Info(ctx, "request starts")
  23. content := g.Client().GetContent(ctx, "http://127.0.0.1:8199/")
  24. g.Log().Infof(ctx, "response: %s", content)
  25. }

执行后,终端输出:

  1. ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
  2. ----------|--------|-------|-----------------------------------------------------------------|--------------------
  3. :8199 | ALL | / | main.main.func1 |
  4. ----------|--------|-------|-----------------------------------------------------------------|--------------------
  5. :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE
  6. ----------|--------|-------|-----------------------------------------------------------------|--------------------
  7. 2022-06-06 21:40:03.897 [INFO] pid[58435]: http server started listening on [:8199]
  8. 2022-06-06 21:40:04.900 [INFO] {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} request starts
  9. 2022-06-06 21:40:04.901 [INFO] {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} handler
  10. 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 传递给后续业务逻辑。

我来给你写个例子吧:

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/frame/g"
  4. "github.com/gogf/gf/v2/net/ghttp"
  5. "github.com/gogf/gf/v2/net/gtrace"
  6. "github.com/gogf/gf/v2/os/gctx"
  7. )
  8. func main() {
  9. // 内部服务
  10. internalServer := g.Server("internalServer")
  11. internalServer.BindHandler("/", func(r *ghttp.Request) {
  12. traceID := gctx.CtxId(r.Context())
  13. g.Log().Info(r.Context(), "internalServer handler")
  14. r.Response.Write(traceID)
  15. })
  16. internalServer.SetPort(8199)
  17. go internalServer.Start()
  18. // 外部服务,访问以测试
  19. // http://127.0.0.1:8299/?RequestID=33612a70-990a-11ea-87fe-fd68517e7a2d
  20. userSideServer := g.Server("userSideServer")
  21. userSideServer.Use(func(r *ghttp.Request) {
  22. requestID := r.Get("RequestID").String()
  23. if requestID != "" {
  24. newCtx, err := gtrace.WithUUID(r.Context(), requestID)
  25. if err != nil {
  26. panic(err)
  27. }
  28. r.SetCtx(newCtx)
  29. }
  30. r.Middleware.Next()
  31. })
  32. userSideServer.BindHandler("/", func(r *ghttp.Request) {
  33. ctx := r.Context()
  34. g.Log().Info(ctx, "request internalServer starts")
  35. content := g.Client().GetContent(ctx, "http://127.0.0.1:8199/")
  36. g.Log().Infof(ctx, "internalServer response: %s", content)
  37. r.Response.Write(content)
  38. })
  39. userSideServer.SetPort(8299)
  40. userSideServer.Run()
  41. }

为了演示服务间的链路跟踪能力,这个示例代码中运行了两个HTTP服务,一个内部服务,提供功能逻辑;一个外部服务,供外部的接口调用,它的功能是调用内部服务来实现的。执行后,我们访问: http://127.0.0.1:8299/?RequestID=33612a70-990a-11ea-87fe-fd68517e7a2d

随后查看终端输出:

  1. 2022-06-07 14:51:21.957 [INFO] openapi specification is disabled
  2. 2022-06-07 14:51:21.958 [INTE] ghttp_server.go:78 78198: graceful reload feature is disabled
  3. SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
  4. -----------------|---------|---------|--------|-------|-----------------------------------------------------------------|--------------------
  5. internalServer | default | :8199 | ALL | / | main.main.func1 |
  6. -----------------|---------|---------|--------|-------|-----------------------------------------------------------------|--------------------
  7. internalServer | default | :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE
  8. -----------------|---------|---------|--------|-------|-----------------------------------------------------------------|--------------------
  9. 2022-06-07 14:51:21.959 [INFO] pid[78198]: http server started listening on [:8199]
  10. 2022-06-07 14:51:21.965 [INFO] openapi specification is disabled
  11. SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
  12. -----------------|---------|---------|--------|-------|-----------------------------------------------------------------|--------------------
  13. userSideServer | default | :8299 | ALL | / | main.main.func3 |
  14. -----------------|---------|---------|--------|-------|-----------------------------------------------------------------|--------------------
  15. userSideServer | default | :8299 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE
  16. -----------------|---------|---------|--------|-------|-----------------------------------------------------------------|--------------------
  17. userSideServer | default | :8299 | ALL | /* | main.main.func2 | GLOBAL MIDDLEWARE
  18. -----------------|---------|---------|--------|-------|-----------------------------------------------------------------|--------------------
  19. 2022-06-07 14:51:21.965 [INFO] pid[78198]: http server started listening on [:8299]
  20. 2022-06-07 14:53:05.322 [INFO] {33612a70990a11ea87fefd68517e7a2d} request internalServer starts
  21. 2022-06-07 14:53:05.323 [INFO] {33612a70990a11ea87fefd68517e7a2d} internalServer handler
  22. 2022-06-07 14:53:05.323 [INFO] {33612a70990a11ea87fefd68517e7a2d} internalServer response: 33612a70990a11ea87fefd68517e7a2d

我们发现, RequestID 作为 TraceID 贯通了整个服务间的链路了呢!