88 lines
		
	
	
		
			1.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			88 lines
		
	
	
		
			1.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package http
 | 
						|
 | 
						|
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),
 | 
						|
	}
 | 
						|
}
 |