Repository: rs/zerolog
Pull request #360 (2021-09-17 17:41:34)

Add Stringers support

rs: Please add the cbor version too.
ramasauskas: @rs


rs: Why not using the new `enc.AppendStringer` in the existing `Event.Stringer`?
ramasauskas: @rs event now uses encoder's `AppendStringer`. 

Patch:
diff --git a/event.go b/event.go
index 6aaa618..b409955 100644
--- a/event.go
+++ b/event.go
@@ -257,18 +257,24 @@ func (e *Event) Strs(key string, vals []string) *Event {
 	return e
 }
 
-// Stringer adds the field key with val.String() (or null if val is nil) to the *Event context.
+// Stringer adds the field key with val.String() (or null if val is nil)
+// to the *Event context.
 func (e *Event) Stringer(key string, val fmt.Stringer) *Event {
 	if e == nil {
 		return e
 	}
+	e.buf = enc.AppendStringer(enc.AppendKey(e.buf, key), val)
+	return e
+}
 
-	if val != nil {
-		e.buf = enc.AppendString(enc.AppendKey(e.buf, key), val.String())
+// Stringers adds the field key with vals where each individual val
+// is used as val.String() (or null if val is empty) to the *Event
+// context.
+func (e *Event) Stringers(key string, vals []fmt.Stringer) *Event {
+	if e == nil {
 		return e
 	}
-
-	e.buf = enc.AppendInterface(enc.AppendKey(e.buf, key), nil)
+	e.buf = enc.AppendStringers(enc.AppendKey(e.buf, key), vals)
 	return e
 }
 
diff --git a/internal/cbor/string.go b/internal/cbor/string.go
index ff42afa..e7f90df 100644
--- a/internal/cbor/string.go
+++ b/internal/cbor/string.go
@@ -1,5 +1,7 @@
 package cbor
 
+import "fmt"
+
 // AppendStrings encodes and adds an array of strings to the dst byte array.
 func (e Encoder) AppendStrings(dst []byte, vals []string) []byte {
 	major := majorTypeArray
@@ -30,6 +32,31 @@ func (Encoder) AppendString(dst []byte, s string) []byte {
 	return append(dst, s...)
 }
 
+// AppendStringers encodes and adds an array of Stringer values
+// to the dst byte array.
+func (e Encoder) AppendStringers(dst []byte, vals []fmt.Stringer) []byte {
+	if len(vals) == 0 {
+		return e.AppendArrayEnd(e.AppendArrayStart(dst))
+	}
+	dst = e.AppendArrayStart(dst)
+	dst = e.AppendStringer(dst, vals[0])
+	if len(vals) > 1 {
+		for _, val := range vals[1:] {
+			dst = e.AppendStringer(dst, val)
+		}
+	}
+	return e.AppendArrayEnd(dst)
+}
+
+// AppendStringer encodes and adds the Stringer value to the dst
+// byte array.
+func (e Encoder) AppendStringer(dst []byte, val fmt.Stringer) []byte {
+	if val == nil {
+		return e.AppendNil(dst)
+	}
+	return e.AppendString(dst, val.String())
+}
+
 // AppendBytes encodes and adds an array of bytes to the dst byte array.
 func (Encoder) AppendBytes(dst, s []byte) []byte {
 	major := majorTypeByteString
diff --git a/internal/json/string.go b/internal/json/string.go
index 815906f..46698b5 100644
--- a/internal/json/string.go
+++ b/internal/json/string.go
@@ -1,6 +1,9 @@
 package json
 
-import "unicode/utf8"
+import (
+	"fmt"
+	"unicode/utf8"
+)
 
 const hex = "0123456789abcdef"
 
@@ -60,7 +63,32 @@ func (Encoder) AppendString(dst []byte, s string) []byte {
 	return append(dst, '"')
 }
 
-// appendStringComplex is used by appendString to take over an in
+// AppendStringers encodes the provided Stringer list to json and
+// appends the encoded Stringer list to the input byte slice.
+func (e Encoder) AppendStringers(dst []byte, vals []fmt.Stringer) []byte {
+	if len(vals) == 0 {
+		return append(dst, '[', ']')
+	}
+	dst = append(dst, '[')
+	dst = e.AppendStringer(dst, vals[0])
+	if len(vals) > 1 {
+		for _, val := range vals[1:] {
+			dst = e.AppendStringer(append(dst, ','), val)
+		}
+	}
+	return append(dst, ']')
+}
+
+// AppendStringer encodes the input Stringer to json and appends the
+// encoded Stringer value to the input byte slice.
+func (e Encoder) AppendStringer(dst []byte, val fmt.Stringer) []byte {
+	if val == nil {
+		return e.AppendInterface(dst, nil)
+	}
+	return e.AppendString(dst, val.String())
+}
+
+//// appendStringComplex is used by appendString to take over an in
 // progress JSON string encoding that encountered a character that needs
 // to be encoded.
 func appendStringComplex(dst []byte, s string, i int) []byte {


Test patch:
diff --git a/log_test.go b/log_test.go
index 8f49c8f..2ffd47e 100644
--- a/log_test.go
+++ b/log_test.go
@@ -354,6 +354,7 @@ func TestFieldsArrayEmpty(t *testing.T) {
 	log := New(out)
 	log.Log().
 		Strs("string", []string{}).
+		Stringers("stringer", []fmt.Stringer{}).
 		Errs("err", []error{}).
 		Bools("bool", []bool{}).
 		Ints("int", []int{}).
@@ -371,7 +372,7 @@ func TestFieldsArrayEmpty(t *testing.T) {
 		Durs("dur", []time.Duration{}).
 		Times("time", []time.Time{}).
 		Msg("")
-	if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":[],"err":[],"bool":[],"int":[],"int8":[],"int16":[],"int32":[],"int64":[],"uint":[],"uint8":[],"uint16":[],"uint32":[],"uint64":[],"float32":[],"float64":[],"dur":[],"time":[]}`+"\n"; got != want {
+	if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":[],"stringer":[],"err":[],"bool":[],"int":[],"int8":[],"int16":[],"int32":[],"int64":[],"uint":[],"uint8":[],"uint16":[],"uint32":[],"uint64":[],"float32":[],"float64":[],"dur":[],"time":[]}`+"\n"; got != want {
 		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
 	}
 }
@@ -381,6 +382,7 @@ func TestFieldsArraySingleElement(t *testing.T) {
 	log := New(out)
 	log.Log().
 		Strs("string", []string{"foo"}).
+		Stringers("stringer", []fmt.Stringer{net.IP{127, 0, 0, 1}}).
 		Errs("err", []error{errors.New("some error")}).
 		Bools("bool", []bool{true}).
 		Ints("int", []int{1}).
@@ -398,7 +400,7 @@ func TestFieldsArraySingleElement(t *testing.T) {
 		Durs("dur", []time.Duration{1 * time.Second}).
 		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 {
+	if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":["foo"],"stringer":["127.0.0.1"],"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)
 	}
 }
@@ -408,6 +410,7 @@ func TestFieldsArrayMultipleElement(t *testing.T) {
 	log := New(out)
 	log.Log().
 		Strs("string", []string{"foo", "bar"}).
+		Stringers("stringer", []fmt.Stringer{nil, net.IP{127, 0, 0, 1}}).
 		Errs("err", []error{errors.New("some error"), nil}).
 		Bools("bool", []bool{true, false}).
 		Ints("int", []int{1, 0}).
@@ -425,7 +428,7 @@ func TestFieldsArrayMultipleElement(t *testing.T) {
 		Durs("dur", []time.Duration{1 * time.Second, 0}).
 		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 {
+	if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":["foo","bar"],"stringer":[null,"127.0.0.1"],"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)
 	}
 }
@@ -436,6 +439,7 @@ func TestFieldsDisabled(t *testing.T) {
 	now := time.Now()
 	log.Debug().
 		Str("string", "foo").
+		Stringer("stringer", net.IP{127, 0, 0, 1}).
 		Bytes("bytes", []byte("bar")).
 		Hex("hex", []byte{0x12, 0xef}).
 		AnErr("some_err", nil).