一、基本介绍
在链路跟踪中,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
贯通了整个服务间的链路了呢!