目录

项目中链路追踪

背景

为了测试环境调试方便和线上环境快速定位问题,客户端请求接口我们要给他们返回一个traceID来标识本次请求。这样拿着traceId来找我们,我们就能分析日志方便了。我们要在中间件中生成唯一traceId,然后后面的请求处理中要拿到这个ID,接口日志打印啊,sql日期打印啊,上传报错平台啊。

生成traceId

我们一般在日志中间件中生成traceID,接口日志中间件执行比较早的中间件。


func LogRequestMiddleware(c *gin.Context) {
	blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
	c.Writer = blw
	startTime := time.Now().UnixNano()
	requestId := idutil.GetSnowFlakeId()
	c.Set(constconfig.GIN_CONTEXT_REQUEST_ID_KEY, requestId)
	c.Writer.Header().Add("Request-Id", requestId)
	
	c.Next()
	// 用于获取接口大概处理时间
	endTime := time.Now().UnixNano()
	
	// get request headers
	...
		
	// get response headers
	...

	// set response body
	if c.Writer.Status() == http.StatusOK {
		
		fmt.Println(string(blw.body.Bytes()))
		err := json.Unmarshal(blw.body.Bytes(), &response)
		if err != nil {
			logutil.LogError(c, err, "打印请求日志错误,response body 反序列化错误", nil)
			return
		}
		...
	}
	// 拿到请求信息和响应信息,带上traceID打印
	logutil.LogRequest(&httpLog)
}

我们c.Set(constconfig.GIN_CONTEXT_REQUEST_ID_KEY, requestId)来给context中加入了traceId,后面的中间件都可以取到了。

gorm中打印traceId

gorm默认的logger是

var Default = New(log.New(os.Stdout, "\r\n", log.LstdFlags), Config{
	SlowThreshold: 100 * time.Millisecond,
	LogLevel:      Warn,
	Colorful:      true,
})

慢查询是100毫秒,最主要是标准输出。更别想自定义打印我们的traceId了。

type MySqlLogger struct {
	logger.Interface
}

func (m MySqlLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
	sql, rows := fc()
	_, file, line, _ := runtime.Caller(3)
	debug := configutil.GoFrameWorkConfigInfo.App.Debug
	if debug {
		fmt.Printf("\n%s\n%d行受影响\n%s:%d\nerr:%+v\n", sql, rows, file, line, err)
	}
	logutil.LogSql(map[string]interface{}{
		"sql":       sql,
		"rows":      rows,
		"caller":    fmt.Sprintf("%s:%d", file, line),
		"message":   ctx.Value("sqlMsg"),
		"requestId": ctx.Value("requestId"),
		"userId":    ctx.Value("userId"),
		"err":       err,
	})
}

newLogger := logger.New(
		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
		logger.Config{
			SlowThreshold: time.Second, // 慢 SQL 阈值
			LogLevel:      logger.Info, // Log level
			Colorful:      true,        // 禁用彩色打印
		},
	)
mySqlLogger := MySqlLogger{newLogger}
gormDb.Logger = mySqlLogger

我们重写默认的logger的Trace方法,参数中这个context并不是gin.context,是我们要调用gorm的db.WithContext方法传进来的context.context。这个gorm v2版本的新增方法,这样我们就能轻松拿到上下文了。

ctx:=context.WithValue(context.Background(),"requestId",10)
db.WithContext(ctx).Raw("select * from user").Scan(&users)