3.4 实现链路日志记录

基于 Gin 封装的高效 Web 框架新亮2024-1-12 14:31

目前可收集日志类型包括:

  1. 当前的请求日志

  2. 当前的响应日志

  3. 自定义调试日志

  4. MySQL 操作日志

  5. Redis 操作信息

  6. Mongo 操作信息

  7. 请求三方 API 接口的请求与响应日志

日志收集,代码片段:

// ./internal/pkg/core/core.go

// region 记录日志
var t *trace.Trace
if x := context.Trace(); x != nil {
	t = x.(*trace.Trace)
} else {
	return
}

decodedURL, _ := url.QueryUnescape(ctx.Request.URL.RequestURI())

// ctx.Request.Header,精简 Header 参数
traceHeader := map[string]string{
	"Content-Type": ctx.GetHeader("Content-Type"),
}

t.WithRequest(&trace.Request{
	TTL:        "un-limit",
	Method:     ctx.Request.Method,
	DecodedURL: decodedURL,
	Header:     traceHeader,
	Body:       string(context.RawData()),
})

var responseBody interface{}

if response != nil {
	responseBody = response
}

t.WithResponse(&trace.Response{
	Header:          ctx.Writer.Header(),
	HttpCode:        ctx.Writer.Status(),
	HttpCodeMsg:     http.StatusText(ctx.Writer.Status()),
	BusinessCode:    businessCode,
	BusinessCodeMsg: businessCodeMsg,
	Body:            responseBody,
	CostSeconds:     time.Since(ts).Seconds(),
})

t.Success = !ctx.IsAborted() && (ctx.Writer.Status() == http.StatusOK)
t.CostSeconds = time.Since(ts).Seconds()

logger.Info("trace-log",
	zap.Any("method", ctx.Request.Method),
	zap.Any("path", decodedURL),
	zap.Any("http_code", ctx.Writer.Status()),
	zap.Any("business_code", businessCode),
	zap.Any("success", t.Success),
	zap.Any("cost_seconds", t.CostSeconds),
	zap.Any("trace_id", t.Identifier),
	zap.Any("trace_info", t),
	zap.Error(abortErr),
)
// endregion

使用日志组件,代码片段:

// main.go

import "gin-api-mono/internal/pkg/logger"

// 将日志输出到文件
accessLogger, err := logger.NewJSONLogger(
	logger.WithField("domain", fmt.Sprintf("%s[%s]", configs.ProjectName, env.Active().Value())),
	logger.WithTimeLayout(timeutil.CSTLayout),
	logger.WithFileP(configs.ProjectAccessLogFile),
)

// 将日志输出到文件 + 控制台
accessLogger, err := logger.NewJSONLogger(
	logger.WithOutputInConsole(),
	logger.WithField("domain", fmt.Sprintf("%s[%s]", configs.ProjectName, env.Active().Value())),
	logger.WithTimeLayout(timeutil.CSTLayout),
	logger.WithFileP(configs.ProjectAccessLogFile),
)

// 如果需要日志分割,可以使用 WithFileRotationP() 方法。

记录自定义调试日志,代码片段:

import "gin-api-mono/internal/pkg/debug"

// 示例一,记录多个数据
debug.WithContext(ctx.RequestContext()).Logger("这是调试信息A1", "这是调试信息A2")

// 示例二,记录单个数据
debug.WithContext(ctx.RequestContext()).Logger("这是调试信息B")

记录 MySQL 操作日志,代码片段:

// 查询数据,核心是使用 .WithContext() 方法
h.db.GetDbR().WithContext(ctx.RequestContext()).Where(queryWhere).Find(&resultData)

记录 Redis 操作日志,代码片段:

import "gin-api-mono/internal/repository/redis"

// get,核心是使用 ctx.RequestContext() 参数
getResult, err := redis.GetRedisClient().Get(ctx.RequestContext(), "name").Result()

记录 Mongo 操作日志,代码片段:

import "gin-api-mono/internal/repository/mongo"

// 获取 Mongo Client
client := mongo.GetMongoClient()

// 获取 Collection,例如 Database 为 gin_api_mono,Collection 为 user
collection := client.Database("gin_api_mono").Collection("user")

// 查询数据,核心是使用 ctx.RequestContext() 参数
findResult, err := collection.Find(ctx.RequestContext(), bson.D{})

记录请求三方 API 接口的日志,代码片段:

import "gin-api-mono/internal/pkg/httpclient"

// 支持在上下文记录执行日志的 httpclient
client := httpclient.GetHttpClientWithContext(ctx.RequestContext())

// 普通的 httpclient
client := httpclient.GetHttpClient()

// GET
resp, err := client.R().
      SetQueryParams(map[string]string{
          "page_no": "1",
          "limit": "20",
          "sort":"name",
          "order": "asc",
          "random":strconv.FormatInt(time.Now().Unix(), 10),
      }).
      SetHeader("Accept", "application/json").
      SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F").
      Get("/search_result")

日志结构,代码片段:

// ./internal/pkg/trace/trace.go

// Trace 记录的参数
type Trace struct {
	mux                sync.Mutex
	Identifier         string     `json:"trace_id"`             // 链路ID
	Request            *Request   `json:"request"`              // 请求信息
	Response           *Response  `json:"response"`             // 返回信息
	ThirdPartyRequests []*HttpLog `json:"third_party_requests"` // 调用第三方接口的信息
	Debugs             []*Debug   `json:"debugs"`               // 调试信息
	SQLs               []*SQL     `json:"sqls"`                 // 执行的 SQL 信息
	Redis              []*Redis   `json:"redis"`                // 执行的 Redis 信息
	Mongos             []*Mongo   `json:"mongos"`               // 执行的 Mongo 信息
	Success            bool       `json:"success"`              // 请求结果 true or false
	CostSeconds        float64    `json:"cost_seconds"`         // 执行时长(单位秒)
}

不同组件能够将日志记录到上下文的核心是 拦截器,具体在第三方组件集成章节详细描述,此模块有些复杂,也可以微信找我沟通。


有启发,左下角点击“启发”告诉我呀,点我即可直接跳转到小册目录合集