diff --git a/array.go b/array.go new file mode 100644 index 0000000..ca17b8e --- /dev/null +++ b/array.go @@ -0,0 +1,169 @@ +package zerolog + +import ( + "sync" + "time" + + "github.com/rs/zerolog/internal/json" +) + +var arrayPool = &sync.Pool{ + New: func() interface{} { + return &Array{ + buf: make([]byte, 0, 500), + } + }, +} + +type Array struct { + buf []byte +} + +// Arr creates an array to be added to an Event or Context. +func Arr() *Array { + a := arrayPool.Get().(*Array) + a.buf = a.buf[:0] + return a +} + +func (*Array) MarshalZerologArray(*Array) { +} + +func (a *Array) write(dst []byte) []byte { + if len(a.buf) == 0 { + dst = append(dst, `[]`...) + } else { + a.buf[0] = '[' + dst = append(append(dst, a.buf...), ']') + } + arrayPool.Put(a) + return dst +} + +// Object marshals an object that implement the LogObjectMarshaler +// interface and append it to the array. +func (a *Array) Object(obj LogObjectMarshaler) *Array { + a.buf = append(a.buf, ',') + e := Dict() + obj.MarshalZerologObject(e) + e.buf = append(e.buf, '}') + a.buf = append(a.buf, e.buf...) + return a +} + +// Str append the val as a string to the array. +func (a *Array) Str(val string) *Array { + a.buf = json.AppendString(append(a.buf, ','), val) + return a +} + +// Bytes append the val as a string to the array. +func (a *Array) Bytes(val []byte) *Array { + a.buf = json.AppendBytes(append(a.buf, ','), val) + return a +} + +// Err append the err as a string to the array. +func (a *Array) Err(err error) *Array { + a.buf = json.AppendError(append(a.buf, ','), err) + return a +} + +// Bool append the val as a bool to the array. +func (a *Array) Bool(b bool) *Array { + a.buf = json.AppendBool(append(a.buf, ','), b) + return a +} + +// Int append i as a int to the array. +func (a *Array) Int(i int) *Array { + a.buf = json.AppendInt(append(a.buf, ','), i) + return a +} + +// Int8 append i as a int8 to the array. +func (a *Array) Int8(i int8) *Array { + a.buf = json.AppendInt8(append(a.buf, ','), i) + return a +} + +// Int16 append i as a int16 to the array. +func (a *Array) Int16(i int16) *Array { + a.buf = json.AppendInt16(append(a.buf, ','), i) + return a +} + +// Int32 append i as a int32 to the array. +func (a *Array) Int32(i int32) *Array { + a.buf = json.AppendInt32(append(a.buf, ','), i) + return a +} + +// Int64 append i as a int64 to the array. +func (a *Array) Int64(i int64) *Array { + a.buf = json.AppendInt64(append(a.buf, ','), i) + return a +} + +// Uint append i as a uint to the array. +func (a *Array) Uint(i uint) *Array { + a.buf = json.AppendUint(append(a.buf, ','), i) + return a +} + +// Uint8 append i as a uint8 to the array. +func (a *Array) Uint8(i uint8) *Array { + a.buf = json.AppendUint8(append(a.buf, ','), i) + return a +} + +// Uint16 append i as a uint16 to the array. +func (a *Array) Uint16(i uint16) *Array { + a.buf = json.AppendUint16(append(a.buf, ','), i) + return a +} + +// Uint32 append i as a uint32 to the array. +func (a *Array) Uint32(i uint32) *Array { + a.buf = json.AppendUint32(append(a.buf, ','), i) + return a +} + +// Uint64 append i as a uint64 to the array. +func (a *Array) Uint64(i uint64) *Array { + a.buf = json.AppendUint64(append(a.buf, ','), i) + return a +} + +// Float32 append f as a float32 to the array. +func (a *Array) Float32(f float32) *Array { + a.buf = json.AppendFloat32(append(a.buf, ','), f) + return a +} + +// Float64 append f as a float64 to the array. +func (a *Array) Float64(f float64) *Array { + a.buf = json.AppendFloat64(append(a.buf, ','), f) + return a +} + +// Time append t formated as string using zerolog.TimeFieldFormat. +func (a *Array) Time(t time.Time) *Array { + a.buf = json.AppendTime(append(a.buf, ','), t, TimeFieldFormat) + return a +} + +// Dur append d to the array. +func (a *Array) Dur(d time.Duration) *Array { + a.buf = json.AppendDuration(append(a.buf, ','), d, DurationFieldUnit, DurationFieldInteger) + return a +} + +// Interface append i marshaled using reflection. +func (a *Array) Interface(i interface{}) *Array { + if obj, ok := i.(LogObjectMarshaler); ok { + return a.Object(obj) + } + a.buf = json.AppendInterface(append(a.buf, ','), i) + return a +} diff --git a/array_test.go b/array_test.go new file mode 100644 index 0000000..02fe9ae --- /dev/null +++ b/array_test.go @@ -0,0 +1,30 @@ +package zerolog + +import ( + "testing" + "time" +) + +func TestArray(t *testing.T) { + a := Arr(). + 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). + Str("a"). + Time(time.Time{}). + Dur(0) + want := `[true,1,2,3,4,5,6,7,8,9,10,11,12,"a","0001-01-01T00:00:00Z",0]` + if got := string(a.write([]byte{})); got != want { + t.Errorf("Array.write()\ngot: %s\nwant: %s", got, want) + } +} diff --git a/context.go b/context.go index 021659b..cd25687 100644 --- a/context.go +++ b/context.go @@ -31,6 +31,26 @@ func (c Context) Dict(key string, dict *Event) Context { return c } +// Array adds the field key with an array to the event context. +// Use zerolog.Arr() 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 = json.AppendKey(c.l.context, key) + if arr, ok := arr.(*Array); ok { + 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) + } + 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{ioutil.Discard}, 0, true) diff --git a/event.go b/event.go index 72e3288..9796f86 100644 --- a/event.go +++ b/event.go @@ -28,10 +28,18 @@ type Event struct { done func(msg string) } +// LogObjectMarshaler provides a strongly-typed and encoding-agnostic interface +// to be implemented by types used with Event/Context's Object methods. type LogObjectMarshaler interface { MarshalZerologObject(e *Event) } +// LogArrayMarshaler provides a strongly-typed and encoding-agnostic interface +// to be implemented by types used with Event/Context's Array methods. +type LogArrayMarshaler interface { + MarshalZerologArray(a *Array) +} + func newEvent(w LevelWriter, level Level, enabled bool) *Event { if !enabled { return &Event{} @@ -127,6 +135,25 @@ func Dict() *Event { return newEvent(levelWriterAdapter{ioutil.Discard}, 0, true) } +// Array adds the field key with an array to the event context. +// Use zerolog.Arr() to create the array or pass a type that +// implement the LogArrayMarshaler interface. +func (e *Event) Array(key string, arr LogArrayMarshaler) *Event { + if !e.enabled { + return e + } + e.buf = json.AppendKey(e.buf, key) + var a *Array + if aa, ok := arr.(*Array); ok { + a = aa + } else { + a = Arr() + arr.MarshalZerologArray(a) + } + e.buf = a.write(e.buf) + return e +} + func (e *Event) appendObject(obj LogObjectMarshaler) { pos := len(e.buf) obj.MarshalZerologObject(e) @@ -143,6 +170,9 @@ func (e *Event) appendObject(obj LogObjectMarshaler) { // Object marshals an object that implement the LogObjectMarshaler interface. func (e *Event) Object(key string, obj LogObjectMarshaler) *Event { + if !e.enabled { + return e + } e.buf = json.AppendKey(e.buf, key) e.appendObject(obj) return e @@ -166,7 +196,7 @@ func (e *Event) Strs(key string, vals []string) *Event { return e } -// Bytes adds the field key with val as a []byte to the *Event context. +// Bytes adds the field key with val as a string to the *Event context. func (e *Event) Bytes(key string, val []byte) *Event { if !e.enabled { return e diff --git a/log_example_test.go b/log_example_test.go index 5be0cad..00c3868 100644 --- a/log_example_test.go +++ b/log_example_test.go @@ -150,9 +150,49 @@ func (u User) MarshalZerologObject(e *zerolog.Event) { Time("created", u.Created) } +type Users []User + +func (uu Users) MarshalZerologArray(a *zerolog.Array) { + for _, u := range uu { + a.Object(u) + } +} + +func ExampleEvent_Array() { + log := zerolog.New(os.Stdout) + + log.Log(). + Str("foo", "bar"). + Array("array", zerolog.Arr(). + Str("baz"). + Int(1), + ). + Msg("hello world") + + // Output: {"foo":"bar","array":["baz",1],"message":"hello world"} +} + +func ExampleEvent_Array_object() { + log := zerolog.New(os.Stdout) + + // Users implements zerolog.LogArrayMarshaler + u := Users{ + User{"John", 35, time.Time{}}, + User{"Bob", 55, time.Time{}}, + } + + log.Log(). + Str("foo", "bar"). + Array("users", u). + Msg("hello world") + + // Output: {"foo":"bar","users":[{"name":"John","age":35,"created":"0001-01-01T00:00:00Z"},{"name":"Bob","age":55,"created":"0001-01-01T00:00:00Z"}],"message":"hello world"} +} + func ExampleEvent_Object() { log := zerolog.New(os.Stdout) + // User implements zerolog.LogObjectMarshaler u := User{"John", 35, time.Time{}} log.Log(). @@ -222,7 +262,38 @@ func ExampleContext_Dict() { // 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() + + log.Log().Msg("hello world") + + // Output: {"foo":"bar","array":["baz",1],"message":"hello world"} +} + +func ExampleContext_Array_object() { + // Users implements zerolog.LogArrayMarshaler + u := Users{ + User{"John", 35, time.Time{}}, + User{"Bob", 55, time.Time{}}, + } + + log := zerolog.New(os.Stdout).With(). + Str("foo", "bar"). + Array("users", u). + Logger() + + log.Log().Msg("hello world") + + // Output: {"foo":"bar","users":[{"name":"John","age":35,"created":"0001-01-01T00:00:00Z"},{"name":"Bob","age":55,"created":"0001-01-01T00:00:00Z"}],"message":"hello world"} +} + func ExampleContext_Object() { + // User implements zerolog.LogObjectMarshaler u := User{"John", 35, time.Time{}} log := zerolog.New(os.Stdout).With().