Skip to content

Commit

Permalink
Make Fields method accept both map and slice (#352)
Browse files Browse the repository at this point in the history
  • Loading branch information
hn8 committed Sep 8, 2021
1 parent cdd7417 commit 65adfd8
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 34 deletions.
40 changes: 20 additions & 20 deletions benchmark_test.go
Expand Up @@ -148,16 +148,16 @@ func BenchmarkLogFieldType(b *testing.B) {
{"a", "a", 0},
}
objects := []obj{
obj{"a", "a", 0},
obj{"a", "a", 0},
obj{"a", "a", 0},
obj{"a", "a", 0},
obj{"a", "a", 0},
obj{"a", "a", 0},
obj{"a", "a", 0},
obj{"a", "a", 0},
obj{"a", "a", 0},
obj{"a", "a", 0},
{"a", "a", 0},
{"a", "a", 0},
{"a", "a", 0},
{"a", "a", 0},
{"a", "a", 0},
{"a", "a", 0},
{"a", "a", 0},
{"a", "a", 0},
{"a", "a", 0},
{"a", "a", 0},
}
errs := []error{errors.New("a"), errors.New("b"), errors.New("c"), errors.New("d"), errors.New("e")}
types := map[string]func(e *Event) *Event{
Expand Down Expand Up @@ -272,16 +272,16 @@ func BenchmarkContextFieldType(b *testing.B) {
{"a", "a", 0},
}
objects := []obj{
obj{"a", "a", 0},
obj{"a", "a", 0},
obj{"a", "a", 0},
obj{"a", "a", 0},
obj{"a", "a", 0},
obj{"a", "a", 0},
obj{"a", "a", 0},
obj{"a", "a", 0},
obj{"a", "a", 0},
obj{"a", "a", 0},
{"a", "a", 0},
{"a", "a", 0},
{"a", "a", 0},
{"a", "a", 0},
{"a", "a", 0},
{"a", "a", 0},
{"a", "a", 0},
{"a", "a", 0},
{"a", "a", 0},
{"a", "a", 0},
}
errs := []error{errors.New("a"), errors.New("b"), errors.New("c"), errors.New("d"), errors.New("e")}
types := map[string]func(c Context) Context{
Expand Down
72 changes: 72 additions & 0 deletions binary_test.go
Expand Up @@ -364,6 +364,42 @@ func ExampleEvent_Durs() {
// Output: {"foo":"bar","durs":[10000,20000],"message":"hello world"}
}

func ExampleEvent_Fields_map() {
fields := map[string]interface{}{
"bar": "baz",
"n": 1,
}

dst := bytes.Buffer{}
log := New(&dst)

log.Log().
Str("foo", "bar").
Fields(fields).
Msg("hello world")

fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","bar":"baz","n":1,"message":"hello world"}
}

func ExampleEvent_Fields_slice() {
fields := []interface{}{
"bar", "baz",
"n", 1,
}

dst := bytes.Buffer{}
log := New(&dst)

log.Log().
Str("foo", "bar").
Fields(fields).
Msg("hello world")

fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","bar":"baz","n":1,"message":"hello world"}
}

func ExampleContext_Dict() {
dst := bytes.Buffer{}
log := New(&dst).With().
Expand Down Expand Up @@ -510,3 +546,39 @@ func ExampleContext_Durs() {
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","durs":[10000,20000],"message":"hello world"}
}

func ExampleContext_Fields_map() {
fields := map[string]interface{}{
"bar": "baz",
"n": 1,
}

dst := bytes.Buffer{}
log := New(&dst).With().
Str("foo", "bar").
Fields(fields).
Logger()

log.Log().Msg("hello world")

fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","bar":"baz","n":1,"message":"hello world"}
}

func ExampleContext_Fields_slice() {
fields := []interface{}{
"bar", "baz",
"n", 1,
}

dst := bytes.Buffer{}
log := New(&dst).With().
Str("foo", "bar").
Fields(fields).
Logger()

log.Log().Msg("hello world")

fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","bar":"baz","n":1,"message":"hello world"}
}
6 changes: 4 additions & 2 deletions context.go
Expand Up @@ -18,8 +18,10 @@ func (c Context) Logger() Logger {
return c.l
}

// Fields is a helper function to use a map to set fields using type assertion.
func (c Context) Fields(fields map[string]interface{}) Context {
// Fields is a helper function to use a map or slice to set fields using type assertion.
// Only map[string]interface{} and []interface{} are accepted. []interface{} must
// alternate string keys and arbitrary values, and extraneous ones are ignored.
func (c Context) Fields(fields interface{}) Context {
c.l.context = appendFields(c.l.context, fields)
return c
}
Expand Down
6 changes: 4 additions & 2 deletions event.go
Expand Up @@ -148,8 +148,10 @@ func (e *Event) msg(msg string) {
}
}

// Fields is a helper function to use a map to set fields using type assertion.
func (e *Event) Fields(fields map[string]interface{}) *Event {
// Fields is a helper function to use a map or slice to set fields using type assertion.
// Only map[string]interface{} and []interface{} are accepted. []interface{} must
// alternate string keys and arbitrary values, and extraneous ones are ignored.
func (e *Event) Fields(fields interface{}) *Event {
if e == nil {
return e
}
Expand Down
37 changes: 29 additions & 8 deletions fields.go
Expand Up @@ -12,15 +12,36 @@ func isNilValue(i interface{}) bool {
return (*[2]uintptr)(unsafe.Pointer(&i))[1] == 0
}

func appendFields(dst []byte, fields map[string]interface{}) []byte {
keys := make([]string, 0, len(fields))
for key := range fields {
keys = append(keys, key)
func appendFields(dst []byte, fields interface{}) []byte {
switch fields := fields.(type) {
case []interface{}:
if n := len(fields); n&0x1 == 1 { // odd number
fields = fields[:n-1]
}
dst = appendFieldList(dst, fields)
case map[string]interface{}:
keys := make([]string, 0, len(fields))
for key := range fields {
keys = append(keys, key)
}
sort.Strings(keys)
kv := make([]interface{}, 2)
for _, key := range keys {
kv[0], kv[1] = key, fields[key]
dst = appendFieldList(dst, kv)
}
}
sort.Strings(keys)
for _, key := range keys {
dst = enc.AppendKey(dst, key)
val := fields[key]
return dst
}

func appendFieldList(dst []byte, kvList []interface{}) []byte {
for i, n := 0, len(kvList); i < n; i += 2 {
key, val := kvList[i], kvList[i+1]
if key, ok := key.(string); ok {
dst = enc.AppendKey(dst, key)
} else {
continue
}
if val, ok := val.(LogObjectMarshaler); ok {
e := newEvent(nil, 0)
e.buf = e.buf[:0]
Expand Down
64 changes: 64 additions & 0 deletions log_example_test.go
Expand Up @@ -335,6 +335,38 @@ func ExampleEvent_Durs() {
// Output: {"foo":"bar","durs":[10000,20000],"message":"hello world"}
}

func ExampleEvent_Fields_map() {
fields := map[string]interface{}{
"bar": "baz",
"n": 1,
}

log := zerolog.New(os.Stdout)

log.Log().
Str("foo", "bar").
Fields(fields).
Msg("hello world")

// Output: {"foo":"bar","bar":"baz","n":1,"message":"hello world"}
}

func ExampleEvent_Fields_slice() {
fields := []interface{}{
"bar", "baz",
"n", 1,
}

log := zerolog.New(os.Stdout)

log.Log().
Str("foo", "bar").
Fields(fields).
Msg("hello world")

// Output: {"foo":"bar","bar":"baz","n":1,"message":"hello world"}
}

func ExampleContext_Dict() {
log := zerolog.New(os.Stdout).With().
Str("foo", "bar").
Expand Down Expand Up @@ -484,3 +516,35 @@ func ExampleContext_MacAddr() {

// Output: {"hostMAC":"00:14:22:01:23:45","message":"hello world"}
}

func ExampleContext_Fields_map() {
fields := map[string]interface{}{
"bar": "baz",
"n": 1,
}

log := zerolog.New(os.Stdout).With().
Str("foo", "bar").
Fields(fields).
Logger()

log.Log().Msg("hello world")

// Output: {"foo":"bar","bar":"baz","n":1,"message":"hello world"}
}

func ExampleContext_Fields_slice() {
fields := []interface{}{
"bar", "baz",
"n", 1,
}

log := zerolog.New(os.Stdout).With().
Str("foo", "bar").
Fields(fields).
Logger()

log.Log().Msg("hello world")

// Output: {"foo":"bar","bar":"baz","n":1,"message":"hello world"}
}
64 changes: 62 additions & 2 deletions log_test.go
Expand Up @@ -246,6 +246,66 @@ func TestFieldsMapNilPnt(t *testing.T) {
}
}

func TestFieldsSlice(t *testing.T) {
out := &bytes.Buffer{}
log := New(out)
log.Log().Fields([]interface{}{
"nil", nil,
"string", "foo",
"bytes", []byte("bar"),
"error", errors.New("some error"),
"bool", true,
"int", int(1),
"int8", int8(2),
"int16", int16(3),
"int32", int32(4),
"int64", int64(5),
"uint", uint(6),
"uint8", uint8(7),
"uint16", uint16(8),
"uint32", uint32(9),
"uint64", uint64(10),
"float32", float32(11),
"float64", float64(12),
"ipv6", net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34},
"dur", 1 * time.Second,
"time", time.Time{},
"obj", obj{"a", "b", 1},
}).Msg("")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"nil":null,"string":"foo","bytes":"bar","error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11,"float64":12,"ipv6":"2001:db8:85a3::8a2e:370:7334","dur":1000,"time":"0001-01-01T00:00:00Z","obj":{"Pub":"a","Tag":"b","priv":1}}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}

func TestFieldsSliceExtraneous(t *testing.T) {
out := &bytes.Buffer{}
log := New(out)
log.Log().Fields([]interface{}{
"string", "foo",
"error", errors.New("some error"),
32, "valueForNonStringKey",
"bool", true,
"int", int(1),
"keyWithoutValue",
}).Msg("")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":"foo","error":"some error","bool":true,"int":1}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}

func TestFieldsNotMapSlice(t *testing.T) {
out := &bytes.Buffer{}
log := New(out)
log.Log().
Fields(obj{"a", "b", 1}).
Fields("string").
Fields(1).
Msg("")
if got, want := decodeIfBinaryToString(out.Bytes()), `{}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}

func TestFields(t *testing.T) {
out := &bytes.Buffer{}
log := New(out)
Expand Down Expand Up @@ -336,7 +396,7 @@ func TestFieldsArraySingleElement(t *testing.T) {
Floats32("float32", []float32{11}).
Floats64("float64", []float64{12}).
Durs("dur", []time.Duration{1 * time.Second}).
Times("time", []time.Time{time.Time{}}).
Times("time", []time.Time{{}}).
Msg("")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":["foo"],"err":["some error"],"bool":[true],"int":[1],"int8":[2],"int16":[3],"int32":[4],"int64":[5],"uint":[6],"uint8":[7],"uint16":[8],"uint32":[9],"uint64":[10],"float32":[11],"float64":[12],"dur":[1000],"time":["0001-01-01T00:00:00Z"]}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
Expand All @@ -363,7 +423,7 @@ func TestFieldsArrayMultipleElement(t *testing.T) {
Floats32("float32", []float32{11, 0}).
Floats64("float64", []float64{12, 0}).
Durs("dur", []time.Duration{1 * time.Second, 0}).
Times("time", []time.Time{time.Time{}, time.Time{}}).
Times("time", []time.Time{{}, {}}).
Msg("")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":["foo","bar"],"err":["some error",null],"bool":[true,false],"int":[1,0],"int8":[2,0],"int16":[3,0],"int32":[4,0],"int64":[5,0],"uint":[6,0],"uint8":[7,0],"uint16":[8,0],"uint32":[9,0],"uint64":[10,0],"float32":[11,0],"float64":[12,0],"dur":[1000,0],"time":["0001-01-01T00:00:00Z","0001-01-01T00:00:00Z"]}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
Expand Down

0 comments on commit 65adfd8

Please sign in to comment.