一、基本介绍

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

二、TraceID的注入

1、客户端

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

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

2、服务端

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

三、TraceID的获取

1、客户端

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

1)自动生成TraceID(推荐)

通过gctx.New/WithCtx方法创建一个带有TraceIDContext,该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贯通了整个服务间的链路了呢!










Content Menu

  • No labels

2 Comments

  1. zs

    郭强 大佬好,



    type
    TraceID [16]byte 是16的byte,能否改成string,这样可以接入其他长度的traceid或者requestid 这样对于后面的日志平台分析 追踪更容易些
  2. 郭强 jaeger显示的不是具体的方法或路由,看了下CTX那里会默认添加一个SPAN,请问有什么办法可以不用初始化的这个SPAN吗?


    还有下面这个路由的替换问题!