feat: add a middleware to log requests

This commit is contained in:
Bruno Carlin 2025-01-29 23:21:16 +01:00
commit 44199b12c0
Signed by: bcarlin
GPG key ID: 8E254EA0FFEB9B6D
3 changed files with 88 additions and 0 deletions

2
doc.go Normal file
View file

@ -0,0 +1,2 @@
// Package mw provides a collection of middlewares for net/http.
package mw

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module code.bcarlin.net/go/mw
go 1.20

83
log_request.go Normal file
View 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
}