Hello everyone!

We have been investing plenty of personal time and energy for many years to share our knowledge with you all. However, we now need your help to keep this blog running. All you have to do is just click one of the adverts on the site, otherwise it will sadly be taken down due to hosting etc. costs. Thank you.

This example will help you to capture request and response related data then log it.


Middleware


package middleware

import (
"bufio"
"fmt"
"net"
"net/http"
"time"

"github.com/sirupsen/logrus"
)

// AccessLog helps log request and response related data.
func AccessLog(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t := time.Now()

wr := newWriter(w, r, t)

h.ServeHTTP(wr, r)

logrus.WithFields(logrus.Fields{
"http_req_client_ip": wr.reqClientIP,
"http_req_duration": time.Since(t).Milliseconds(),
"http_req_host": wr.reqHost,
"http_req_method": wr.reqMethod,
"http_req_path": wr.reqPath,
"http_req_protocol": wr.reqProto,
"http_req_size": wr.reqSize,
"http_req_time": wr.reqTime,
"http_req_user_agent": wr.reqUserAgent,
"http_res_size": wr.resSize,
"http_res_status": wr.resStatus,
}).Info()
})
}

// Acts as an adapter for http.ResponseWriter type to store request and
// response data.
type writer struct {
http.ResponseWriter

reqClientIP string
reqHost string
reqMethod string
reqPath string
reqProto string
reqSize int64 // bytes
reqTime string
reqUserAgent string

resStatus int
resSize int // bytes
}

func newWriter(w http.ResponseWriter, r *http.Request, t time.Time) *writer {
return &writer{
ResponseWriter: w,

reqClientIP: r.Header.Get("X-Forwarded-For"),
reqMethod: r.Method,
reqHost: r.Host,
reqPath: r.RequestURI,
reqProto: r.Proto,
reqSize: r.ContentLength,
reqTime: t.Format(time.RFC3339),
reqUserAgent: r.UserAgent(),
}
}

// Overrides http.ResponseWriter type.
func (w *writer) WriteHeader(status int) {
if w.resStatus == 0 {
w.resStatus = status
w.ResponseWriter.WriteHeader(status)
}
}

// Overrides http.ResponseWriter type.
func (w *writer) Write(body []byte) (int, error) {
if w.resStatus == 0 {
w.WriteHeader(http.StatusOK)
}

var err error
w.resSize, err = w.ResponseWriter.Write(body)

return w.resSize, err
}

// Overrides http.Flusher type.
func (w *writer) Flush() {
if fl, ok := w.ResponseWriter.(http.Flusher); ok {
if w.resStatus == 0 {
w.WriteHeader(http.StatusOK)
}

fl.Flush()
}
}

// Overrides http.Hijacker type.
func (w *writer) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hj, ok := w.ResponseWriter.(http.Hijacker)
if !ok {
return nil, nil, fmt.Errorf("the hijacker interface is not supported")
}

return hj.Hijack()
}

Examples


{
"http_req_client_ip": "1.18.258.61",
"http_req_duration": 33,
"http_req_host": "localhost:8080",
"http_req_method": "POST",
"http_req_path": "/api/v1/leagues",
"http_req_protocol": "HTTP/1.1",
"http_req_size": 133,
"http_req_time": "2021-07-10T22:49:47+01:00",
"http_req_user_agent": "PostmanRuntime/7.26.8",
"http_res_size": 56,
"http_res_status": 201,
"level": "info",
"message": "",
"time": "2021-07-10T22:49:47+01:00"
}

{
"http_req_client_ip": "1a01:1c8:411:f815:1:2:961d:22e1",
"http_req_duration": 1000,
"http_req_host": "localhost:8080",
"http_req_method": "GET",
"http_req_path": "/api/v1/leagues/f47a0eaf-cfd3-4842-9a3e-19eda62404a8",
"http_req_protocol": "HTTP/1.1",
"http_req_size": 0,
"http_req_time": "2021-07-10T22:59:50+01:00",
"http_req_user_agent": "Mozilla/5.0 (Android 9; Mobile; rv:88.0) Gecko/88.0 Firefox/88.0",
"http_res_size": 225,
"http_res_status": 200,
"level": "info",
"message": "",
"time": "2021-07-10T22:59:51+01:00"
}