Gin 框架:實現分散式日誌追蹤
theme: github highlight: a11y-dark
這是我參與11月更文挑戰的第11天,活動詳情檢視:2021最後一次更文挑戰
介紹
通過一個完整例子,在基於 Gin 框架中實現分散式日誌追蹤。
什麼是 API 日誌追蹤?
一個 API 請求會跨多個微服務,我們希望通過一個唯一的 ID 檢索到整個鏈路的日誌。
我們將會使用 rk-boot 來啟動 Gin 框架的微服務。
請訪問如下地址獲取完整教程:
- http://rkdocs.netlify.app/cn
安裝
go
go get github.com/rookie-ninja/rk-boot
go get github.com/rookie-ninja/rk-gin
快速開始
我們會建立 /v1/greeter API 進行驗證,同時開啟 logging, meta 和 tracing 中介軟體以達到目的。
1. 建立 bootA.yaml & serverA.go
ServerA 監聽 1949 埠,並且傳送請求給 ServerB。
我們通過 rkginctx.InjectSpanToNewContext() 方法把 Tracing 資訊注入到 Context 中,傳送給 ServerB。
```yaml
gin: - name: greeter # Required port: 1949 # Required enabled: true # Required interceptors: loggingZap: enabled: true # Optional, enable logging interceptor meta: enabled: true # Optional, enable meta interceptor tracingTelemetry: enabled: true # Optional, enable tracing interceptor ```
```go // Copyright (c) 2021 rookie-ninja // // Use of this source code is governed by an Apache-style // license that can be found in the LICENSE file. package main
import ( "context" "github.com/gin-gonic/gin" "github.com/rookie-ninja/rk-boot" "github.com/rookie-ninja/rk-gin/boot" "github.com/rookie-ninja/rk-gin/interceptor/context" "net/http" )
// Application entrance. func main() { // Create a new boot instance. boot := rkboot.NewBoot(rkboot.WithBootConfigPath("bootA.yaml"))
// Register handler
boot.GetEntry("greeter").(*rkgin.GinEntry).Router.GET("/v1/greeter", GreeterA)
// Bootstrap
boot.Bootstrap(context.Background())
// Wait for shutdown sig
boot.WaitForShutdownSig(context.Background())
}
// GreeterA will add trace info into context and call serverB func GreeterA(ctx *gin.Context) { // Call serverB at 2008 req, _ := http.NewRequest(http.MethodGet, "http://localhost:2008/v1/greeter", nil)
// Inject current trace information into context
rkginctx.InjectSpanToHttpRequest(ctx, req)
// Call server
http.DefaultClient.Do(req)
// Respond to request
ctx.String(http.StatusOK, "Hello from serverA!")
} ```
2. 建立 bootB.yaml & serverB.go
ServerB 監聽 2008 埠。
```yaml
gin: - name: greeter # Required port: 2008 # Required enabled: true # Required interceptors: loggingZap: enabled: true # Optional, enable logging interceptor meta: enabled: true # Optional, enable meta interceptor tracingTelemetry: enabled: true # Optional, enable tracing interceptor ```
```go // Copyright (c) 2021 rookie-ninja // // Use of this source code is governed by an Apache-style // license that can be found in the LICENSE file. package main
import ( "context" "github.com/gin-gonic/gin" "github.com/rookie-ninja/rk-boot" "github.com/rookie-ninja/rk-gin/boot" "net/http" )
// Application entrance. func main() { // Create a new boot instance. boot := rkboot.NewBoot(rkboot.WithBootConfigPath("bootB.yaml"))
// Register handler
boot.GetEntry("greeter").(*rkgin.GinEntry).Router.GET("/v1/greeter", GreeterB)
// Bootstrap
boot.Bootstrap(context.Background())
// Wait for shutdown sig
boot.WaitForShutdownSig(context.Background())
}
// GreeterB receive request from serverA and respond. func GreeterB(ctx *gin.Context) { ctx.String(http.StatusOK, "Hello from serverB!") } ```
3. 資料夾結構
``` . ├── bootA.yaml ├── bootB.yaml ├── go.mod ├── go.sum ├── serverA.go └── serverB.go
0 directories, 6 files ```
4. 啟動 ServerA & ServerB
$ go run serverA.go
$ go run serverB.go
5. 往 ServerA 傳送請求
$ curl localhost:1949/v1/greeter
Hello from serverA!
6. 驗證日誌
兩個服務的日誌中,會有同樣的 traceId,不同的 requestId。
我們可以通過 grep traceId 來追蹤 RPC。
- ServerA
```
endTime=2021-11-18T01:29:56.698997+08:00 ... ids={"eventId":"f5878390-1a5a-4bb9-8b39-bf4261864c0f","requestId":"f5878390-1a5a-4bb9-8b39-bf4261864c0f","traceId":"b2d70ab9f8207ef4a9f0c3fb1be5c22c"} ... operation=/v1/greeter resCode=200 eventStatus=Ended EOE ```
- ServerB
```
endTime=2021-11-18T01:29:56.698606+08:00 ... ids={"eventId":"273c97d2-e11a-46f5-a044-bb9c0cf64540","requestId":"273c97d2-e11a-46f5-a044-bb9c0cf64540","traceId":"b2d70ab9f8207ef4a9f0c3fb1be5c22c"} ... operation=/v1/greeter resCode=200 eventStatus=Ended EOE ```
概念
當我們沒有使用例如 jaeger 呼叫鏈服務的時候,我們希望通過日誌來追蹤分散式系統裡的 RPC 請求。
rk-boot 的中介軟體會通過 openTelemetry 庫來向日志寫入 traceId 來追蹤 RPC。
當啟動了日誌中介軟體,原資料中介軟體,呼叫鏈中介軟體的時候,中介軟體會往日誌裡寫入如下三種 ID。
EventId
當啟動了日誌中介軟體,EventId 會自動生成。
```yaml
gin: - name: greeter # Required port: 1949 # Required enabled: true # Required interceptors: loggingZap: enabled: true ```
```
... ids={"eventId":"cd617f0c-2d93-45e1-bef0-95c89972530d"} ... ```
RequestId
當啟動了日誌中介軟體和原資料中介軟體,RequestId 和 EventId 會自動生成,並且這兩個 ID 會一致。
```yaml
gin: - name: greeter # Required port: 1949 # Required enabled: true # Required interceptors: loggingZap: enabled: true meta: enabled: true ```
```
... ids={"eventId":"8226ba9b-424e-4e19-ba63-d37ca69028b3","requestId":"8226ba9b-424e-4e19-ba63-d37ca69028b3"} ... ```
即使使用者覆蓋了 RequestId,EventId 也會保持一致。
rkginctx.AddHeaderToClient(ctx, rkginctx.RequestIdKey, "overridden-request-id")
```
... ids={"eventId":"overridden-request-id","requestId":"overridden-request-id"} ... ```
TraceId
當啟動了呼叫鏈中介軟體,traceId 會自動生成。
```yaml
gin: - name: greeter # Required port: 1949 # Required enabled: true # Required interceptors: loggingZap: enabled: true meta: enabled: true tracingTelemetry: enabled: true ```
```
... ids={"eventId":"dd19cf9a-c7be-486c-b29d-7af777a78ebe","requestId":"dd19cf9a-c7be-486c-b29d-7af777a78ebe","traceId":"316a7b475ff500a76bfcd6147036951c"} ... ```