Repository: rs/zerolog
Pull request #212 (2020-01-24 15:20:50)

Updated README.md to include missing field types
Updated README to reflect changes in the code - included new field types available, corrected typos.


Patch:
diff --git a/README.md b/README.md
index bd28c29..c690ed9 100644
--- a/README.md
+++ b/README.md
@@ -497,13 +497,17 @@ Some settings can be changed and will by applied to all loggers:
 
 ### Advanced Fields
 
-* `Err`: Takes an `error` and render it as a string using the `zerolog.ErrorFieldName` field name.
-* `Timestamp`: Insert a timestamp field with `zerolog.TimestampFieldName` field name and formatted using `zerolog.TimeFieldFormat`.
-* `Time`: Adds a field with the time formated with the `zerolog.TimeFieldFormat`.
-* `Dur`: Adds a field with a `time.Duration`.
+* `Err`: Takes an `error` and renders it as a string using the `zerolog.ErrorFieldName` field name.
+* `Timestamp`: Inserts a timestamp field with `zerolog.TimestampFieldName` field name, formatted using `zerolog.TimeFieldFormat`.
+* `Time`: Adds a field with time formatted with `zerolog.TimeFieldFormat`.
+* `Dur`: Adds a field with `time.Duration`.
 * `Dict`: Adds a sub-key/value as a field of the event.
+* `RawJSON`: Adds a field with an already encoded JSON (`[]byte`)
+* `Hex`: Adds a field with value formatted as a hexadecimal string (`[]byte`)
 * `Interface`: Uses reflection to marshal the type.
 
+Most fields are also available in the slice format (`Strs` for `[]string`, `Errs` for `[]error` etc.)
+
 ## Binary Encoding
 
 In addition to the default JSON encoding, `zerolog` can produce binary logs using [CBOR](http://cbor.io) encoding. The choice of encoding can be decided at compile time using the build tag `binary_log` as follows:
diff --git a/array.go b/array.go
index d88c497..0f7f53e 100644
--- a/array.go
+++ b/array.go
@@ -93,8 +93,7 @@ func (a *Array) RawJSON(val []byte) *Array {
 
 // Err serializes and appends the err to the array.
 func (a *Array) Err(err error) *Array {
-	marshaled := ErrorMarshalFunc(err)
-	switch m := marshaled.(type) {
+	switch m := ErrorMarshalFunc(err).(type) {
 	case LogObjectMarshaler:
 		e := newEvent(nil, 0)
 		e.buf = e.buf[:0]
@@ -102,7 +101,11 @@ func (a *Array) Err(err error) *Array {
 		a.buf = append(enc.AppendArrayDelim(a.buf), e.buf...)
 		putEvent(e)
 	case error:
-		a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), m.Error())
+		if m == nil || isNilValue(m) {
+			a.buf = enc.AppendNil(enc.AppendArrayDelim(a.buf))
+		} else {
+			a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), m.Error())
+		}
 	case string:
 		a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), m)
 	default:
diff --git a/context.go b/context.go
index 1815a08..27f0d9a 100644
--- a/context.go
+++ b/context.go
@@ -104,14 +104,17 @@ func (c Context) RawJSON(key string, b []byte) Context {
 
 // AnErr adds the field key with serialized err to the logger context.
 func (c Context) AnErr(key string, err error) Context {
-	marshaled := ErrorMarshalFunc(err)
-	switch m := marshaled.(type) {
+	switch m := ErrorMarshalFunc(err).(type) {
 	case nil:
 		return c
 	case LogObjectMarshaler:
 		return c.Object(key, m)
 	case error:
-		return c.Str(key, m.Error())
+		if m == nil || isNilValue(m) {
+			return c
+		} else {
+			return c.Str(key, m.Error())
+		}
 	case string:
 		return c.Str(key, m)
 	default:
@@ -124,12 +127,15 @@ func (c Context) AnErr(key string, err error) Context {
 func (c Context) Errs(key string, errs []error) Context {
 	arr := Arr()
 	for _, err := range errs {
-		marshaled := ErrorMarshalFunc(err)
-		switch m := marshaled.(type) {
+		switch m := ErrorMarshalFunc(err).(type) {
 		case LogObjectMarshaler:
 			arr = arr.Object(m)
 		case error:
-			arr = arr.Str(m.Error())
+			if m == nil || isNilValue(m) {
+				arr = arr.Interface(nil)
+			} else {
+				arr = arr.Str(m.Error())
+			}
 		case string:
 			arr = arr.Str(m)
 		default:
diff --git a/event.go b/event.go
index 1402835..224799c 100644
--- a/event.go
+++ b/event.go
@@ -280,7 +280,11 @@ func (e *Event) AnErr(key string, err error) *Event {
 	case LogObjectMarshaler:
 		return e.Object(key, m)
 	case error:
-		return e.Str(key, m.Error())
+		if m == nil || isNilValue(m) {
+			return e
+		} else {
+			return e.Str(key, m.Error())
+		}
 	case string:
 		return e.Str(key, m)
 	default:
@@ -330,7 +334,9 @@ func (e *Event) Err(err error) *Event {
 		case LogObjectMarshaler:
 			e.Object(ErrorStackFieldName, m)
 		case error:
-			e.Str(ErrorStackFieldName, m.Error())
+			if m != nil && !isNilValue(m) {
+				e.Str(ErrorStackFieldName, m.Error())
+			}
 		case string:
 			e.Str(ErrorStackFieldName, m)
 		default:
diff --git a/fields.go b/fields.go
index 6b62ecc..cf3c3e9 100644
--- a/fields.go
+++ b/fields.go
@@ -4,8 +4,13 @@ import (
 	"net"
 	"sort"
 	"time"
+	"unsafe"
 )
 
+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 {
@@ -29,8 +34,7 @@ func appendFields(dst []byte, fields map[string]interface{}) []byte {
 		case []byte:
 			dst = enc.AppendBytes(dst, val)
 		case error:
-			marshaled := ErrorMarshalFunc(val)
-			switch m := marshaled.(type) {
+			switch m := ErrorMarshalFunc(val).(type) {
 			case LogObjectMarshaler:
 				e := newEvent(nil, 0)
 				e.buf = e.buf[:0]
@@ -38,7 +42,11 @@ func appendFields(dst []byte, fields map[string]interface{}) []byte {
 				dst = append(dst, e.buf...)
 				putEvent(e)
 			case error:
-				dst = enc.AppendString(dst, m.Error())
+				if m == nil || isNilValue(m) {
+					dst = enc.AppendNil(dst)
+				} else {
+					dst = enc.AppendString(dst, m.Error())
+				}
 			case string:
 				dst = enc.AppendString(dst, m)
 			default:
@@ -47,8 +55,7 @@ func appendFields(dst []byte, fields map[string]interface{}) []byte {
 		case []error:
 			dst = enc.AppendArrayStart(dst)
 			for i, err := range val {
-				marshaled := ErrorMarshalFunc(err)
-				switch m := marshaled.(type) {
+				switch m := ErrorMarshalFunc(err).(type) {
 				case LogObjectMarshaler:
 					e := newEvent(nil, 0)
 					e.buf = e.buf[:0]
@@ -56,7 +63,11 @@ func appendFields(dst []byte, fields map[string]interface{}) []byte {
 					dst = append(dst, e.buf...)
 					putEvent(e)
 				case error:
-					dst = enc.AppendString(dst, m.Error())
+					if m == nil || isNilValue(m) {
+						dst = enc.AppendNil(dst)
+					} else {
+						dst = enc.AppendString(dst, m.Error())
+					}
 				case string:
 					dst = enc.AppendString(dst, m)
 				default:


Test patch:
diff --git a/event_test.go b/event_test.go
new file mode 100644
index 0000000..879eca1
--- /dev/null
+++ b/event_test.go
@@ -0,0 +1,39 @@
+// +build !binary_log
+
+package zerolog
+
+import (
+	"bytes"
+	"errors"
+	"strings"
+	"testing"
+)
+
+type nilError struct{}
+
+func (nilError) Error() string {
+	return ""
+}
+
+func TestEvent_AnErr(t *testing.T) {
+	tests := []struct {
+		name string
+		err  error
+		want string
+	}{
+		{"nil", nil, `{}`},
+		{"error", errors.New("test"), `{"err":"test"}`},
+		{"nil interface", func() *nilError { return nil }(), `{}`},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var buf bytes.Buffer
+			e := newEvent(levelWriterAdapter{&buf}, DebugLevel)
+			e.AnErr("err", tt.err)
+			_ = e.write()
+			if got, want := strings.TrimSpace(buf.String()), tt.want; got != want {
+				t.Errorf("Event.AnErr() = %v, want %v", got, want)
+			}
+		})
+	}
+}
diff --git a/journald/journald_test.go b/journald/journald_test.go
index d3624c1..3379fb0 100644
--- a/journald/journald_test.go
+++ b/journald/journald_test.go
@@ -1,14 +1,15 @@
-// +build !windows
+// +build linux
 
 package journald_test
 
 import (
 	"bytes"
-	"github.com/rs/zerolog"
 	"io"
 	"testing"
+
+	"github.com/rs/zerolog"
+	"github.com/rs/zerolog/journald"
 )
-import "github.com/rs/zerolog/journald"
 
 func ExampleNewJournalDWriter() {
 	log := zerolog.New(journald.NewJournalDWriter())