package mw import ( "fmt" "log/slog" "net/http" "time" ) const ( logRequest = "request" logResponse = "response" logMethod = "method" logURI = "uri" logStatus = "status" logSize = "size" logDuration = "duration" ) // LoggingMiddleware returns a middleware that logs the request and response, // each in a loggroup. // // The request group contains the request method and URI. // The response group contains the response status, size and duration. func LoggingMiddleware(l *slog.Logger) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { startTime := time.Now() lwr := &loggingResponseWriter{ ResponseWriter: w, data: &responseData{status: http.StatusOK, size: 0}, } next.ServeHTTP(lwr, r) l.InfoContext(r.Context(), "HTTP request answered", slog.Group( logRequest, slog.String(logMethod, r.Method), slog.String(logURI, r.RequestURI), ), slog.Group( logResponse, slog.Int(logStatus, lwr.data.status), slog.Int(logSize, lwr.data.size), slog.Duration(logDuration, time.Since(startTime)), ), ) }) } } // Struct for holding response details. type responseData struct { status int size int } // loggingResponseWriter is a wrapper for http.ResponseWriter that records the // size of data sent and the status code of the response. type loggingResponseWriter struct { http.ResponseWriter // compose original http.ResponseWriter data *responseData } // Write implements http.ResponseWriter. func (r *loggingResponseWriter) Write(b []byte) (int, error) { size, err := r.ResponseWriter.Write(b) r.data.size += size if err != nil { return size, fmt.Errorf("cannot forward data: %w", err) } return size, nil } // WriteHeader implements http.ResponseWriter. func (r *loggingResponseWriter) WriteHeader(statusCode int) { r.ResponseWriter.WriteHeader(statusCode) r.data.status = statusCode }