-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
⚡️ Added log package (copied from lhbelfanti/goxcrap)
- Loading branch information
1 parent
4ec0cb4
commit 5eb98f2
Showing
7 changed files
with
421 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.