此前分析日志系统时,有两个中间件没搞明白它的作用,后面再看了下,大概理解了。这里再记录下。
// 添加 requestID
r.Use(RequestId(pkg.TrafficKey))
// 添加 go-admin-logger-request
r.Use(api.SetRequestLogger)
这两个中间件,加了和没加,记录的日志是一样的。当时没搞明白,再看了一下框架的源码,大概理解了:
中间件Use(RequestId(pkg.TrafficKey))
首先呢,RequestId(pkg.TrafficKey)的目的是检测请求头里面是否包含"X-Request-Id",如果不包含,使用uuid生成一个,这个过程好理解,但是为什么不用他,日志也会生成该请求头呢?
发现框架里还有个类似的方法:“github.com\go-admin-team\go-admin-core\sdk@v1.3.10\pkg\utils.go”
// GenerateMsgIDFromContext 生成msgID
func GenerateMsgIDFromContext(c *gin.Context) string {
requestId := c.GetHeader(TrafficKey)
if requestId == "" {
requestId = uuid.New().String()
c.Header(TrafficKey, requestId)
}
return requestId
}
这个方法,乍一看功能也是一样的,其实不同,c.Header(TrafficKey, requestId)是在response的请求头写入,而c.Set(trafficKey, requestId)是给request的请求头写入,这是两回事。
PS: RequestId(pkg.TrafficKey)里面没有将X-Request-Id转化成全小写,所以在我们仅引入该中间件时,日志里的id key是大写的:
if requestId == "" {
requestId = c.GetHeader(strings.ToLower(trafficKey)) // ToLower转化成小写再检查
}
我看http请求头好像都是用小写的,所以最好就转化成小的。
所以:该中间件做了一下事情:
- 检查 X-Request-Id,如果没有,则生成X-Request-Id
- 将 X-Request-Id写入request的请求头中
- 将logger对象写入request的请求头中
中间件Use(api.SetRequestLogger)
首先呢,她会调用下面这个方法,这个方法前面分析了,其实它是给response写入请求头的,而如果我们启用了前一个中间件,那么这个方法是不执行的。意味着没有给response写入X-Request-Id这个请求头,实测也确实是如此。
PS:所以该框架的本意是想给response写还是不写呢?
requestId := pkg.GenerateMsgIDFromContext(c)
然后呢,在RequestId(pkg.TrafficKey)这个中间件里,就有添加该请求头的代码:
c.Set(pkg.LoggerKey,
logger.NewHelper(logger.DefaultLogger).
WithFields(map[string]interface{}{
trafficKey: requestId,
}))
而在本中间件里,也有一样的功能:
log := logger.NewHelper(sdk.Runtime.GetLogger()).WithFields(map[string]interface{}{
strings.ToLower(pkg.TrafficKey): requestId,
})
c.Set(pkg.LoggerKey, log)
我感觉这里内容重复了。。。可以择其一
所以:该中间件做了一下事情:
- 检查X-Request-Id,如果没有,则给response的请求头写入新的X-Request-Id
- 将logger对象写入request的请求头中
中间件Use(LoggerToFile())
而在 r.Use(LoggerToFile())这个中间件里,不包含设置pkg.LoggerKey这个请求头的内容,它里面有一句关联紧密的代码:
log := api.GetRequestLogger(c)
这个函数的源代码:
// GetRequestLogger 获取上下文提供的日志
func GetRequestLogger(c *gin.Context) *logger.Helper {
var log *logger.Helper
l, ok := c.Get(pkg.LoggerKey)
if ok {
ok = false
log, ok = l.(*logger.Helper)
if ok {
return log
}
}
//如果没有在上下文中放入logger
requestId := pkg.GenerateMsgIDFromContext(c)
log = logger.NewHelper(sdk.Runtime.GetLogger()).WithFields(map[string]interface{}{
strings.ToLower(pkg.TrafficKey): requestId,
})
return log
}
首先呢,看这里的上半部分代码,它尝试获取“c.Get(pkg.LoggerKey)”,前面两个中间件写入这个LoggerKey请求头,实际上就是将本次请求的logger对象放到c *gin.Context这个对象中,然后后续的业务都会把这个logger对象一直传递下去,保证记录日志的时候都是使用同一个logger对象,这样日志之间才能关联起来。
而下半部分代码,就是在没有获取到这个请求头的情况下,再自己创建这个log 对象,再返回出去。
但是呢,如果抛掉前面两个中间件的话,这样在c *gin.Context就没有放入logger对象,这样呢,后续的步骤写入日志的时候就关联不到该日志对象,也就无法做到日志关联了。
PS:这个中间件实现了一些前面两个中间件没有做的事,所以这个中间件肯定得要
所以:该中间件做了一下事情:
- 检查X-Request-Id,如果没有,则给response的请求头写入新的X-Request-Id
- 其他事,前面两个中间件没做的事
结论
- 总结下来,第一个中间件可以不用,因为 X-Request-Id被包含在logger对象中,完全不需要再把它放到request的请求头中。第二个中间件可以把第一个中间件的其他事做了。
- 如果需要用别的方式生成 X-Request-Id那么可以使用第一个中间件
PS: 从记录的日志看,我觉得还是有问题没搞明白,因为我启动程序后首次访问的日志是这样的:
2022-02-26 17:21:48.800+0800 file:apis/health.go:40 level:info 默认值
2022-02-26 17:21:48.800+0800 file:middleware/logger.go:90 latencyTime:530.9µs level:info method:GET statusCode:200 uri:/v1/health x-request-id:5a4b2943-5fdb-4a80-a3fb-da8ad8e2aec3
第一条日志是我logger.Info(name)这样记录的,它没有完整的信息,但是当我第二次及之后请求接口,它的日志又是完整的:
2022-02-26 17:23:35.336+0800 file:apis/health.go:40 latencyTime:530.9µs level:info method:GET statusCode:200 uri:/v1/health x-request-id:5a4b2943-5fdb-4a80-a3fb-da8ad8e2aec3 默认值
2022-02-26 17:23:35.336+0800 file:middleware/logger.go:90 latencyTime:518.7µs level:info method:GET statusCode:200 uri:/v1/health x-request-id:232c4ac0-eba2-4999-ba15-2d113f6efebe
时间有限,还得琢磨。