3.4 实现链路日志记录

日志记录 调试日志 数据库日志 API 日志 日志收集 日志结构 拦截器
本文介绍了如何实现链路日志记录,详细列举了可收集的日志类型,包括请求日志、响应日志、自定义调试日志、MySQL、Redis、Mongo操作日志以及三方API接口的请求与响应日志。通过代码片段展示了如何在不同场景下记录日志,如请求和响应的日志记录、自定义调试日志、数据库操作日志等。日志结构通过`Trace`类型进行统一管理,包含链路ID、请求信息、响应信息、调试信息、数据库操作信息等。日志记录的核心是通过拦截器将日志信息绑定到上下文中,确保日志的完整性和可追溯性。
文章内容
思维导图
常见问题
社交分享

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

  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"`         // 执行时长(单位秒)
}

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


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

思维导图生成中,请稍候...

问题 1: 目前可以收集哪些类型的日志?
回答: 可以收集的日志类型包括:当前的请求日志、当前的响应日志、自定义调试日志、MySQL 操作日志、Redis 操作信息、Mongo 操作信息以及请求三方 API 接口的请求与响应日志。

问题 2: 如何记录请求和响应日志?
回答: 通过 trace.Trace 结构体记录请求和响应日志,使用 WithRequestWithResponse 方法分别记录请求信息和响应信息,包括请求方法、URL、Header、Body 以及响应状态码、业务码、执行时长等。

问题 3: 如何将日志输出到文件或控制台?
回答: 使用 logger.NewJSONLogger 方法,通过 WithFileP 参数将日志输出到文件,或通过 WithOutputInConsole 参数同时输出到文件和控制台。还可以使用 WithFileRotationP 方法实现日志分割。

问题 4: 如何记录自定义调试日志?
回答: 使用 debug.WithContext 方法,结合 Logger 方法记录自定义调试日志,支持同时记录多个或单个调试信息。

问题 5: 如何记录 MySQL、Redis 和 Mongo 操作日志?
回答: 对于 MySQL,使用 .WithContext() 方法在查询时记录日志;对于 Redis,使用 ctx.RequestContext() 参数在操作时记录日志;对于 Mongo,使用 ctx.RequestContext() 参数在查询时记录日志。

问题 6: 如何记录请求三方 API 接口的日志?
回答: 使用 httpclient.GetHttpClientWithContext 方法获取支持上下文日志记录的 HTTP 客户端,然后通过该客户端发起请求,日志会自动记录。

问题 7: 日志结构中的 Trace 包含哪些信息?
回答: Trace 结构体包含链路 ID、请求信息、响应信息、第三方接口调用信息、调试信息、SQL 信息、Redis 信息、Mongo 信息、请求结果以及执行时长等信息。

问题 8: 不同组件如何将日志记录到上下文中?
回答: 不同组件通过拦截器将日志记录到上下文中,具体实现可以参考第三方组件集成章节,或通过微信沟通获取更多细节。