package middleware import ( "net/http" "time" chi_middleware "github.com/go-chi/chi/v5/middleware" "golang.org/x/exp/slog" ) // Slogger is a slog-enabled logging middleware. // It logs the start and end of the request, and logs info // about the request itself, response status, and response time. // Slogger returns a log handler that uses the given slog logger as the base. func Slogger(sl *slog.Logger) func(next http.Handler) http.Handler { logger := sl.WithGroup("http") return func(next http.Handler) http.Handler { // this triple-nested function is strange, but basically the Slogger() call makes a new middleware function (above) // the middleware function returns a handler that calls the next handler in the chain(wrapping it) fn := func(w http.ResponseWriter, r *http.Request) { // wrap writer allows us to get info on the response from further handlers. ww := chi_middleware.NewWrapResponseWriter(w, r.ProtoMajor) t1 := time.Now() // attrs is stored to allow for the helpers to add additional elements to the main record. attrs := make([]slog.Attr, 0) // This function runs at the end and adds all the response details to the attrs before logging them. defer func() { attrs = append(attrs, slog.Int("status_code", ww.Status())) attrs = append(attrs, slog.Int("resp_size", ww.BytesWritten())) attrs = append(attrs, slog.Duration("duration", time.Since(t1))) attrs = append(attrs, slog.String("method", r.Method)) logger.LogAttrs(r.Context(), slog.LevelInfo, r.RequestURI, attrs...) }() // embed the logger and the attrs for later items in the chain. next.ServeHTTP(ww, r) } return http.HandlerFunc(fn) } } type slogKeyType int const ( SloggerLogKey slogKeyType = iota SloggerAttrsKey ) func addSlogAttr(r *http.Request, attr slog.Attr) { ctx := r.Context() attrs, ok := ctx.Value(SloggerAttrsKey).([]slog.Attr) if !ok { return } attrs = append(attrs, attr) }