版权声明 本站原创文章 由 萌叔 发表
转载请注明 萌叔 | http://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 内存分配优化](http://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


如果我的文章对你有帮助,你可以给我打赏以促使我拿出更多的时间和精力来分享我的经验和思考总结。

微信支付码

发表评论

电子邮件地址不会被公开。

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据