88 lines
1.9 KiB
Go
88 lines
1.9 KiB
Go
package srv
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"regexp"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
var ExcludeFromLogging = regexp.MustCompile(`\/(ready|live|metrics)$`)
|
|
|
|
type LoggingResponseWriter struct {
|
|
http.ResponseWriter
|
|
statusCode int
|
|
body *bytes.Buffer
|
|
}
|
|
|
|
func loggingMiddleware(appCtx context.Context, next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if ExcludeFromLogging.Match([]byte(r.URL.Path)) {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
log := zerolog.Ctx(appCtx)
|
|
|
|
start := time.Now()
|
|
lrr := newLoggingResponseWriter(w)
|
|
next.ServeHTTP(lrr, r)
|
|
|
|
log.Debug().
|
|
Str("path", r.URL.Path).
|
|
Any("query", r.URL.Query()).
|
|
Int("statusCode", lrr.statusCode).
|
|
Str("protocol", r.Proto).
|
|
Str("remote", r.RemoteAddr).
|
|
Dur("duration", time.Since(start)).
|
|
Msg("http request served")
|
|
|
|
// Log response body
|
|
trcLog := log.Trace().
|
|
Str("path", r.URL.Path).
|
|
Int("statusCode", lrr.statusCode)
|
|
|
|
// Check if it's JSON
|
|
firstByte, err := lrr.body.ReadByte()
|
|
if err != nil {
|
|
trcLog.Err(errors.New("invalid response body")).Send()
|
|
return
|
|
}
|
|
lrr.body.UnreadByte()
|
|
|
|
if firstByte == '{' {
|
|
trcLog = trcLog.RawJSON("response", lrr.body.Bytes())
|
|
} else {
|
|
trcLog = trcLog.Bytes("response", lrr.body.Bytes())
|
|
}
|
|
trcLog.Msg("response payload")
|
|
})
|
|
}
|
|
|
|
// Implement Flush to support the http.Flusher interface
|
|
func (w *LoggingResponseWriter) Flush() {
|
|
if flusher, ok := w.ResponseWriter.(http.Flusher); ok {
|
|
flusher.Flush()
|
|
}
|
|
}
|
|
|
|
func (w *LoggingResponseWriter) WriteHeader(code int) {
|
|
w.statusCode = code
|
|
w.ResponseWriter.WriteHeader(code)
|
|
}
|
|
|
|
func (w *LoggingResponseWriter) Write(b []byte) (int, error) {
|
|
w.body.Write(b)
|
|
return w.ResponseWriter.Write(b)
|
|
}
|
|
|
|
func newLoggingResponseWriter(w http.ResponseWriter) *LoggingResponseWriter {
|
|
return &LoggingResponseWriter{
|
|
ResponseWriter: w, statusCode: http.StatusOK, body: bytes.NewBuffer(nil),
|
|
}
|
|
}
|