package chislog import ( "context" "log/slog" "net/http" "time" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" ) type Slogger interface { Log(ctx context.Context, level slog.Level, msg string, args ...any) } // Logger returns a go-chi middleware that logs requests using the given Slogger. func Logger(logger Slogger, opts ...Option) func(next http.Handler) http.Handler { cfg := newConfig(opts...) return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { for _, f := range cfg.filters { if !f(r) { next.ServeHTTP(w, r) return } } ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) start := time.Now() next.ServeHTTP(ww, r) end := time.Now() status := ww.Status() logger.Log( r.Context(), statusLevel(status), statusLabel(status), slog.String("routePattern", chi.RouteContext(r.Context()).RoutePattern()), slog.String("httpRequest.method", r.Method), slog.String("httpRequest.uri", r.RequestURI), slog.String("httpRequest.referer", r.Referer()), slog.String("httpRequest.userAgent", r.UserAgent()), slog.String("httpRequest.proto", r.Proto), slog.String("httpRequest.ip", cfg.ipExtractor(r)), slog.Int("httpResponse.status", status), slog.Int("httpResponse.bytes", ww.BytesWritten()), //nolint:mnd slog.Float64("httpResponse.duration", float64(end.Sub(start).Nanoseconds())/1000000.0), // in milliseconds ) }) } } func statusLevel(status int) slog.Level { switch { case status < http.StatusContinue: return slog.LevelWarn case status >= http.StatusBadRequest && status < http.StatusInternalServerError: return slog.LevelWarn case status >= http.StatusInternalServerError: return slog.LevelError default: // for statuses 100 <= 400 return slog.LevelInfo } } func statusLabel(status int) string { switch { case status >= http.StatusContinue && status < http.StatusMultipleChoices: return "OK" case status >= http.StatusMultipleChoices && status < http.StatusBadRequest: return "Redirect" case status >= http.StatusBadRequest && status < http.StatusInternalServerError: return "Client Error" case status >= http.StatusInternalServerError: return "Server Error" default: return "Unknown" } }