diff --git a/benchmark_test.go b/benchmark_test.go index 7ba70d0..1b6b67d 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -72,12 +72,19 @@ func BenchmarkLogFields(b *testing.B) { }) } +type obj struct { + Pub string + Tag string `json:"tag"` + priv int +} + +func (o obj) MarshalZerologObject(e *Event) { + e.Str("Pub", o.Pub). + Str("Tag", o.Tag). + Int("priv", o.priv) +} + func BenchmarkLogFieldType(b *testing.B) { - type obj struct { - Pub string - Tag string `json:"tag"` - priv int - } bools := []bool{true, false, true, false, true, false, true, false, true, false} ints := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} floats := []float64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} @@ -143,6 +150,9 @@ func BenchmarkLogFieldType(b *testing.B) { "Interface": func(e *Event) *Event { return e.Interface("k", o) }, + "Object": func(e *Event) *Event { + return e.Object("k", o) + }, } logger := New(ioutil.Discard) b.ResetTimer() diff --git a/context.go b/context.go index 1e3a330..68aaef7 100644 --- a/context.go +++ b/context.go @@ -1,6 +1,9 @@ package zerolog -import "time" +import ( + "io/ioutil" + "time" +) // Context configures a new sub-logger with contextual fields. type Context struct { @@ -26,6 +29,16 @@ func (c Context) Dict(key string, dict *Event) 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) + e.Object(key, obj) + e.buf[0] = ',' // A new event starts as an object, we want to embed it. + c.l.context = append(c.l.context, e.buf...) + eventPool.Put(e) + return c +} + // Str adds the field key with val as a string to the logger context. func (c Context) Str(key, val string) Context { c.l.context = appendString(c.l.context, key, val) diff --git a/event.go b/event.go index 94676f0..3778be2 100644 --- a/event.go +++ b/event.go @@ -26,6 +26,10 @@ type Event struct { done func(msg string) } +type LogObjectMarshaler interface { + MarshalZerologObject(e *Event) +} + func newEvent(w LevelWriter, level Level, enabled bool) *Event { if !enabled { return &Event{} @@ -121,6 +125,23 @@ func Dict() *Event { return newEvent(levelWriterAdapter{ioutil.Discard}, 0, true) } +// Object marshals an object that implement the LogObjectMarshaler interface. +func (e *Event) Object(key string, obj LogObjectMarshaler) *Event { + e.buf = appendKey(e.buf, key) + pos := len(e.buf) + obj.MarshalZerologObject(e) + if pos < len(e.buf) { + // As MarshalZerologObject will use event API, the first field will be + // preceded by a coma. If at least one field has been added (buf grew), + // we replace this coma by the opening bracket. + e.buf[pos] = '{' + } else { + e.buf = append(e.buf, '{') + } + e.buf = append(e.buf, '}') + return e +} + // Str adds the field key with val as a string to the *Event context. func (e *Event) Str(key, val string) *Event { if !e.enabled { diff --git a/log_example_test.go b/log_example_test.go index 63c1a44..5be0cad 100644 --- a/log_example_test.go +++ b/log_example_test.go @@ -138,6 +138,31 @@ func ExampleEvent_Dict() { // Output: {"foo":"bar","dict":{"bar":"baz","n":1},"message":"hello world"} } +type User struct { + Name string + Age int + Created time.Time +} + +func (u User) MarshalZerologObject(e *zerolog.Event) { + e.Str("name", u.Name). + Int("age", u.Age). + Time("created", u.Created) +} + +func ExampleEvent_Object() { + log := zerolog.New(os.Stdout) + + u := User{"John", 35, time.Time{}} + + log.Log(). + Str("foo", "bar"). + Object("user", u). + Msg("hello world") + + // Output: {"foo":"bar","user":{"name":"John","age":35,"created":"0001-01-01T00:00:00Z"},"message":"hello world"} +} + func ExampleEvent_Interface() { log := zerolog.New(os.Stdout) @@ -197,6 +222,19 @@ func ExampleContext_Dict() { // Output: {"foo":"bar","dict":{"bar":"baz","n":1},"message":"hello world"} } +func ExampleContext_Object() { + u := User{"John", 35, time.Time{}} + + log := zerolog.New(os.Stdout).With(). + Str("foo", "bar"). + Object("user", u). + Logger() + + log.Log().Msg("hello world") + + // Output: {"foo":"bar","user":{"name":"John","age":35,"created":"0001-01-01T00:00:00Z"},"message":"hello world"} +} + func ExampleContext_Interface() { obj := struct { Name string `json:"name"`