1package web23import (4 "bufio"5 "fmt"6 "net"7 "net/http"8 "time"910 "charm.land/log/v2"11 "github.com/dustin/go-humanize"12)1314// logWriter is a wrapper around http.ResponseWriter that allows us to capture15// the HTTP status code and bytes written to the response.16type logWriter struct {17 http.ResponseWriter18 code, bytes int19}2021var (22 _ http.ResponseWriter = (*logWriter)(nil)23 _ http.Flusher = (*logWriter)(nil)24 _ http.Hijacker = (*logWriter)(nil)25 _ http.CloseNotifier = (*logWriter)(nil)26)2728// Write implements http.ResponseWriter.29func (r *logWriter) Write(p []byte) (int, error) {30 written, err := r.ResponseWriter.Write(p)31 r.bytes += written32 return written, err33}3435// Note this is generally only called when sending an HTTP error, so it's36// important to set the `code` value to 200 as a default.37func (r *logWriter) WriteHeader(code int) {38 r.code = code39 r.ResponseWriter.WriteHeader(code)40}4142// Unwrap returns the underlying http.ResponseWriter.43func (r *logWriter) Unwrap() http.ResponseWriter {44 return r.ResponseWriter45}4647// Flush implements http.Flusher.48func (r *logWriter) Flush() {49 if f, ok := r.ResponseWriter.(http.Flusher); ok {50 f.Flush()51 }52}5354// CloseNotify implements http.CloseNotifier.55func (r *logWriter) CloseNotify() <-chan bool {56 if cn, ok := r.ResponseWriter.(http.CloseNotifier); ok {57 return cn.CloseNotify()58 }59 return nil60}6162// Hijack implements http.Hijacker.63func (r *logWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {64 if h, ok := r.ResponseWriter.(http.Hijacker); ok {65 return h.Hijack()66 }67 return nil, nil, fmt.Errorf("http.Hijacker not implemented")68}6970// NewLoggingMiddleware returns a new logging middleware.71func NewLoggingMiddleware(next http.Handler, logger *log.Logger) http.Handler {72 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {73 start := time.Now()74 writer := &logWriter{code: http.StatusOK, ResponseWriter: w}75 logger.Debug("request",76 "method", r.Method,77 "path", r.URL,78 "addr", r.RemoteAddr)79 next.ServeHTTP(writer, r)80 elapsed := time.Since(start)81 logger.Debug("response",82 "status", fmt.Sprintf("%d %s", writer.code, http.StatusText(writer.code)),83 "bytes", humanize.Bytes(uint64(writer.bytes)), //nolint:gosec84 "time", elapsed)85 })86}