Commit 9c3c4f7

mo khan <mo@mokhan.ca>
2025-04-04 22:11:00
feat: add log package
1 parent 5cbcac4
pkg/log/context.go
@@ -0,0 +1,17 @@
+package log
+
+import (
+	"context"
+
+	"github.com/rs/zerolog"
+)
+
+func WithFields(ctx context.Context, fields Fields) {
+	From(ctx).UpdateContext(func(c zerolog.Context) zerolog.Context {
+		return c.Fields(fields.ToMap())
+	})
+}
+
+func From(ctx context.Context) *zerolog.Logger {
+	return zerolog.Ctx(ctx)
+}
pkg/log/fields.go
@@ -0,0 +1,7 @@
+package log
+
+type Fields map[string]interface{}
+
+func (f Fields) ToMap() map[string]interface{} {
+	return map[string]interface{}(f)
+}
pkg/log/log.go
@@ -0,0 +1,19 @@
+package log
+
+import (
+	"io"
+
+	"github.com/rs/zerolog"
+	"github.com/xlgmokha/x/pkg/convert"
+)
+
+func New(writer io.Writer, fields Fields) *zerolog.Logger {
+	return convert.ToPtr(
+		zerolog.
+			New(writer).
+			With().
+			Timestamp().
+			Fields(fields.ToMap()).
+			Logger(),
+	)
+}
pkg/log/log_test.go
@@ -0,0 +1,83 @@
+package log
+
+import (
+	"bufio"
+	"bytes"
+	"context"
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/rs/zerolog"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"github.com/xlgmokha/x/pkg/serde"
+)
+
+func TestLog(t *testing.T) {
+	t.Run("New", func(t *testing.T) {
+		var b bytes.Buffer
+		writer := bufio.NewWriter(&b)
+
+		log := New(writer, Fields{"env": "test"})
+		log.Print()
+
+		require.NoError(t, writer.Flush())
+
+		items, err := serde.FromJSON[map[string]string](bufio.NewReader(&b))
+		require.NoError(t, err)
+
+		assert.Equal(t, "test", items["env"])
+		assert.Equal(t, "debug", items["level"])
+		assert.NotEmpty(t, items["time"])
+	})
+
+	t.Run("WithFields", func(t *testing.T) {
+		var b bytes.Buffer
+		writer := bufio.NewWriter(&b)
+
+		log := New(writer, Fields{"env": "test"})
+		ctx := log.WithContext(context.Background())
+		WithFields(ctx, Fields{"ip": "127.0.0.1"})
+		zerolog.Ctx(ctx).Print()
+		log.Print()
+
+		require.NoError(t, writer.Flush())
+
+		items, err := serde.FromJSON[map[string]string](bufio.NewReader(&b))
+		require.NoError(t, err)
+
+		assert.Equal(t, "test", items["env"])
+		assert.Equal(t, "debug", items["level"])
+		assert.Equal(t, "127.0.0.1", items["ip"])
+	})
+
+	t.Run("WithMiddleware", func(t *testing.T) {
+		var b bytes.Buffer
+		writer := bufio.NewWriter(&b)
+		log := New(writer, Fields{"env": "test"})
+
+		server := httptest.NewServer(Middleware(log)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			w.WriteHeader(http.StatusTeapot)
+		})))
+		defer server.Close()
+
+		response, err := http.Get(server.URL)
+		require.NoError(t, err)
+		assert.Equal(t, http.StatusTeapot, response.StatusCode)
+
+		require.NoError(t, writer.Flush())
+		items, err := serde.FromJSON[map[string]interface{}](bufio.NewReader(&b))
+		require.NoError(t, err)
+
+		fmt.Printf("%v\n", items)
+		assert.Equal(t, "test", items["env"])
+		assert.Equal(t, "debug", items["level"])
+		assert.NotEmpty(t, items["time"])
+		assert.Equal(t, float64(http.StatusTeapot), items["status"])
+		assert.Equal(t, "GET", items["method"])
+		assert.Equal(t, "/", items["path"])
+		assert.Contains(t, items["remote_host"], "127.0.0.1")
+	})
+}
pkg/log/middleware.go
@@ -0,0 +1,29 @@
+package log
+
+import (
+	"net/http"
+
+	"github.com/go-chi/chi/v5/middleware"
+	"github.com/rs/zerolog"
+)
+
+func Middleware(logger *zerolog.Logger) func(http.Handler) http.Handler {
+	return func(next http.Handler) http.Handler {
+		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			ctx := logger.WithContext(r.Context())
+			ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
+
+			defer func() {
+				WithFields(ctx, Fields{
+					"status":      ww.Status(),
+					"method":      r.Method,
+					"path":        r.URL.Path,
+					"remote_host": r.RemoteAddr,
+				})
+				zerolog.Ctx(ctx).Print()
+			}()
+
+			next.ServeHTTP(ww, r.WithContext(ctx))
+		})
+	}
+}
go.mod
@@ -3,13 +3,18 @@ module github.com/xlgmokha/x
 go 1.18
 
 require (
+	github.com/go-chi/chi/v5 v5.2.1
 	github.com/google/jsonapi v1.0.0
-	github.com/stretchr/testify v1.8.0
+	github.com/rs/zerolog v1.34.0
+	github.com/stretchr/testify v1.8.1
 	gopkg.in/yaml.v2 v2.4.0
 )
 
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/mattn/go-colorable v0.1.13 // indirect
+	github.com/mattn/go-isatty v0.0.19 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
+	golang.org/x/sys v0.12.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
go.sum
@@ -1,15 +1,34 @@
+github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
+github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/google/jsonapi v1.0.0 h1:qIGgO5Smu3yJmSs+QlvhQnrscdZfFhiV6S8ryJAglqU=
 github.com/google/jsonapi v1.0.0/go.mod h1:YYHiRPJT8ARXGER8In9VuLv4qvLfDmA9ULQqptbLE4s=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
+github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
+github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=