gin 统计请求状态信息
版权声明 本站原创文章 由 萌叔 发表
转载请注明 萌叔 | https://vearne.cc
1. 引言
gin-gonic/gin
是Golang API 开发中最常用到的web框架。我们可以轻松的写一个gin的中间件获取HTTP的状态码, 然后暴露给phrometheus
。但是如果我想获取的body体中的错误码呢?
2. 官方的例子
package main
import (
"time"
"log"
"github.com/gin-gonic/gin"
)
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
// Set example variable
c.Set("example", "12345")
// before request
c.Next()
// after request
latency := time.Since(t)
log.Println("latency", latency)
// access the status we are sending
status := c.Writer.Status()
log.Println("status_code", status)
}
}
func main() {
r := gin.New()
r.Use(Logger())
r.GET("/test", func(c *gin.Context) {
example := c.MustGet("example").(string)
// it would print: "12345"
log.Println(example)
})
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
3. 读取body中的错误码
假定我们的服务在请求处理失败的情况下,返回如下结构体
type ErrorResp struct {
Code string `json:"code"`
Msg string `json:"msg"`
}
我们都知道gin的writer是向流中写入数据,而获取错误码需要对ErrorResp进行JSON的反序列化。
type Context struct {
Writer ResponseWriter
...
}
所以可行的方法是,创建1个本地的Buff, 再向网络流中写入数据的同时,也向Buff中写入。这样我们就能获得完整的Response的Body体
package main
import (
"log"
"github.com/gin-gonic/gin"
"net/http"
"bytes"
"encoding/json"
)
const SUCCESS_CODE = "E000"
const INTERNAL_CODE = "E001"
type ErrorResp struct {
Code string `json:"code"`
Msg string `json:"msg"`
}
type ErrorCode struct{
Code string `json:"code"`
}
type bodyLogWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (w bodyLogWriter) Write(b []byte) (int, error) {
if n, err := w.body.Write(b); err != nil {
log.Println("err", err)
return n, err
}
return w.ResponseWriter.Write(b)
}
func Logger2() gin.HandlerFunc {
return func(c *gin.Context) {
// before request
// 注意这里Buff的创建可能带来性能问题
// 可参考 [GOLANG 内存分配优化](https://vearne.cc/archives/671)
// 进行优化
blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
c.Writer = blw
c.Next()
var code string
var res ErrorCode
log.Println(string(blw.body.Bytes()))
if err := json.Unmarshal(blw.body.Bytes(), &res); err != nil {
// 1. Body体不是合法的JSON
code = INTERNAL_CODE
log.Println("error", err)
} else if len(res.Code) <= 0 {
// 2. Body体是合法JSON
// JSON不含有"code"字段
code = SUCCESS_CODE
}else{
// 3.Body体是合法JSON
// JSON含有"code"字段
code = res.Code
}
log.Println("code", code)
}
}
func main() {
r := gin.New()
r.Use(Logger2())
r.GET("/test", func(c *gin.Context) {
log.Println("-----gin_middleware2------")
data := `{"msg":"test"}`
//data := `xxx`
//data := `{"code":"E002", "msg":"test"}`
c.Data(http.StatusOK,
"application/json", []byte(data))
})
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
注意
这里需要考虑3种情况
1)Body体不是合法的JSON
json.Unmarshal()
返回error
2)Body体是合法JSON,不含有"code"字段
res.Code是空字符串
3)Body体是合法JSON,含有"code"字段
后记
有了HTTP状态码和Respnse的错误码,在结合phrometheus
我们就能很好的对业务进行监控了。
参考资料
gin-gonic/gin#custom-middleware