大猩猩日志记录每个请求的持续时间和状态代码

问题描述

我有一个用Go1.13编写的REST API,使用的是gorilla / mux v1.7.3和gorilla / context v1.1.1。

我想记录每个请求的持续时间和状态代码。我已经有一个基本的工作版本,但是我想知道是否有更干净的选择。

我大约有20个HTTP调用。让我们以其中一个为例:

client.HandleFunc("/register",register.Handle).Methods("POST") 

register.Handle的签名是:

func Handle(w http.ResponseWriter,r *http.Request) { ... }

因此,为了记录每个请求的持续时间和状态代码,我只用“日志处理程序”包装方法处理程序register.Handle

client.HandleFunc("/register",log.HandleRequestWithLog(register.Handle)).Methods("POST")

log.HandleRequestWithLog仅对函数执行进行计时,并使用返回的状态代码(使用Azure指标进行记录)对其进行记录:

func HandleRequestWithLog(h func(http.ResponseWriter,*http.Request) int) http.HandlerFunc {
    return func(writer http.ResponseWriter,request *http.Request) {
        startTime := time.Now()
        statusCode := h(writer,request)
        duration := time.Now().Sub(startTime)
        trace := appinsights.NewRequestTelemetry(request.Method,request.URL.Path,duration,string(rune(statusCode)))
        trace.Timestamp = time.Now()
        client.Track(trace)
    }
}

因此,为了提取状态码,我需要每个HTTP方法都返回状态码。所以我将签名更改为:

func Handle(w http.ResponseWriter,r *http.Request) int { .. }

我想知道是否有比返回每个请求的HTTP状态代码更好的选择。另外,如果我想从请求中获取更多信息,该怎么办?我还要返回这个值吗?

解决方法

您可以定义LogResponseWriter来实现http.ResponseWriter,以跟踪响应数据(状态,正文等),而不是更改HTTP处理程序签名。

  1. 让我们定义LogResponseWriter
type LogResponseWriter struct {
    http.ResponseWriter
    statusCode int
    buf        bytes.Buffer
}

func NewLogResponseWriter(w http.ResponseWriter) *LogResponseWriter {
    return &LogResponseWriter{ResponseWriter: w}
}

func (w *LogResponseWriter) WriteHeader(code int) {
    w.statusCode = code
    w.ResponseWriter.WriteHeader(code)
}

func (w *LogResponseWriter) Write(body []byte) (int,error) {
    w.buf.Write(body)
    return w.ResponseWriter.Write(body)
}
  1. 定义LogMiddleware(在这里,我使用标准日志和mux.MiddlewareFunc
type LogMiddleware struct {
    logger *log.Logger
}

func NewLogMiddleware(logger *log.Logger) *LogMiddleware {
    return &LogMiddleware{logger: logger}
}

func (m *LogMiddleware) Func() mux.MiddlewareFunc {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter,r *http.Request) {
            startTime := time.Now()

            logRespWriter := NewLogResponseWriter(w)
            next.ServeHTTP(logRespWriter,r)

            m.logger.Printf(
                "duration=%s status=%d body=%s",time.Since(startTime).String(),logRespWriter.statusCode,logRespWriter.buf.String())
        })
    }
}
  1. main.go中启动服务器。在这里,我们可以在路由器中使用LogMiddleware
func main() {
    logger := log.New(os.Stdout,"",log.LstdFlags)

    router := mux.NewRouter()
    router.HandleFunc("/",func(w http.ResponseWriter,r *http.Request) {
        time.Sleep(1 * time.Second)

        w.WriteHeader(http.StatusOK)
        w.Header().Set("Content-Type","application/json")
        w.Write([]byte(`{"msg": "hello world"}`))
    })

    logMiddleware := NewLogMiddleware(logger)
    router.Use(logMiddleware.Func())

    logger.Fatalln(http.ListenAndServe(":8080",router))
}

我访问http://localhost:8080时的日志结果:

2020/10/08 00:51:38 duration=1.003927394s status=200 body={"msg": "hello world"}
  1. 跟进:如果我想从请求中获取更多信息,将会怎样?

在日志中间件中,您可以读取请求标头和正文(来自r *http.Request),然后使用持续时间和响应数据记录它们。请注意,您应该reassign the request body,以便您的实际处理程序可以读取请求正文。