83 lines
2 KiB
Go
83 lines
2 KiB
Go
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
|
|
}
|