feat: add a middleware to log requests
This commit is contained in:
commit
44199b12c0
3 changed files with 88 additions and 0 deletions
2
doc.go
Normal file
2
doc.go
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Package mw provides a collection of middlewares for net/http.
|
||||||
|
package mw
|
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module code.bcarlin.net/go/mw
|
||||||
|
|
||||||
|
go 1.20
|
83
log_request.go
Normal file
83
log_request.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
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
|
||||||
|
}
|
Loading…
Reference in a new issue