diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1eaa9b..a14d135 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ on: push: branches: - main - - 'v*' + - "v*" pull_request: {} release: types: [published] @@ -14,13 +14,17 @@ jobs: name: Tests runs-on: ubuntu-latest + strategy: + matrix: + go-version: [1.14.x, 1.21.x] + steps: - uses: actions/checkout@v2 - name: Install Go uses: actions/setup-go@v2 with: - go-version: 1.14.x + go-version: ${{matrix.go-version}} - name: Install Dependencies run: go mod download diff --git a/context.go b/context.go index dc10a12..d12afd2 100644 --- a/context.go +++ b/context.go @@ -2,8 +2,10 @@ package log import "context" +type logContextType string + // ContextKeyLogFields is the key for the logging fields context value. -const ContextKeyLogFields = "nrfta/go-log/Fields" +const ContextKeyLogFields logContextType = "nrfta/go-log/Fields" // WithContext initializes context with a logging fields stack with the given fields. If the given // context has already bene initialized, then the fields are pushed onto the existing stack. diff --git a/go.mod b/go.mod index fba0746..20920cb 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/nrfta/go-log -go 1.14 +go 1.21 require ( github.com/go-chi/chi v4.1.2+incompatible @@ -9,3 +9,16 @@ require ( github.com/onsi/gomega v1.10.1 github.com/sirupsen/logrus v1.6.0 ) + +require ( + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect + github.com/nxadm/tail v1.4.4 // indirect + github.com/pkg/errors v0.9.1 // indirect + golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 // indirect + golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 // indirect + golang.org/x/text v0.3.2 // indirect + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect +) diff --git a/go.sum b/go.sum index 30c0f29..d98dfdd 100644 --- a/go.sum +++ b/go.sum @@ -15,7 +15,6 @@ github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= diff --git a/slog.go b/slog.go new file mode 100644 index 0000000..2eb7236 --- /dev/null +++ b/slog.go @@ -0,0 +1,43 @@ +//go:build go1.21 +// +build go1.21 + +package log + +import ( + "log/slog" + "net/http" + "time" + + "github.com/go-chi/chi/middleware" +) + +// NewSLogChiMiddleware is used to log http request information. It takes +// a pointer to an slog.Logger to use. If `l` is nil, it uses the +// default logger +func NewSLogChiMiddleware(l *slog.Logger) func(http.Handler) http.Handler { + if l == nil { + l = slog.Default() + } + + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) + + defer func(start time.Time) { + l.LogAttrs( + r.Context(), + slog.LevelInfo, + "HTTP Request Served", + slog.String("proto", r.Proto), + slog.String("path", r.URL.Path), + slog.Duration("duration", time.Since(start)), + slog.Int("status", ww.Status()), + slog.Int("size", ww.BytesWritten()), + slog.String("ip", r.RemoteAddr), + ) + }(time.Now()) + + next.ServeHTTP(ww, r) + }) + } +} diff --git a/slog_test.go b/slog_test.go new file mode 100644 index 0000000..16ce4ec --- /dev/null +++ b/slog_test.go @@ -0,0 +1,62 @@ +//go:build go1.21 +// +build go1.21 + +package log_test + +import ( + "bytes" + "encoding/json" + "log/slog" + "net/http" + "net/http/httptest" + "strings" + + "github.com/nrfta/go-log" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Logger", func() { + Describe("NewSlogHTTPMiddleware", func() { + It("should log http request information", func() { + var ( + buf bytes.Buffer + logger = slog.New(slog.NewJSONHandler(&buf, nil)) + mw = log.NewSLogChiMiddleware(logger) + res = "test" + hf = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(res)) + }) + ) + + ts := httptest.NewServer(mw(hf)) + defer ts.Close() + + _, err := http.Get(ts.URL) + Expect(err).To(Succeed()) + + type logOutput struct { + Msg string `json:"msg"` + Proto string `json:"proto"` + Path string `json:"path"` + Duration int `json:"duration"` + Status int `json:"status"` + Size int `json:"size"` + IP string `json:"ip"` + } + + var lo logOutput + err = json.Unmarshal(buf.Bytes(), &lo) + Expect(err).To(Succeed()) + + Expect(lo.Msg).To(Equal("HTTP Request Served")) + Expect(lo.Proto).To(Equal("HTTP/1.1")) + Expect(lo.Path).To(Equal("/")) + Expect(lo.Duration).To(BeNumerically(">", 0)) + Expect(lo.Status).To(Equal(200)) + Expect(lo.Size).To(Equal(len(res))) + Expect(strings.Split(lo.IP, ":")[0]).To(Equal("127.0.0.1")) + }) + }) +})