Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support any type in encoder #28

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 6 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,14 @@ with simple APIs and configurable behavior.

## Why another logger?

Golog is designed to address mainly two issues:
Golog is designed to address mainly an issues:

#### Reduce the amount of PII (personally identifiable information) data in logs

Golog exposes APIs which does not allow to simply introduce a struct or a map as part of the log fields.

This design pushes the consumers of this library to care about PII data and
pushes to reduce as much as possible the amount of data which can be logged.
#### Add tracing and other extra data into the logging behavior
Golog expects to have a context passed down to the logging API.

It is possible to extend the logger behavior
for handling complex data type
by writing custom field factory functions as shown in the customization section.
The `context.Context` in Go is usually the holder for tracing information and
embedding one of the decorators available to the logger plugs this behavior for free
in all the places where the logger is used.

#### Add tracing and other extra data into the logging behavior

Expand Down
94 changes: 62 additions & 32 deletions benchmarks/logger/logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"testing"
"time"

"github.com/sirupsen/logrus"
"go.uber.org/zap"
Expand All @@ -19,46 +20,22 @@ goos: darwin
goarch: amd64
pkg: github.com/damianopetrungaro/golog/benchmarks/logger
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkLogger/golog-12 1266547 929.9 ns/op 2826 B/op 26 allocs/op
BenchmarkLogger/zap-12 1000000 1066 ns/op 2836 B/op 20 allocs/op
BenchmarkLogger/logrus-12 344604 3395 ns/op 6168 B/op 69 allocs/op
BenchmarkLogger/golog.Check-12 56982846 20.10 ns/op 64 B/op 1 allocs/op
BenchmarkLogger/zap.Check-12 1000000000 0.9662 ns/op 0 B/op 0 allocs/op
BenchmarkLogger/golog-12 73681 16878 ns/op 17361 B/op 128 allocs/op
BenchmarkLogger/zap-12 58617 20173 ns/op 28346 B/op 125 allocs/op
BenchmarkLogger/logrus-12 66474 18344 ns/op 13882 B/op 172 allocs/op
BenchmarkLogger/golog.Check-12 53974438 22.63 ns/op 64 B/op 1 allocs/op
BenchmarkLogger/zap.Check-12 1000000000 0.8838 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/damianopetrungaro/golog/benchmarks/logger 6.781s
ok github.com/damianopetrungaro/golog/benchmarks/logger 6.494s
*/
func BenchmarkLogger(b *testing.B) {

b.Run("logrus", func(b *testing.B) {
logrus.SetFormatter(&logrus.JSONFormatter{})
logrus.SetOutput(io.Discard)
logrus.SetLevel(logrus.DebugLevel)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logrus.WithFields(logrus.Fields{
"int": 10,
"ints": []int{1, 2, 3, 4, 5, 6, 7},
"string": "a string",
"strings": []string{"one", "one", "one", "one", "one", "one"},
"int_2": 10,
"ints_2": []int{1, 2, 3, 4, 5, 6, 7},
"string_2": "a string",
"strings_2": []string{"one", "one", "one", "one", "one", "one"},
"int_3": 10,
"ints_3": []int{1, 2, 3, 4, 5, 6, 7},
"string_3": "a string",
"strings_3": []string{"one", "one", "one", "one", "one", "one"},
"error": fmt.Errorf("an error occurred"),
}).Debug("This is a message")
}
})
})
users := helperUsers()

b.Run("golog", func(b *testing.B) {
ctx := context.Background()
writer := golog.NewBufWriter(
golog.NewTextEncoder(golog.DefaultTextConfig()),
golog.NewJsonEncoder(golog.DefaultJsonConfig()),
bufio.NewWriter(io.Discard),
golog.DefaultErrorHandler(),
golog.DEBUG,
Expand All @@ -84,6 +61,7 @@ func BenchmarkLogger(b *testing.B) {
golog.String("string_3", "a string"),
golog.Strings("strings_3", []string{"one", "one", "one", "one", "one", "one"}),
golog.Err(fmt.Errorf("an error occurred")),
golog.Any("users", users),
).Debug(ctx, "This is a message")
}
})
Expand Down Expand Up @@ -118,11 +96,39 @@ func BenchmarkLogger(b *testing.B) {
zap.String("string_3", "a string"),
zap.Strings("strings_3", []string{"one", "one", "one", "one", "one", "one"}),
zap.Error(fmt.Errorf("an error occurred")),
zap.Any("users", users),
).Debug("This is a message")
}
})
})

b.Run("logrus", func(b *testing.B) {
logrus.SetFormatter(&logrus.JSONFormatter{})
logrus.SetOutput(io.Discard)
logrus.SetLevel(logrus.DebugLevel)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logrus.WithFields(logrus.Fields{
"int": 10,
"ints": []int{1, 2, 3, 4, 5, 6, 7},
"string": "a string",
"strings": []string{"one", "one", "one", "one", "one", "one"},
"int_2": 10,
"ints_2": []int{1, 2, 3, 4, 5, 6, 7},
"string_2": "a string",
"strings_2": []string{"one", "one", "one", "one", "one", "one"},
"int_3": 10,
"ints_3": []int{1, 2, 3, 4, 5, 6, 7},
"string_3": "a string",
"strings_3": []string{"one", "one", "one", "one", "one", "one"},
"error": fmt.Errorf("an error occurred"),
"users": users,
}).Debug("This is a message")
}
})
})

b.Run("golog.Check", func(b *testing.B) {
ctx := context.Background()
writer := golog.NewBufWriter(
Expand Down Expand Up @@ -152,6 +158,7 @@ func BenchmarkLogger(b *testing.B) {
golog.String("string_3", "a string"),
golog.Strings("strings_3", []string{"one", "one", "one", "one", "one", "one"}),
golog.Err(fmt.Errorf("an error occurred")),
golog.Any("users", users),
)
}
}
Expand Down Expand Up @@ -188,9 +195,32 @@ func BenchmarkLogger(b *testing.B) {
zap.String("string_3", "a string"),
zap.Strings("strings_3", []string{"one", "one", "one", "one", "one", "one"}),
zap.Error(fmt.Errorf("an error occurred")),
zap.Any("users", users),
)
}
}
})
})
}

func helperUser() any {
return struct {
ID string
Name string
Age int
BirthDate time.Time
}{
ID: "123",
Name: "John",
Age: 123,
BirthDate: time.Now(),
}
}

func helperUsers() any {
us := make([]any, 100)
for i := 0; i < 100; i++ {
us[i] = helperUser()
}
return us
}
15 changes: 15 additions & 0 deletions encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package golog

import (
"bytes"
"encoding/json"
"io"
"strconv"
"time"
Expand Down Expand Up @@ -404,6 +405,20 @@ func (j JsonEncoder) encodeField(f Field, w *bytes.Buffer) {
j.addArrayElemQuoted(w, v.Format(j.Config.TimeLayout), i != len(val)-1)
}
})
default:
j.addElemEncoder(w, f.Key(), val)
}
}

func (j JsonEncoder) addElemEncoder(w *bytes.Buffer, k string, val any) {
w.WriteString(`"`)
w.WriteString(k)
w.WriteString(`":`)
if err := json.NewEncoder(w).Encode(val); err != nil {
w.WriteString(`"`)
w.WriteString(err.Error())
w.WriteString(`"`)
return
}
}

Expand Down
29 changes: 29 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,26 @@ import (

func ExampleLogger() {

user := struct {
ID string
Name string
}{
ID: "123",
Name: "John",
}

invalidJsonMap := map[any]any{
1: 1,
"2": 2,
"metadata": map[string]string{"count": "12"},
}

validJsonMap := map[string]any{
"1": 1,
"2": 2,
"metadata": map[string]string{"count": "12"},
}

w := golog.NewBufWriter(
golog.NewJsonEncoder(golog.DefaultJsonConfig()),
bufio.NewWriter(os.Stdout),
Expand All @@ -35,10 +55,19 @@ func ExampleLogger() {
logger.Info(ctx, "an info message")
loggerWithErr.Warn(ctx, "a warning message")

loggerWithErrAndUser := loggerWithErr.With(golog.Any("user", user))
loggerWithErrAndUser.With(golog.Any("unsupported map", invalidJsonMap)).Warn(ctx, "a warning message with an unsupported map")
loggerWithErrAndUser.With(golog.Any("supported map", validJsonMap)).Warn(ctx, "a warning message with a supported map")

// Output:
// {"level":"ERROR","message":"an error message","hello":"world"}
// {"level":"ERROR","message":"another error message","hello":"world"}
// {"level":"INFO","message":"an info message","hello":"world"}
// {"level":"WARN","message":"a warning message","hello":"world","error":"error: ops!"}
// {"level":"WARN","message":"a warning message with an unsupported map","hello":"world","error":"error: ops!","user":{"ID":"123","Name":"John"}
//,"unsupported map":"json: unsupported type: map[interface {}]interface {}"}
// {"level":"WARN","message":"a warning message with a supported map","hello":"world","error":"error: ops!","user":{"ID":"123","Name":"John"}
//,"supported map":{"1":1,"2":2,"metadata":{"count":"12"}}
//}
//
}
5 changes: 5 additions & 0 deletions field.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ func Times(k string, v []time.Time) Field {
return Field{k: k, v: v}
}

//Any ...
func Any(k string, v any) Field {
return Field{k: k, v: v}
}

//Err creates a field containing a value of type ") Fi"
func Err(err error) Field {
const k = "error"
Expand Down