Repository: rs/zerolog
Pull request #241 (2020-06-17 09:47:57)

Add stringer support for sub-logger
Same than #185 but for `zerolog.Context`.

Enables the use of `stringer`for sub-loggers:

```go
	sublogger := log.With().
		Str("component", "foo").
		Stringer("ip", net.IP{127,0,0,1}).
		Logger()
	
	sublogger.Info().Msg("hello world")
```


Patch:
diff --git a/context.go b/context.go
index 27f0d9a..08c62fd 100644
--- a/context.go
+++ b/context.go
@@ -1,6 +1,7 @@
 package zerolog
 
 import (
+	"fmt"
 	"io/ioutil"
 	"math"
 	"net"
@@ -81,6 +82,17 @@ func (c Context) Strs(key string, vals []string) Context {
 	return c
 }
 
+// Stringer adds the field key with val.String() (or null if val is nil) to the logger context.
+func (c Context) Stringer(key string, val fmt.Stringer) Context {
+	if val != nil {
+		c.l.context = enc.AppendString(enc.AppendKey(c.l.context, key), val.String())
+		return c
+	}
+
+	c.l.context = enc.AppendInterface(enc.AppendKey(c.l.context, key), nil)
+	return c
+}
+
 // Bytes adds the field key with val as a []byte to the logger context.
 func (c Context) Bytes(key string, val []byte) Context {
 	c.l.context = enc.AppendBytes(enc.AppendKey(c.l.context, key), val)


Test patch:
diff --git a/benchmark_test.go b/benchmark_test.go
index 84e7370..7fc93e2 100644
--- a/benchmark_test.go
+++ b/benchmark_test.go
@@ -3,6 +3,7 @@ package zerolog
 import (
 	"errors"
 	"io/ioutil"
+	"net"
 	"testing"
 	"time"
 )
@@ -240,6 +241,7 @@ func BenchmarkContextFieldType(b *testing.B) {
 	ints := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
 	floats := []float64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
 	strings := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
+	stringer := net.IP{127, 0, 0, 1}
 	durations := []time.Duration{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
 	times := []time.Time{
 		time.Unix(0, 0),
@@ -307,6 +309,9 @@ func BenchmarkContextFieldType(b *testing.B) {
 		"Strs": func(c Context) Context {
 			return c.Strs("k", strings)
 		},
+		"Stringer": func(c Context) Context {
+			return c.Stringer("k", stringer)
+		},
 		"Err": func(c Context) Context {
 			return c.Err(errs[0])
 		},
diff --git a/log_test.go b/log_test.go
index 4a4740c..2129bb9 100644
--- a/log_test.go
+++ b/log_test.go
@@ -81,6 +81,8 @@ func TestWith(t *testing.T) {
 	out := &bytes.Buffer{}
 	ctx := New(out).With().
 		Str("string", "foo").
+		Stringer("stringer", net.IP{127, 0, 0, 1}).
+		Stringer("stringer_nil", nil).
 		Bytes("bytes", []byte("bar")).
 		Hex("hex", []byte{0x12, 0xef}).
 		RawJSON("json", []byte(`{"some":"json"}`)).
@@ -104,7 +106,7 @@ func TestWith(t *testing.T) {
 	caller := fmt.Sprintf("%s:%d", file, line+3)
 	log := ctx.Caller().Logger()
 	log.Log().Msg("")
-	if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":"foo","bytes":"bar","hex":"12ef","json":{"some":"json"},"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.101,"float64":12.30303,"time":"0001-01-01T00:00:00Z","caller":"`+caller+`"}`+"\n"; got != want {
+	if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","json":{"some":"json"},"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.101,"float64":12.30303,"time":"0001-01-01T00:00:00Z","caller":"`+caller+`"}`+"\n"; got != want {
 		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
 	}
 
@@ -118,7 +120,7 @@ func TestWith(t *testing.T) {
 	}()
 	// The above line is a little contrived, but the line above should be the line due
 	// to the extra frame skip.
-	if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":"foo","bytes":"bar","hex":"12ef","json":{"some":"json"},"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.101,"float64":12.30303,"time":"0001-01-01T00:00:00Z","caller":"`+caller+`"}`+"\n"; got != want {
+	if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","json":{"some":"json"},"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.101,"float64":12.30303,"time":"0001-01-01T00:00:00Z","caller":"`+caller+`"}`+"\n"; got != want {
 		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
 	}
 }