Skip to content

Commit

Permalink
⚡️ Added log package (copied from lhbelfanti/goxcrap)
Browse files Browse the repository at this point in the history
  • Loading branch information
lhbelfanti committed Sep 5, 2024
1 parent 4ec0cb4 commit 5eb98f2
Show file tree
Hide file tree
Showing 7 changed files with 421 additions and 0 deletions.
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@ require (
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/kr/text v0.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
17 changes: 17 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
Expand All @@ -14,10 +16,20 @@ github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NB
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
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/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/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/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
Expand All @@ -29,6 +41,11 @@ golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
79 changes: 79 additions & 0 deletions internal/log/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package log

import (
"context"
"time"

"github.com/rs/zerolog"
)

// field represent a key-value tuple that will be added to the context
type (
field struct {
Key string
Value interface{}
}

logCtxKey struct{}
)

// Param creates a new field to be saved into context
func Param(key string, value interface{}) field {
return field{key, value}
}

// With custom function to add log parameters to the context
func With(ctx context.Context, fields ...field) context.Context {
// Get the existing map of parameters or create a new one
params, ok := ctx.Value(logCtxKey{}).(map[string]interface{})
if !ok {
params = make(map[string]interface{}, len(fields))
}

for _, t := range fields {
params[t.Key] = t.Value
}

return context.WithValue(ctx, logCtxKey{}, params)
}

func withContextParams(ctx context.Context, event *zerolog.Event) *zerolog.Event {
if ctx == nil {
return event
}

params, ok := ctx.Value(logCtxKey{}).(map[string]interface{})
if !ok {
return event
}

// If a specific type (supported by zerolog.Event) is needed, add it to the switch
for key, value := range params {
switch v := value.(type) {
case string:
event = event.Str(key, v)
case int:
event = event.Int(key, v)
case float64:
event = event.Float64(key, v)
case bool:
event = event.Bool(key, v)
case error:
event = event.AnErr(key, v)
case []string:
event = event.Strs(key, v)
case []int:
event = event.Ints(key, v)
case []float64:
event = event.Floats64(key, v)
case []byte:
event = event.Bytes(key, v)
case time.Time:
event = event.Time(key, v)
default:
event = event.Interface(key, v)
}
}

return event
}
123 changes: 123 additions & 0 deletions internal/log/context_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package log

import (
"bytes"
"context"
"testing"
"time"

"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
)

func TestWithContextParams_success(t *testing.T) {
tests := []struct {
name string
ctx context.Context
want map[string]interface{}
}{
{
name: "Nil context",
ctx: nil,
want: map[string]interface{}{},
},
{
name: "Empty context",
ctx: context.Background(),
want: map[string]interface{}{},
},
{
name: "Single types",
ctx: With(context.Background(),
Param("stringKey", "stringValue"),
Param("intKey", 42),
Param("float64Key", 42.5),
Param("boolKey", true),
),
want: map[string]interface{}{
"stringKey": "stringValue",
"intKey": 42,
"float64Key": 42.5,
"boolKey": true,
},
},
{
name: "Error value",
ctx: With(context.Background(), Param("errorKey", assert.AnError)),
want: map[string]interface{}{"errorKey": assert.AnError},
},
{
name: "Array values",
ctx: With(context.Background(),
Param("stringArrayKey", []string{"value1", "value2"}),
Param("intArrayKey", []int{1, 2, 3}),
Param("float64ArrayKey", []float64{1.0, 2.0, 3.0}),
Param("bytesKey", []byte("value")),
),
want: map[string]interface{}{
"stringArrayKey": []string{"value1", "value2"},
"intArrayKey": []int{1, 2, 3},
"float64ArrayKey": []float64{1.0, 2.0, 3.0},
"bytesKey": []byte("value"),
},
},
{
name: "Time value",
ctx: With(context.Background(), Param("timeKey", time.Now())),
want: map[string]interface{}{"timeKey": time.Now()},
},
{
name: "Interface value",
ctx: With(context.Background(),
Param("interfaceKey", struct{ key string }{key: "value"}),
),
want: map[string]interface{}{
"interfaceKey": struct{ key string }{key: "value"},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var buf bytes.Buffer
NewCustomLogger(&buf, zerolog.TraceLevel)
Info(tt.ctx, "")

got := buf.String()

for key, _ := range tt.want {
assert.Contains(t, got, key)
}
})
}
}
func TestWith_success(t *testing.T) {
ctx := context.Background()
want := []struct {
Key string
Value interface{}
}{
{"key1", "value1"},
{"key2", 123},
{"key1", "newValue"},
}

// Empty context
field1 := Param(want[0].Key, want[0].Value)
field2 := Param(want[1].Key, want[1].Value)

ctx = With(ctx, field1, field2)

got, ok := ctx.Value(logCtxKey{}).(map[string]interface{})
assert.True(t, ok)
assert.Equal(t, want[0].Value, got[want[0].Key])
assert.Equal(t, want[1].Value, got[want[1].Key])

// Context with params added
field3 := Param(want[2].Key, want[2].Value)
ctx = With(ctx, field3)

got, ok = ctx.Value(logCtxKey{}).(map[string]interface{})
assert.True(t, ok)
assert.Equal(t, want[2].Value, got[want[2].Key])
}
21 changes: 21 additions & 0 deletions internal/log/context_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package log_test

import (
"testing"

"github.com/stretchr/testify/assert"

"goxcrap/internal/log"
)

func TestParam_success(t *testing.T) {
want := struct {
Key string
Value string
}{"key", "value"}

got := log.Param(want.Key, want.Value)

assert.Equal(t, want.Key, got.Key)
assert.Equal(t, want.Value, got.Value)
}
71 changes: 71 additions & 0 deletions internal/log/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package log

import (
"context"
"io"
"os"

"github.com/rs/zerolog"
)

var logger = zerolog.New(os.Stdout).With().Timestamp().Logger().Level(zerolog.DebugLevel)

// NewCustomLogger returns a new custom logger writing to the provided writer
func NewCustomLogger(writer io.Writer, logLevel zerolog.Level) {
if writer == nil {
writer = os.Stdout
}

logger = zerolog.New(writer).With().Timestamp().Logger().Level(logLevel)
}

// Trace starts a new message with trace level
func Trace(ctx context.Context, msg string) {
logWithLevel(ctx, zerolog.TraceLevel, msg)
}

// Debug starts a new message with debug level
func Debug(ctx context.Context, msg string) {
logWithLevel(ctx, zerolog.DebugLevel, msg)
}

// Info starts a new message with info level
func Info(ctx context.Context, msg string) {
logWithLevel(ctx, zerolog.InfoLevel, msg)
}

// Warn starts a new message with warn level
func Warn(ctx context.Context, msg string) {
logWithLevel(ctx, zerolog.WarnLevel, msg)
}

// Err starts a new message with error level with err as a field if not nil or
// with info level if err is nil
func Err(ctx context.Context, err error, msg string) {
event := withContextParams(ctx, logger.Err(err))
event.Msg(msg)
}

// Error starts a new message with error level
func Error(ctx context.Context, msg string) {
logWithLevel(ctx, zerolog.ErrorLevel, msg)
}

// Fatal starts a new message with fatal level. The os.Exit(1) function
// is called by the Msg method
func Fatal(ctx context.Context, msg string) {
logWithLevel(ctx, zerolog.FatalLevel, msg)
}

// Panic starts a new message with panic level. The message is also sent
// to the panic function
func Panic(ctx context.Context, msg string) {
logWithLevel(ctx, zerolog.PanicLevel, msg)
}

// logWithLevel is a generic function to log messages at different levels
func logWithLevel(ctx context.Context, level zerolog.Level, msg string) {
event := logger.WithLevel(level)
event = withContextParams(ctx, event)
event.Msg(msg)
}
Loading

0 comments on commit 5eb98f2

Please sign in to comment.