core/internal/chislog/chislog.go

84 lines
2.3 KiB
Go

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:gomnd
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"
}
}