Fork me on GitHub

版权声明 本站原创文章 由 萌叔 发表
转载请注明 萌叔 | 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


请我喝瓶饮料

微信支付码

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注