项目中链路追踪
目录
背景
为了测试环境调试方便和线上环境快速定位问题,客户端请求接口我们要给他们返回一个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)