1
0
mirror of https://github.com/rs/zerolog synced 2026-06-08 17:13:30 +00:00

Fix missing context.Context in Object/EmbedObject (#737)

Copies over event settings for ctx, hooks, and stack

- Fix missing context for Event.appendObjects passes event's stack, ctx, and hooks (ch).
- Add Array.Errs support.
- Context.Errs, Event.Errs,  use new Array.Errs()
- Array.Err handles nil correctly.
- Fix Event.Err code ignoring modified event in ErrorStackMarshaler handling.
- Fix Fields.appendFieldList when ErrorMarshalFunc returns a nil.
- Fields.appendFieldList does no longer adds the ErrorStackFieldName field when ErrorStackMarshaler returns nil.
- Don't call the ErrorMarshalFunc on nil errors.
- Removed unreachable code NOP in Context.Array
- Rewrite ErrorMarshalFunc testing.
- Made diode tests more stable.
- Log copy error in test.
- Address review comments
- Added CreateArray() and CreateDict() for both Context and Event and deprecate the Arr() and Dict().
Array now carries then stack/context/hooks so they are available to LogObjectMarshal of Array elements.
Added unit tests for error marshal that returns an interface{}
This commit is contained in:
Marc Brooks
2026-01-05 12:19:15 -06:00
committed by GitHub
parent 0d69d7537a
commit 0cf9361666
14 changed files with 595 additions and 577 deletions
+39 -5
View File
@@ -1,6 +1,7 @@
package zerolog
import (
"context"
"net"
"sync"
"time"
@@ -17,7 +18,10 @@ var arrayPool = &sync.Pool{
// Array is used to prepopulate an array of items
// which can be re-used to add to log messages.
type Array struct {
buf []byte
buf []byte
stack bool // enable error stack trace
ctx context.Context // Optional Go context
ch []Hook // hooks
}
func putArray(a *Array) {
@@ -31,13 +35,22 @@ func putArray(a *Array) {
if cap(a.buf) > maxSize {
return
}
a.stack = false
a.ctx = nil
a.ch = nil
arrayPool.Put(a)
}
// Arr creates an array to be added to an Event or Context.
// WARNING: This function is deprecated because it does not preserve
// the stack, hooks, and context from the parent event.
// Deprecated: Use Event.CreateArray or Context.CreateArray instead.
func Arr() *Array {
a := arrayPool.Get().(*Array)
a.buf = a.buf[:0]
a.stack = false
a.ctx = nil
a.ch = nil
return a
}
@@ -59,7 +72,7 @@ func (a *Array) write(dst []byte) []byte {
// Object marshals an object that implement the LogObjectMarshaler
// interface and appends it to the array.
func (a *Array) Object(obj LogObjectMarshaler) *Array {
a.buf = appendObject(enc.AppendArrayDelim(a.buf), obj)
a.buf = appendObject(enc.AppendArrayDelim(a.buf), obj, a.stack, a.ctx, a.ch)
return a
}
@@ -90,12 +103,12 @@ func (a *Array) RawJSON(val []byte) *Array {
// Err serializes and appends the err to the array.
func (a *Array) Err(err error) *Array {
switch m := ErrorMarshalFunc(err).(type) {
case nil:
a.buf = enc.AppendNil(enc.AppendArrayDelim(a.buf))
case LogObjectMarshaler:
a = a.Object(m)
case error:
if m == nil || isNilValue(m) {
a.buf = enc.AppendNil(enc.AppendArrayDelim(a.buf))
} else {
if !isNilValue(m) {
a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), m.Error())
}
case string:
@@ -107,6 +120,27 @@ func (a *Array) Err(err error) *Array {
return a
}
// Errs serializes and appends errors to the array.
func (a *Array) Errs(errs []error) *Array {
for _, err := range errs {
switch m := ErrorMarshalFunc(err).(type) {
case nil:
a = a.Interface(nil)
case LogObjectMarshaler:
a = a.Object(m)
case error:
if !isNilValue(m) {
a = a.Str(m.Error())
}
case string:
a = a.Str(m)
default:
a = a.Interface(m)
}
}
return a
}
// Bool appends the val as a bool to the array.
func (a *Array) Bool(b bool) *Array {
a.buf = enc.AppendBool(enc.AppendArrayDelim(a.buf), b)
+2 -2
View File
@@ -47,14 +47,14 @@ func TestArray(t *testing.T) {
).
Err(nil).
Err(fmt.Errorf("failure")).
Err(errorObjectMarshalerImpl{fmt.Errorf("oops")}).
Err(loggableError{fmt.Errorf("oops")}).
Object(logObjectMarshalerImpl{
name: "ZIT",
age: 22,
}).
Type(3.14)
want := `[true,1,2,3,4,5,6,7,8,9,10,11.98122,12.987654321,"a","b","1f",{"some":"json"},{"longer":[1111,2222,3333,4444,5555]},"0001-01-01T00:00:00Z","192.168.0.10","127.0.0.0/24","01:23:45:67:89:ab",{"Pub":"A","tag":"j"},{"name":"zot","age":-35},0,{"bar":"baz","n":1},null,"failure",{"error":"OOPS"},{"name":"zit","age":-22},"float64"]`
want := `[true,1,2,3,4,5,6,7,8,9,10,11.98122,12.987654321,"a","b","1f",{"some":"json"},{"longer":[1111,2222,3333,4444,5555]},"0001-01-01T00:00:00Z","192.168.0.10","127.0.0.0/24","01:23:45:67:89:ab",{"Pub":"A","tag":"j"},{"name":"zot","age":-35},0,{"bar":"baz","n":1},null,"failure",{"l":"OOPS"},{"name":"zit","age":-22},"float64"]`
if got := decodeObjectToStr(a.write([]byte{})); got != want {
t.Errorf("Array.write()\ngot: %s\nwant: %s", got, want)
}
+30 -26
View File
@@ -206,12 +206,13 @@ func ExampleEvent_Dict() {
dst := bytes.Buffer{}
log := New(&dst)
log.Log().
Str("foo", "bar").
Dict("dict", Dict().
Str("bar", "baz").
Int("n", 1),
).
e := log.Log().
Str("foo", "bar")
e.Dict("dict", e.CreateDict().
Str("bar", "baz").
Int("n", 1),
).
Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
@@ -243,12 +244,13 @@ func ExampleEvent_Array() {
dst := bytes.Buffer{}
log := New(&dst)
log.Log().
Str("foo", "bar").
Array("array", Arr().
Str("baz").
Int(1),
).
e := log.Log().
Str("foo", "bar")
e.Array("array", e.CreateArray().
Str("baz").
Int(1),
).
Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
@@ -414,14 +416,15 @@ func ExampleEvent_Fields_slice() {
func ExampleContext_Dict() {
dst := bytes.Buffer{}
log := New(&dst).With().
Str("foo", "bar").
Dict("dict", Dict().
Str("bar", "baz").
Int("n", 1),
).Logger()
ctx := New(&dst).With().
Str("foo", "bar")
log.Log().Msg("hello world")
logger := ctx.Dict("dict", ctx.CreateDict().
Str("bar", "baz").
Int("n", 1),
).Logger()
logger.Log().Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","dict":{"bar":"baz","n":1},"message":"hello world"}
@@ -429,14 +432,15 @@ func ExampleContext_Dict() {
func ExampleContext_Array() {
dst := bytes.Buffer{}
log := New(&dst).With().
Str("foo", "bar").
Array("array", Arr().
Str("baz").
Int(1),
).Logger()
ctx := New(&dst).With().
Str("foo", "bar")
log.Log().Msg("hello world")
logger := ctx.Array("array", ctx.CreateArray().
Str("baz").
Int(1),
).Logger()
logger.Log().Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","array":["baz",1],"message":"hello world"}
+33 -37
View File
@@ -3,7 +3,6 @@ package zerolog
import (
"context"
"fmt"
"io"
"math"
"net"
"time"
@@ -23,7 +22,7 @@ func (c Context) Logger() Logger {
// 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, c.l.stack)
c.l.context = appendFields(c.l.context, fields, c.l.stack, c.l.ctx, c.l.hooks)
return c
}
@@ -35,8 +34,28 @@ func (c Context) Dict(key string, dict *Event) Context {
return c
}
// CreateDict creates an Event to be used with the Context.Dict method.
// It preserves the stack, hooks, and context from the logger.
// Call usual field methods like Str, Int etc to add fields to this
// event and give it as argument the Context.Dict method.
func (c Context) CreateDict() *Event {
return newEvent(nil, DebugLevel, c.l.stack, c.l.ctx, c.l.hooks)
}
// CreateArray creates an Array to be used with the Context.Array method.
// It preserves the stack, hooks, and context from the logger.
// Call usual field methods like Str, Int etc to add elements to this
// array and give it as argument the Context.Array method.
func (c Context) CreateArray() *Array {
a := Arr()
a.stack = c.l.stack
a.ctx = c.l.ctx
a.ch = c.l.hooks
return a
}
// Array adds the field key with an array to the event context.
// Use zerolog.Arr() to create the array or pass a type that
// Use c.CreateArray() to create the array or pass a type that
// implement the LogArrayMarshaler interface.
func (c Context) Array(key string, arr LogArrayMarshaler) Context {
c.l.context = enc.AppendKey(c.l.context, key)
@@ -44,20 +63,15 @@ func (c Context) Array(key string, arr LogArrayMarshaler) Context {
c.l.context = arr.write(c.l.context)
return c
}
var a *Array
if aa, ok := arr.(*Array); ok {
a = aa
} else {
a = Arr()
arr.MarshalZerologArray(a)
}
a := c.CreateArray()
arr.MarshalZerologArray(a)
c.l.context = a.write(c.l.context)
return c
}
// Object marshals an object that implement the LogObjectMarshaler interface.
func (c Context) Object(key string, obj LogObjectMarshaler) Context {
e := newEvent(LevelWriterAdapter{io.Discard}, 0)
e := c.l.scratchEvent()
e.Object(key, obj)
c.l.context = enc.AppendObjectData(c.l.context, e.buf)
putEvent(e)
@@ -66,7 +80,7 @@ func (c Context) Object(key string, obj LogObjectMarshaler) Context {
// Object marshals an object that implement the LogObjectMarshaler interface.
func (c Context) Objects(key string, objs []LogObjectMarshaler) Context {
e := newEvent(LevelWriterAdapter{io.Discard}, 0)
e := c.l.scratchEvent()
e.Objects(key, objs)
c.l.context = enc.AppendObjectData(c.l.context, e.buf)
putEvent(e)
@@ -75,7 +89,7 @@ func (c Context) Objects(key string, objs []LogObjectMarshaler) Context {
// EmbedObject marshals and Embeds an object that implement the LogObjectMarshaler interface.
func (c Context) EmbedObject(obj LogObjectMarshaler) Context {
e := newEvent(LevelWriterAdapter{io.Discard}, 0)
e := c.l.scratchEvent()
e.EmbedObject(obj)
c.l.context = enc.AppendObjectData(c.l.context, e.buf)
putEvent(e)
@@ -139,6 +153,7 @@ func (c Context) RawJSON(key string, b []byte) Context {
}
// AnErr adds the field key with serialized err to the logger context.
// If err is nil, no field is added.
func (c Context) AnErr(key string, err error) Context {
switch m := ErrorMarshalFunc(err).(type) {
case nil:
@@ -146,11 +161,10 @@ func (c Context) AnErr(key string, err error) Context {
case LogObjectMarshaler:
return c.Object(key, m)
case error:
if m == nil || isNilValue(m) {
if isNilValue(m) {
return c
} else {
return c.Str(key, m.Error())
}
return c.Str(key, m.Error())
case string:
return c.Str(key, m)
default:
@@ -161,24 +175,7 @@ func (c Context) AnErr(key string, err error) Context {
// Errs adds the field key with errs as an array of serialized errors to the
// logger context.
func (c Context) Errs(key string, errs []error) Context {
arr := Arr()
for _, err := range errs {
switch m := ErrorMarshalFunc(err).(type) {
case LogObjectMarshaler:
arr = arr.Object(m)
case error:
if m == nil || isNilValue(m) {
arr = arr.Interface(nil)
} else {
arr = arr.Str(m.Error())
}
case string:
arr = arr.Str(m)
default:
arr = arr.Interface(m)
}
}
arr := c.CreateArray().Errs(errs)
return c.Array(key, arr)
}
@@ -187,12 +184,11 @@ func (c Context) Err(err error) Context {
if c.l.stack && ErrorStackMarshaler != nil {
switch m := ErrorStackMarshaler(err).(type) {
case nil:
// do nothing
case LogObjectMarshaler:
c = c.Object(ErrorStackFieldName, m)
case error:
if m != nil && !isNilValue(m) {
c = c.Str(ErrorStackFieldName, m.Error())
}
c = c.Str(ErrorStackFieldName, m.Error())
case string:
c = c.Str(ErrorStackFieldName, m)
default:
-8
View File
@@ -82,14 +82,6 @@ func (t logObjectMarshalerImpl) MarshalZerologObject(e *Event) {
e.Str("name", strings.ToLower(t.name)).Int("age", -t.age)
}
type errorObjectMarshalerImpl struct {
error
}
func (t errorObjectMarshalerImpl) MarshalZerologObject(e *Event) {
e.Str("error", strings.ToUpper(t.error.Error()))
}
func Test_InterfaceLogObjectMarshaler(t *testing.T) {
var buf bytes.Buffer
log := New(&buf)
+27 -8
View File
@@ -7,6 +7,7 @@ import (
"log"
"os"
"os/exec"
"sync"
"testing"
"time"
@@ -60,15 +61,25 @@ func TestFatal(t *testing.T) {
if err != nil {
t.Fatal(err)
}
slurp, err := io.ReadAll(stderr)
if err != nil {
t.Fatal(err)
}
var stderrBuf bytes.Buffer
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
if _, err := io.Copy(&stderrBuf, stderr); err != nil {
t.Errorf("failed to copy stderr: %v", err)
}
}()
err = cmd.Wait()
if err == nil {
t.Error("Expected log.Fatal to exit with non-zero status")
}
wg.Wait() // Wait for the goroutine to finish copying
slurp := stderrBuf.Bytes()
want := "{\"level\":\"fatal\",\"message\":\"test\"}\n"
got := cbor.DecodeIfBinaryToString(slurp)
if got != want {
@@ -112,15 +123,23 @@ func TestFatalWithFilteredLevelWriter(t *testing.T) {
if err != nil {
t.Fatal(err)
}
slurp, err := io.ReadAll(stdout)
if err != nil {
t.Fatal(err)
}
var stdoutBuf bytes.Buffer
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
_, _ = io.Copy(&stdoutBuf, stdout)
}()
err = cmd.Wait()
if err == nil {
t.Error("Expected log.Fatal to exit with non-zero status")
}
wg.Wait() // Wait for the goroutine to finish copying
slurp := stdoutBuf.Bytes()
got := cbor.DecodeIfBinaryToString(slurp)
want := "{\"level\":\"fatal\",\"message\":\"test\"}\n"
if got != want {
+224
View File
@@ -0,0 +1,224 @@
package zerolog
import (
"bytes"
"fmt"
"strings"
"testing"
)
type loggableError struct {
error
}
func (l loggableError) MarshalZerologObject(e *Event) {
if l.error == nil {
return
}
e.Str("l", strings.ToUpper(l.error.Error()))
}
type nonLoggableError struct {
error
line int
}
type wrappedError struct {
error
msg string
}
func (w wrappedError) Error() string {
if w.error == nil {
return w.msg
}
return w.error.Error() + ": " + w.msg
}
type interfaceError struct {
val string
}
func TestArrayErrorMarshalFunc(t *testing.T) {
prefixed := func(s, prefix string) string {
if s == "null" {
return ""
}
return prefix + s + `,`
}
errs := []error{
nil,
fmt.Errorf("failure"),
loggableError{fmt.Errorf("whoops")},
nonLoggableError{fmt.Errorf("oops"), 402},
}
type testCase struct {
name string
marshal func(err error) interface{}
want []string
}
testCases := []testCase{
{
name: "default",
marshal: nil,
want: []string{`null`, `"failure"`, `{"l":"WHOOPS"}`, `"oops"`},
},
{
name: "string",
marshal: func(err error) interface{} {
if err == nil {
return nil
}
return err.Error()
},
want: []string{`null`, `"failure"`, `"whoops"`, `"oops"`},
},
{
name: "loggable",
marshal: func(err error) interface{} {
if err == nil {
return nil
}
return loggableError{err}
},
want: []string{`null`, `{"l":"FAILURE"}`, `{"l":"WHOOPS"}`, `{"l":"OOPS"}`},
},
{
name: "non-loggable",
marshal: func(err error) interface{} {
if err == nil {
return nil
}
return nonLoggableError{err, 404}
},
want: []string{`null`, `"failure"`, `"whoops"`, `"oops"`},
},
{
name: "interface",
marshal: func(err error) interface{} {
var some interfaceError
if err != nil {
some.val = err.Error()
}
var interfaceErr interface{} = some
return interfaceErr
},
want: []string{`{}`, `{}`, `{}`, `{}`},
},
{
name: "nilError",
marshal: func(err error) interface{} {
var errNil error = nil
return errNil
},
want: []string{`null`, `null`, `null`, `null`},
},
{
name: "wrapped error",
marshal: func(err error) interface{} {
if err == nil {
return nil
} else if we, ok := err.(wrappedError); ok {
return we
} else {
return wrappedError{err, "addendum"}
}
},
want: []string{`null`, `"failure: addendum"`, `"whoops: addendum"`, `"oops: addendum"`},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
originalErrorMarshalFunc := ErrorMarshalFunc
defer func() {
ErrorMarshalFunc = originalErrorMarshalFunc
}()
if tc.marshal != nil {
ErrorMarshalFunc = tc.marshal
}
t.Run("Err", func(t *testing.T) {
for i, err := range errs {
want := tc.want[i]
t.Run("Arr", func(t *testing.T) {
wants := `[` + want + `]`
a := Arr().Err(err)
if got := decodeObjectToStr(a.write([]byte{})); got != wants {
t.Errorf("%s %d Array.Err(%v)\ngot: %s\nwant: %s", tc.name, i, err, got, wants)
}
})
t.Run("Ctx", func(t *testing.T) {
wants := `{` + prefixed(want, `"error":`) + `"message":"msg"}` + "\n"
out := &bytes.Buffer{}
logger := New(out).With().Err(err).Logger()
logger.Log().Msg("msg")
if got := decodeIfBinaryToString(out.Bytes()); got != wants {
t.Errorf("%s %d Ctx.Err(%v)\ngot: %v\nwant: %v", tc.name, i, err, got, wants)
}
})
t.Run("Event", func(t *testing.T) {
wants := `{` + prefixed(want, `"error":`) + `"message":"msg"}` + "\n"
out := &bytes.Buffer{}
logger := New(out)
logger.Log().Err(err).Msg("msg")
if got := decodeIfBinaryToString(out.Bytes()); got != wants {
t.Errorf("%s %d Event.Err(%v)\ngot: %v\nwant: %v", tc.name, i, err, got, wants)
}
})
t.Run("Fields", func(t *testing.T) {
if i == 0 && tc.want[i] == "{}" {
want = `null`
}
wants := `{"err":` + want + `,"message":"msg"}` + "\n"
out := &bytes.Buffer{}
logger := New(out)
logger.Log().Fields(map[string]interface{}{"err": err}).Msg("msg")
if got := decodeIfBinaryToString(out.Bytes()); got != wants {
t.Errorf("%s %d Event.Fields(%v)\ngot: %v\nwant: %v", tc.name, i, err, got, wants)
}
})
}
})
t.Run("Errs", func(t *testing.T) {
want := `[` + strings.Join(tc.want, ",") + `]`
t.Run("Arr", func(t *testing.T) {
a := Arr().Errs(errs)
if got := decodeObjectToStr(a.write([]byte{})); got != want {
t.Errorf("%s Array.Errs()\ngot: %s\nwant: %s", tc.name, got, want)
}
})
t.Run("Ctx", func(t *testing.T) {
wants := `{"e":` + want + `,"message":"msg"}` + "\n"
out := &bytes.Buffer{}
logger := New(out).With().Errs("e", errs).Logger()
logger.Log().Msg("msg")
if got := decodeIfBinaryToString(out.Bytes()); got != wants {
t.Errorf("%s Ctx.Errs()\ngot: %v\nwant: %v", tc.name, got, wants)
}
})
t.Run("Event", func(t *testing.T) {
wants := `{"e":` + want + `,"message":"msg"}` + "\n"
out := &bytes.Buffer{}
logger := New(out)
logger.Log().Errs("e", errs).Msg("msg")
if got := decodeIfBinaryToString(out.Bytes()); got != wants {
t.Errorf("%s Ctx.Errs()\ngot: %v\nwant: %v", tc.name, got, wants)
}
})
t.Run("Fields", func(t *testing.T) {
wants := `{"e":` + want + `,"message":"msg"}` + "\n"
out := &bytes.Buffer{}
logger := New(out)
logger.Log().Fields(map[string]interface{}{"e": errs}).Msg("msg")
if got := decodeIfBinaryToString(out.Bytes()); got != wants {
t.Errorf("%s Ctx.Errs()\ngot: %v\nwant: %v", tc.name, got, wants)
}
})
})
})
}
}
+48 -32
View File
@@ -57,14 +57,15 @@ type LogArrayMarshaler interface {
MarshalZerologArray(a *Array)
}
func newEvent(w LevelWriter, level Level) *Event {
func newEvent(w LevelWriter, level Level, stack bool, ctx context.Context, hooks []Hook) *Event {
e := eventPool.Get().(*Event)
e.buf = e.buf[:0]
e.ch = nil
e.stack = stack
e.ctx = ctx
e.ch = hooks
e.buf = enc.AppendBeginMarker(e.buf)
e.w = w
e.level = level
e.stack = false
e.skipFrame = 0
return e
}
@@ -164,12 +165,12 @@ func (e *Event) Fields(fields interface{}) *Event {
if e == nil {
return e
}
e.buf = appendFields(e.buf, fields, e.stack)
e.buf = appendFields(e.buf, fields, e.stack, e.ctx, e.ch)
return e
}
// Dict adds the field key with a dict to the event context.
// Use zerolog.Dict() to create the dictionary.
// Use e.CreateDict() to create the dictionary.
func (e *Event) Dict(key string, dict *Event) *Event {
if e == nil {
return e
@@ -180,15 +181,43 @@ func (e *Event) Dict(key string, dict *Event) *Event {
return e
}
// CreateDict creates an Event to be used with the *Event.Dict method.
// It preserves the stack, hooks, and context from the parent event.
// Call usual field methods like Str, Int etc to add fields to this
// event and give it as argument the *Event.Dict method.
func (e *Event) CreateDict() *Event {
if e == nil {
return newEvent(nil, DebugLevel, false, nil, nil)
}
return newEvent(nil, DebugLevel, e.stack, e.ctx, e.ch)
}
// Dict creates an Event to be used with the *Event.Dict method.
// Call usual field methods like Str, Int etc to add fields to this
// event and give it as argument the *Event.Dict method.
// NOTE: This function is deprecated because it does not preserve
// the stack, hooks, and context from the parent event.
// Deprecated: Use Event.CreateDict instead.
func Dict() *Event {
return newEvent(nil, 0)
return newEvent(nil, DebugLevel, false, nil, nil)
}
// CreateArray creates an Array to be used with the *Event.Array method.
// It preserves the stack, hooks, and context from the parent event.
// Call usual field methods like Str, Int etc to add elements to this
// array and give it as argument the *Event.Array method.
func (e *Event) CreateArray() *Array {
a := Arr()
if e != nil {
a.stack = e.stack
a.ctx = e.ctx
a.ch = e.ch
}
return a
}
// Array adds the field key with an array to the event context.
// Use zerolog.Arr() to create the array or pass a type that
// Use e.CreateArray() to create the array or pass a type that
// implement the LogArrayMarshaler interface.
func (e *Event) Array(key string, arr LogArrayMarshaler) *Event {
if e == nil {
@@ -199,7 +228,7 @@ func (e *Event) Array(key string, arr LogArrayMarshaler) *Event {
if aa, ok := arr.(*Array); ok {
a = aa
} else {
a = Arr()
a = e.CreateArray()
arr.MarshalZerologArray(a)
}
e.buf = a.write(e.buf)
@@ -235,7 +264,7 @@ func (e *Event) Objects(key string, objs []LogObjectMarshaler) *Event {
}
e.buf = enc.AppendArrayStart(enc.AppendKey(e.buf, key))
for i, obj := range objs {
e.buf = appendObject(e.buf, obj)
e.buf = appendObject(e.buf, obj, e.stack, e.ctx, e.ch)
if i < (len(objs) - 1) {
e.buf = enc.AppendArrayDelim(e.buf)
}
@@ -360,11 +389,10 @@ func (e *Event) AnErr(key string, err error) *Event {
case LogObjectMarshaler:
return e.Object(key, m)
case error:
if m == nil || isNilValue(m) {
if isNilValue(m) {
return e
} else {
return e.Str(key, m.Error())
}
return e.Str(key, m.Error())
case string:
return e.Str(key, m)
default:
@@ -378,20 +406,7 @@ func (e *Event) Errs(key string, errs []error) *Event {
if e == nil {
return e
}
arr := Arr()
for _, err := range errs {
switch m := ErrorMarshalFunc(err).(type) {
case LogObjectMarshaler:
arr = arr.Object(m)
case error:
arr = arr.Err(m)
case string:
arr = arr.Str(m)
default:
arr = arr.Interface(m)
}
}
arr := e.CreateArray().Errs(errs)
return e.Array(key, arr)
}
@@ -407,21 +422,22 @@ func (e *Event) Err(err error) *Event {
if e == nil {
return e
}
if e.stack && ErrorStackMarshaler != nil {
switch m := ErrorStackMarshaler(err).(type) {
case nil:
// do nothing
case LogObjectMarshaler:
e.Object(ErrorStackFieldName, m)
e = e.Object(ErrorStackFieldName, m)
case error:
if m != nil && !isNilValue(m) {
e.Str(ErrorStackFieldName, m.Error())
}
e = e.Str(ErrorStackFieldName, m.Error())
case string:
e.Str(ErrorStackFieldName, m)
e = e.Str(ErrorStackFieldName, m)
default:
e.Interface(ErrorStackFieldName, m)
e = e.Interface(ErrorStackFieldName, m)
}
}
return e.AnErr(ErrorFieldName, err)
}
+110 -30
View File
@@ -1,9 +1,11 @@
//go:build !binary_log
// +build !binary_log
package zerolog
import (
"bytes"
"context"
"errors"
"io"
"os"
@@ -14,7 +16,7 @@ import (
type nilError struct{}
func (nilError) Error() string {
return ""
return "nope"
}
func TestEvent_AnErr(t *testing.T) {
@@ -30,9 +32,13 @@ func TestEvent_AnErr(t *testing.T) {
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()
e := newEvent(LevelWriterAdapter{&buf}, DebugLevel, false, nil, nil)
e = e.AnErr("err", tt.err)
err := e.write()
if err != nil {
t.Errorf("Event.AnErr() error: %v", err)
}
if got, want := strings.TrimSpace(buf.String()), tt.want; got != want {
t.Errorf("Event.AnErr() = %v, want %v", got, want)
}
@@ -50,30 +56,104 @@ func TestEvent_writeWithNil(t *testing.T) {
}
}
func TestEvent_ObjectWithNil(t *testing.T) {
var buf bytes.Buffer
e := newEvent(LevelWriterAdapter{&buf}, DebugLevel)
_ = e.Object("obj", nil)
_ = e.write()
want := `{"obj":null}`
got := strings.TrimSpace(buf.String())
if got != want {
t.Errorf("Event.Object() = %q, want %q", got, want)
}
type loggableObject struct {
member string
}
func TestEvent_EmbedObjectWithNil(t *testing.T) {
var buf bytes.Buffer
e := newEvent(LevelWriterAdapter{&buf}, DebugLevel)
_ = e.EmbedObject(nil)
_ = e.write()
func (o loggableObject) MarshalZerologObject(e *Event) {
e.Str("member", o.member)
}
want := "{}"
got := strings.TrimSpace(buf.String())
if got != want {
t.Errorf("Event.EmbedObject() = %q, want %q", got, want)
}
func TestEvent_Object(t *testing.T) {
t.Run("ObjectWithNil", func(t *testing.T) {
var buf bytes.Buffer
e := newEvent(LevelWriterAdapter{&buf}, DebugLevel, false, nil, nil)
e = e.Object("obj", nil)
err := e.write()
if err != nil {
t.Errorf("Event.Object() error: %v", err)
}
want := `{"obj":null}`
got := strings.TrimSpace(buf.String())
if got != want {
t.Errorf("Event.Object()\ngot: %s\nwant: %s", got, want)
}
})
t.Run("EmbedObjectWithNil", func(t *testing.T) {
var buf bytes.Buffer
e := newEvent(LevelWriterAdapter{&buf}, DebugLevel, false, nil, nil)
e = e.EmbedObject(nil)
err := e.write()
if err != nil {
t.Errorf("Event.EmbedObject() error: %v", err)
}
want := "{}"
got := strings.TrimSpace(buf.String())
if got != want {
t.Errorf("Event.EmbedObject()\ngot: %s\nwant: %s", got, want)
}
})
type contextKeyType struct{}
var contextKey = contextKeyType{}
called := false
ctxHook := HookFunc(func(e *Event, level Level, message string) {
called = true
ctx := e.GetCtx()
if ctx == nil {
t.Errorf("expected context to be set in Event")
}
val := ctx.Value(contextKey)
if val == nil {
t.Errorf("expected context value, got %v", val)
}
e.Str("ctxValue", val.(string))
e.Bool("stackValue", e.stack)
})
t.Run("ObjectWithFullContext", func(t *testing.T) {
called = false
ctx := context.WithValue(context.Background(), contextKey, "ctx-object")
var buf bytes.Buffer
e := newEvent(LevelWriterAdapter{&buf}, DebugLevel, true, ctx, []Hook{ctxHook})
e = e.Object("obj", loggableObject{member: "object-value"})
e.Msg("hello")
if !called {
t.Errorf("hook was not called")
}
want := `{"obj":{"member":"object-value"},"ctxValue":"ctx-object","stackValue":true,"message":"hello"}`
got := strings.TrimSpace(buf.String())
if got != want {
t.Errorf("Event.EmbedObject()\ngot: %s\nwant: %s", got, want)
}
})
t.Run("EmbedObjectWithFullContext", func(t *testing.T) {
called = false
ctx := context.WithValue(context.Background(), contextKey, "ctx-embed")
var buf bytes.Buffer
e := newEvent(LevelWriterAdapter{&buf}, DebugLevel, false, ctx, []Hook{ctxHook})
e = e.EmbedObject(loggableObject{member: "embedded-value"})
e.Msg("hello")
if !called {
t.Errorf("hook was not called")
}
want := `{"member":"embedded-value","ctxValue":"ctx-embed","stackValue":false,"message":"hello"}`
got := strings.TrimSpace(buf.String())
if got != want {
t.Errorf("Event.EmbedObject()\ngot: %s\nwant: %s", got, want)
}
})
}
func TestEvent_WithNilEvent(t *testing.T) {
@@ -83,7 +163,7 @@ func TestEvent_WithNilEvent(t *testing.T) {
fixtures := makeFieldFixtures()
types := map[string]func() *Event{
"Array": func() *Event {
arr := Arr()
arr := e.CreateArray()
return e.Array("k", arr)
},
"Bool": func() *Event {
@@ -198,7 +278,7 @@ func TestEvent_WithNilEvent(t *testing.T) {
return e.Times("k", fixtures.Times)
},
"Dict": func() *Event {
d := Dict()
d := e.CreateDict()
d.Str("greeting", "hello")
return e.Dict("k", d)
},
@@ -289,7 +369,7 @@ func TestEvent_WithNilEvent(t *testing.T) {
func TestEvent_MsgFunc(t *testing.T) {
var buf bytes.Buffer
e := newEvent(LevelWriterAdapter{&buf}, DebugLevel)
e := newEvent(LevelWriterAdapter{&buf}, DebugLevel, false, nil, nil)
called := false
e.MsgFunc(func() string {
@@ -308,7 +388,7 @@ func TestEvent_MsgFunc(t *testing.T) {
}
func TestEvent_DoneHandler(t *testing.T) {
e := newEvent(nil, InfoLevel)
e := newEvent(nil, InfoLevel, false, nil, nil)
// Set up a done handler to capture calls
var called bool
@@ -351,7 +431,7 @@ func TestEvent_Msg_ErrorHandlerNil(t *testing.T) {
// Create a LevelWriter that always returns an error
mockWriter := &badLevelWriter{err: errors.New("write error")}
e := newEvent(mockWriter, InfoLevel)
e := newEvent(mockWriter, InfoLevel, false, nil, nil)
if e == nil {
t.Fatal("Event should not be nil")
}
+30 -24
View File
@@ -1,7 +1,9 @@
package zerolog
import (
"context"
"encoding/json"
"io"
"net"
"sort"
"time"
@@ -12,13 +14,13 @@ func isNilValue(i interface{}) bool {
return (*[2]uintptr)(unsafe.Pointer(&i))[1] == 0
}
func appendFields(dst []byte, fields interface{}, stack bool) []byte {
func appendFields(dst []byte, fields interface{}, stack bool, ctx context.Context, hooks []Hook) []byte {
switch fields := fields.(type) {
case []interface{}:
if n := len(fields); n&0x1 == 1 { // odd number
fields = fields[:n-1]
}
dst = appendFieldList(dst, fields, stack)
dst = appendFieldList(dst, fields, stack, ctx, hooks)
case map[string]interface{}:
keys := make([]string, 0, len(fields))
for key := range fields {
@@ -28,14 +30,14 @@ func appendFields(dst []byte, fields interface{}, stack bool) []byte {
kv := make([]interface{}, 2)
for _, key := range keys {
kv[0], kv[1] = key, fields[key]
dst = appendFieldList(dst, kv, stack)
dst = appendFieldList(dst, kv, stack, ctx, hooks)
}
}
return dst
}
func appendObject(dst []byte, obj LogObjectMarshaler) []byte {
e := newEvent(nil, 0)
func appendObject(dst []byte, obj LogObjectMarshaler, stack bool, ctx context.Context, hooks []Hook) []byte {
e := newEvent(LevelWriterAdapter{io.Discard}, DebugLevel, stack, ctx, hooks)
e.buf = e.buf[:0] // discard the beginning marker added by newEvent
e.appendObject(obj)
dst = append(dst, e.buf...)
@@ -43,7 +45,7 @@ func appendObject(dst []byte, obj LogObjectMarshaler) []byte {
return dst
}
func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
func appendFieldList(dst []byte, kvList []interface{}, stack bool, ctx context.Context, hooks []Hook) []byte {
for i, n := 0, len(kvList); i < n; i += 2 {
key, val := kvList[i], kvList[i+1]
if key, ok := key.(string); ok {
@@ -51,10 +53,6 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
} else {
continue
}
if val, ok := val.(LogObjectMarshaler); ok {
dst = appendObject(dst, val)
continue
}
switch val := val.(type) {
case string:
dst = enc.AppendString(dst, val)
@@ -62,12 +60,12 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
dst = enc.AppendBytes(dst, val)
case error:
switch m := ErrorMarshalFunc(val).(type) {
case nil:
dst = enc.AppendNil(dst)
case LogObjectMarshaler:
dst = appendObject(dst, m)
dst = appendObject(dst, m, stack, ctx, hooks)
case error:
if m == nil || isNilValue(m) {
dst = enc.AppendNil(dst)
} else {
if !isNilValue(m) {
dst = enc.AppendString(dst, m.Error())
}
case string:
@@ -77,16 +75,20 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
}
if stack && ErrorStackMarshaler != nil {
dst = enc.AppendKey(dst, ErrorStackFieldName)
switch m := ErrorStackMarshaler(val).(type) {
case nil:
// do nothing
case LogObjectMarshaler:
dst = enc.AppendKey(dst, ErrorStackFieldName)
dst = appendObject(dst, m, stack, ctx, hooks)
case error:
if m != nil && !isNilValue(m) {
dst = enc.AppendString(dst, m.Error())
}
dst = enc.AppendKey(dst, ErrorStackFieldName)
dst = enc.AppendString(dst, m.Error())
case string:
dst = enc.AppendKey(dst, ErrorStackFieldName)
dst = enc.AppendString(dst, m)
default:
dst = enc.AppendKey(dst, ErrorStackFieldName)
dst = enc.AppendInterface(dst, m)
}
}
@@ -94,12 +96,12 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
dst = enc.AppendArrayStart(dst)
for i, err := range val {
switch m := ErrorMarshalFunc(err).(type) {
case nil:
dst = enc.AppendNil(dst)
case LogObjectMarshaler:
dst = appendObject(dst, m)
dst = appendObject(dst, m, stack, ctx, hooks)
case error:
if m == nil || isNilValue(m) {
dst = enc.AppendNil(dst)
} else {
if !isNilValue(m) {
dst = enc.AppendString(dst, m.Error())
}
case string:
@@ -116,7 +118,7 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
case []LogObjectMarshaler:
dst = enc.AppendArrayStart(dst)
for i, obj := range val {
dst = appendObject(dst, obj)
dst = appendObject(dst, obj, stack, ctx, hooks)
if i < (len(val) - 1) {
dst = enc.AppendArrayDelim(dst)
}
@@ -294,7 +296,11 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
case json.RawMessage:
dst = appendJSON(dst, val)
default:
dst = enc.AppendInterface(dst, val)
if lom, ok := val.(LogObjectMarshaler); ok {
dst = appendObject(dst, lom, stack, ctx, hooks)
} else {
dst = enc.AppendInterface(dst, val)
}
}
}
return dst
+1 -1
View File
@@ -120,7 +120,7 @@ func makeFieldFixtures() *fieldFixtures {
ipPfxV6 := net.IPNet{IP: net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x00}, Mask: net.CIDRMask(64, 128)}
ipPfxs := []net.IPNet{ipPfxV4, ipPfxV6, ipPfxV4, ipPfxV6, ipPfxV4, ipPfxV6, ipPfxV4, ipPfxV6, ipPfxV4, ipPfxV6}
macAddr := net.HardwareAddr{0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E}
errs := []error{errors.New("a"), errors.New("b"), errors.New("c"), errors.New("d"), errors.New("e"), nil, errorObjectMarshalerImpl{fmt.Errorf("oops")}}
errs := []error{errors.New("a"), errors.New("b"), errors.New("c"), errors.New("d"), errors.New("e"), nil, loggableError{fmt.Errorf("oops")}}
ctx := context.Background()
stringers := []fmt.Stringer{ipAddrs[0], durations[0]}
rawJSONs := [][]byte{[]byte(`{"some":"json"}`), []byte(`{"longer":[1111,2222,3333,4444,5555]}`)}
+5 -6
View File
@@ -487,22 +487,21 @@ func (l *Logger) newEvent(level Level, done func(string)) *Event {
}
return nil
}
e := newEvent(l.w, level)
e := newEvent(l.w, level, l.stack, l.ctx, l.hooks)
e.done = done
e.ch = l.hooks
e.ctx = l.ctx
if level != NoLevel && LevelFieldName != "" {
e.Str(LevelFieldName, LevelFieldMarshalFunc(level))
}
if len(l.context) > 1 {
e.buf = enc.AppendObjectData(e.buf, l.context)
}
if l.stack {
e.Stack()
}
return e
}
func (l *Logger) scratchEvent() *Event {
return newEvent(LevelWriterAdapter{io.Discard}, DebugLevel, l.stack, l.ctx, l.hooks)
}
// should returns true if the log event should be logged.
func (l *Logger) should(lvl Level) bool {
if l.w == nil {
+34 -30
View File
@@ -192,12 +192,13 @@ func ExampleLogger_Log() {
func ExampleEvent_Dict() {
log := zerolog.New(os.Stdout)
log.Log().
Str("foo", "bar").
Dict("dict", zerolog.Dict().
Str("bar", "baz").
Int("n", 1),
).
e := log.Log().
Str("foo", "bar")
e.Dict("dict", e.CreateDict().
Str("bar", "baz").
Int("n", 1),
).
Msg("hello world")
// Output: {"foo":"bar","dict":{"bar":"baz","n":1},"message":"hello world"}
@@ -242,16 +243,17 @@ func (uu Users) MarshalZerologArray(a *zerolog.Array) {
func ExampleEvent_Array() {
log := zerolog.New(os.Stdout)
log.Log().
Str("foo", "bar").
Array("array", zerolog.Arr().
Str("baz").
Int(1).
Dict(zerolog.Dict().
Str("bar", "baz").
Int("n", 1),
),
).
e := log.Log().
Str("foo", "bar")
e.Array("array", e.CreateArray().
Str("baz").
Int(1).
Dict(e.CreateDict().
Str("bar", "baz").
Int("n", 1),
),
).
Msg("hello world")
// Output: {"foo":"bar","array":["baz",1,{"bar":"baz","n":1}],"message":"hello world"}
@@ -380,27 +382,29 @@ func ExampleEvent_Fields_slice() {
}
func ExampleContext_Dict() {
log := zerolog.New(os.Stdout).With().
Str("foo", "bar").
Dict("dict", zerolog.Dict().
Str("bar", "baz").
Int("n", 1),
).Logger()
ctx := zerolog.New(os.Stdout).With().
Str("foo", "bar")
log.Log().Msg("hello world")
logger := ctx.Dict("dict", ctx.CreateDict().
Str("bar", "baz").
Int("n", 1),
).Logger()
logger.Log().Msg("hello world")
// Output: {"foo":"bar","dict":{"bar":"baz","n":1},"message":"hello world"}
}
func ExampleContext_Array() {
log := zerolog.New(os.Stdout).With().
Str("foo", "bar").
Array("array", zerolog.Arr().
Str("baz").
Int(1),
).Logger()
ctx := zerolog.New(os.Stdout).With().
Str("foo", "bar")
log.Log().Msg("hello world")
logger := ctx.Array("array", ctx.CreateArray().
Str("baz").
Int(1),
).Logger()
logger.Log().Msg("hello world")
// Output: {"foo":"bar","array":["baz",1],"message":"hello world"}
}
+12 -368
View File
@@ -163,7 +163,7 @@ func TestWithPlurals(t *testing.T) {
Strs("strings_nil", nil).
Stringers("stringers", []fmt.Stringer{net.IP{127, 0, 0, 1}, nil}).
Stringers("stringers_nil", nil).
Errs("errs", []error{errors.New("some error"), errors.New("some other error"), nil, errorObjectMarshalerImpl{fmt.Errorf("oops")}}).
Errs("errs", []error{errors.New("some error"), errors.New("some other error"), nil, loggableError{fmt.Errorf("oops")}, nonLoggableError{fmt.Errorf("whoops"), 401}}).
Bools("bool", []bool{true, false}).
Ints("int", []int{1, 2}).
Ints8("int8", []int8{2, 3}).
@@ -182,7 +182,7 @@ func TestWithPlurals(t *testing.T) {
log := ctx.Logger()
log.Log().Msg("")
if got, want := decodeIfBinaryToString(out.Bytes()),
`{"strings":["foo","bar"],"strings_nil":[],"stringers":["127.0.0.1",null],"stringers_nil":null,"errs":["some error","some other error",null,{"error":"OOPS"}],"bool":[true,false],"int":[1,2],"int8":[2,3],"int16":[3,4],"int32":[4,5],"int64":[5,6],"uint":[6,7],"uint8":[7,8],"uint16":[8,9],"uint32":[9,10],"uint64":[10,11],"float32":[1.1,2.2],"float64":[2.2,3.3],"time":["0001-02-03T00:00:00Z","0005-06-07T00:00:00Z"],"dur":[1000,2000]}`+"\n"; got != want {
`{"strings":["foo","bar"],"strings_nil":[],"stringers":["127.0.0.1",null],"stringers_nil":null,"errs":["some error","some other error",null,{"l":"OOPS"},"whoops"],"bool":[true,false],"int":[1,2],"int8":[2,3],"int16":[3,4],"int32":[4,5],"int64":[5,6],"uint":[6,7],"uint8":[7,8],"uint16":[8,9],"uint32":[9,10],"uint64":[10,11],"float32":[1.1,2.2],"float64":[2.2,3.3],"time":["0001-02-03T00:00:00Z","0005-06-07T00:00:00Z"],"dur":[1000,2000]}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@@ -268,7 +268,7 @@ func TestFieldsMap_Arrays(t *testing.T) {
"times": []time.Time{{}},
"objs": []fixtureObj{{"a", "b", 1}},
}).Msg("")
// special case: []uint8 is logged as base64 string so we use "," for 44
// special case: []uint8 are logged as base64 string so we use "," for 44
if got, want := decodeIfBinaryToString(out.Bytes()), `{"bools":[true],"durs":[1000],"errors":["some error"],"floats32":[11],"floats64":[12],"ints":[1],"ints16":[3],"ints32":[4],"ints64":[5],"ints8":[1],"ipnets":["2001:db8:85a3::8a2e:370:7334/64"],"ipv6s":["2001:db8:85a3::8a2e:370:7334"],"macaddrs":["ABorPE1e"],"objs":[{"Pub":"a","tag":"b"}],"strings":["foo"],"times":["0001-01-01T00:00:00Z"],"uint8s":",","uints":[6],"uints16":[8],"uints32":[9],"uints64":[10]}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
@@ -278,13 +278,13 @@ func TestFieldsErr(t *testing.T) {
out := &bytes.Buffer{}
log := New(out)
log.Log().Fields(map[string]interface{}{
"nil": nil,
"nilerror": err,
"error": errors.New("some error"),
"loggable": loggableError{errors.New("loggable")},
"marshal": errorObjectMarshalerImpl{fmt.Errorf("oops")},
"nil": nil,
"nilerror": err,
"error": errors.New("some error"),
"loggable": loggableError{errors.New("loggable")},
"non-loggable": nonLoggableError{fmt.Errorf("oops"), 401},
}).Msg("")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"error":"some error","loggable":{"message":"loggable loggableError"},"marshal":{"error":"OOPS"},"nil":null,"nilerror":null}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"error":"some error","loggable":{"l":"LOGGABLE"},"nil":null,"nilerror":null,"non-loggable":"oops"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@@ -293,9 +293,9 @@ func TestFieldsErrs(t *testing.T) {
out := &bytes.Buffer{}
log := New(out)
log.Log().Fields(map[string]interface{}{
"errors": []error{errors.New("some error"), nil, err, loggableError{errors.New("loggable")}, errorObjectMarshalerImpl{fmt.Errorf("oops")}},
"errors": []error{errors.New("some error"), nil, err, loggableError{errors.New("loggable")}, nonLoggableError{fmt.Errorf("oops"), 404}},
}).Msg("")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"errors":["some error",null,null,{"message":"loggable loggableError"},{"error":"OOPS"}]}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"errors":["some error",null,null,{"l":"LOGGABLE"},"oops"]}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@@ -606,6 +606,7 @@ func TestFieldsDisabled(t *testing.T) {
Ctx(context.Background()).
Any("any", struct{ A string }{"test"}).
Interface("interface", fixtureObj{"a", "z", 1}).
Err(errors.New("some error")).
Msg("")
if got, want := decodeIfBinaryToString(out.Bytes()), ""; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
@@ -863,363 +864,6 @@ func TestOutputWithTimestamp(t *testing.T) {
}
}
type loggableError struct {
error
}
func (l loggableError) MarshalZerologObject(e *Event) {
if l.error == nil {
return
}
e.Str("message", l.error.Error()+" loggableError")
}
type nonLoggable struct {
where string
how int
what string
}
type wrappedError struct {
error
msg string
}
func (w wrappedError) Error() string {
if w.error == nil {
return w.msg
}
return w.error.Error() + ": " + w.msg
}
func TestErrorMarshalFunc_Default(t *testing.T) {
// test default behaviour
testErrorMarshalFunc(t, nil, "default",
"",
"null",
`"zot"`,
`"zot"`,
`{"message":"log loggableError"}`,
`{"message":"log loggableError"}`,
"[]",
`"foo","bar"`,
)
}
func TestErrorMarshalFunc_AppendedString(t *testing.T) {
// test returning an appended string from ErrorMarshalFunc
testErrorMarshalFunc(t, func(err error) interface{} {
if err == nil {
return nil
}
return err.Error() + ": marshaled"
}, "appended string",
"",
"null",
`"zot: marshaled"`,
`"zot: marshaled"`,
`"log: marshaled"`,
`{"message":"log loggableError"}`,
"[]",
`"foo: marshaled","bar: marshaled"`,
)
}
func TestErrorMarshalFunc_NilError(t *testing.T) {
// test returning an nil error from ErrorMarshalFunc
testErrorMarshalFunc(t, func(err error) interface{} {
return error(nil)
}, "nil error",
"",
"null",
"",
"null",
"",
`{"message":"log loggableError"}`,
"[]",
"null,null",
)
}
func TestErrorMarshalFunc_UntypedNil(t *testing.T) {
// test returning an untyped nil from ErrorMarshalFunc
testErrorMarshalFunc(t, func(err error) interface{} {
return nil
}, "untyped nil",
"",
"null",
"",
"null",
"",
`{"message":"log loggableError"}`,
"[]",
"null,null",
)
}
func TestErrorMarshalFunc_WrappedError(t *testing.T) {
// test returning an wrapped error from ErrorMarshalFunc
testErrorMarshalFunc(t, func(err error) interface{} {
if err == nil {
return nil
} else if we, ok := err.(wrappedError); ok {
return we
} else {
return wrappedError{err, "addendum"}
}
}, "wrapped error",
"",
"null",
`"zot: addendum"`,
`"zot: addendum"`,
`"log: addendum"`,
`{"message":"log loggableError"}`,
"[]",
`"foo: addendum","bar: addendum"`,
)
}
func TestErrorMarshalFunc_LoggableType(t *testing.T) {
// test returning a loggable type from ErrorMarshalFunc
testErrorMarshalFunc(t, func(err error) interface{} {
return loggableError{err}
}, "loggable type",
"{}",
"null",
`{"message":"zot loggableError"}`,
`{"message":"zot loggableError"}`,
`{"message":"log loggableError"}`,
`{"message":"log loggableError"}`,
"[]",
`{"message":"foo loggableError"},{"message":"bar loggableError"}`,
)
}
func TestErrorMarshalFunc_String(t *testing.T) {
// test returning a string type from ErrorMarshalFunc
testErrorMarshalFunc(t, func(err error) interface{} {
return "i'm an err" // return just a string
}, "string type",
`"i'm an err"`,
"null",
`"i'm an err"`,
`"i'm an err"`,
`"i'm an err"`,
`{"message":"log loggableError"}`,
"[]",
`"i'm an err","i'm an err"`,
)
}
func TestErrorMarshalFunc_NonLoggableType(t *testing.T) {
// test returning a non-loggable type from ErrorMarshalFunc
testErrorMarshalFunc(t, func(err error) interface{} {
if err == nil {
return nil
}
return nonLoggable{where: "here", how: 42, what: err.Error()}
}, "non-loggable type",
"",
"null",
"{}",
"{}",
"{}",
`{"message":"log loggableError"}`,
"[]",
"{},{}",
)
}
func TestErrorMarshalFunc_NilMarshal(t *testing.T) {
// test returning a nil from ErrorMarshalFunc
testErrorMarshalFunc(t, func(err error) interface{} {
var nilerr error = nil
return nilerr
}, "nil-marshal",
"",
"null",
"",
"null",
"",
`{"message":"log loggableError"}`,
"[]",
"null,null",
)
}
func testErrorMarshalFunc(t *testing.T,
errFunc func(err error) interface{},
testcase string,
whenNil string,
whenNilField string,
hasValue string,
hasValueField string,
whenLoggable string,
whenLoggableField string,
errsNilValue string,
errsValue string,
) {
const suffix = `"message":"msg"}`
if errFunc != nil {
originalErrorMarshalFunc := ErrorMarshalFunc
defer func() {
ErrorMarshalFunc = originalErrorMarshalFunc
}()
ErrorMarshalFunc = errFunc
}
var err error
wantNil := `{`
if whenNil != "" {
wantNil = wantNil + `"error":` + whenNil + `,`
}
wantNil = wantNil + suffix
wantNilField := `{"err":` + whenNilField + `,` + suffix
err = nil
testContextErrorMarshalFunc(t, testcase+" / context.Err(nil)", wantNil, func(ctx Context) Context {
ctx = ctx.Err(err)
return ctx
})
testEventErrorMarshalFunc(t, testcase+" / event.Err(nil)", wantNil, func(e *Event) *Event {
e.Err(err)
return e
})
testFieldsErrorMarshalFunc(t, testcase+" / field.Err(nil)", wantNilField, func(e *Event) *Event {
e.Fields(map[string]interface{}{"err": err})
return e
})
wantErr := `{`
if hasValue != "" {
wantErr = wantErr + `"error":` + hasValue + `,`
}
wantErr = wantErr + suffix
wantErrField := `{"err":` + hasValueField + `,` + suffix
err = errors.New("zot")
testContextErrorMarshalFunc(t, testcase+" / context.Err(error)", wantErr, func(ctx Context) Context {
ctx = ctx.Err(err)
return ctx
})
testEventErrorMarshalFunc(t, testcase+" / event.Err(error)", wantErr, func(e *Event) *Event {
e.Err(err)
return e
})
testFieldsErrorMarshalFunc(t, testcase+" / field.Err(error)", wantErrField, func(e *Event) *Event {
e.Fields(map[string]interface{}{"err": err})
return e
})
wantLoggable := `{`
if whenLoggable != "" {
wantLoggable = wantLoggable + `"error":` + whenLoggable + `,`
}
wantLoggable = wantLoggable + suffix
wantLoggableField := `{"err":` + whenLoggableField + `,` + suffix
err = loggableError{errors.New("log")}
testContextErrorMarshalFunc(t, testcase+" / context.Err(loggable)", wantLoggable, func(ctx Context) Context {
ctx = ctx.Err(err)
return ctx
})
testEventErrorMarshalFunc(t, testcase+" / event.Err(loggable)", wantLoggable, func(e *Event) *Event {
e.Err(err)
return e
})
testFieldsErrorMarshalFunc(t, testcase+" / field.Err(loggable)", wantLoggableField, func(e *Event) *Event {
e.Fields(map[string]interface{}{"err": err})
return e
})
wantErrsNil := `{"errs":` + errsNilValue + `,` + suffix
var errs []error = nil
testContextErrorMarshalFunc(t, testcase+" / context.Errs(nil)", wantErrsNil, func(ctx Context) Context {
ctx = ctx.Errs("errs", errs)
return ctx
})
testEventErrorMarshalFunc(t, testcase+" / event.Errs(nil)", wantErrsNil, func(e *Event) *Event {
e.Errs("errs", errs)
return e
})
testFieldsErrorMarshalFunc(t, testcase+" / field.Errs(nil)", wantErrsNil, func(e *Event) *Event {
e.Fields(map[string]interface{}{"errs": errs})
return e
})
wantErrs := `{"errs":[` + errsValue + `],` + suffix
errs = []error{errors.New("foo"), errors.New("bar")}
testContextErrorMarshalFunc(t, testcase+" / context.Errs([])", wantErrs, func(ctx Context) Context {
ctx = ctx.Errs("errs", []error{errors.New("foo"), errors.New("bar")})
return ctx
})
testEventErrorMarshalFunc(t, testcase+" / event.Errs([])", wantErrs, func(e *Event) *Event {
e.Errs("errs", errs)
return e
})
testFieldsErrorMarshalFunc(t, testcase+" / field.Errs([])", wantErrs, func(e *Event) *Event {
e.Fields(map[string]interface{}{"errs": errs})
return e
})
}
func testEventErrorMarshalFunc(t *testing.T, testcase string, wants string, test func(e *Event) *Event) {
out := &bytes.Buffer{}
logger := New(out)
test(logger.Log()).Msg("msg")
if got, want := decodeIfBinaryToString(out.Bytes()), wants+"\n"; got != want {
t.Errorf("%s output:\ngot: %v\nwant: %v", testcase, got, want)
}
}
func testFieldsErrorMarshalFunc(t *testing.T, testcase string, wants string, test func(e *Event) *Event) {
out := &bytes.Buffer{}
logger := New(out)
test(logger.Log()).Msg("msg")
if got, want := decodeIfBinaryToString(out.Bytes()), wants+"\n"; got != want {
t.Errorf("%s output:\ngot: %v\nwant: %v", testcase, got, want)
}
}
func testContextErrorMarshalFunc(t *testing.T, testcase string, wants string, test func(ctx Context) Context) {
out := &bytes.Buffer{}
ctx := New(out).With()
ctx = test(ctx)
logger := ctx.Logger()
logger.Log().Msg("msg")
if got, want := decodeIfBinaryToString(out.Bytes()), wants+"\n"; got != want {
t.Errorf("%s output:\ngot: %v\nwant: %v", testcase, got, want)
}
}
func TestCallerMarshalFunc(t *testing.T) {
out := &bytes.Buffer{}
log := New(out)