Repository: rs/zerolog
Pull request #623 (2023-12-07 10:29:38)

add: ContextLogger Interface LogObjectMarshaler
The issue I've identified is that the `Interface` method, derived from `Ctx`, does not support `LogObjectMarshaler`. 

I made a PR out of the blue because it is expected to be minor, do I need an ISSUE?


Patch:
diff --git a/context.go b/context.go
index 48c1d5d..df352eb 100644
--- a/context.go
+++ b/context.go
@@ -163,6 +163,22 @@ func (c Context) Errs(key string, errs []error) Context {
 
 // Err adds the field "error" with serialized err to the logger context.
 func (c Context) Err(err error) Context {
+	if c.l.stack && ErrorStackMarshaler != nil {
+		switch m := ErrorStackMarshaler(err).(type) {
+		case nil:
+		case LogObjectMarshaler:
+			c = c.Object(ErrorStackFieldName, m)
+		case error:
+			if m != nil && !isNilValue(m) {
+				c = c.Str(ErrorStackFieldName, m.Error())
+			}
+		case string:
+			c = c.Str(ErrorStackFieldName, m)
+		default:
+			c = c.Interface(ErrorStackFieldName, m)
+		}
+	}
+
 	return c.AnErr(ErrorFieldName, err)
 }
 
@@ -375,6 +391,9 @@ func (c Context) Durs(key string, d []time.Duration) Context {
 
 // Interface adds the field key with obj marshaled using reflection.
 func (c Context) Interface(key string, i interface{}) Context {
+	if obj, ok := i.(LogObjectMarshaler); ok {
+		return c.Object(key, obj)
+	}
 	c.l.context = enc.AppendInterface(enc.AppendKey(c.l.context, key), i)
 	return c
 }
diff --git a/log.go b/log.go
index caa1845..c6baa3a 100644
--- a/log.go
+++ b/log.go
@@ -325,10 +325,13 @@ func (l Logger) Sample(s Sampler) Logger {
 }
 
 // Hook returns a logger with the h Hook.
-func (l Logger) Hook(h Hook) Logger {
-	newHooks := make([]Hook, len(l.hooks), len(l.hooks)+1)
+func (l Logger) Hook(hooks ...Hook) Logger {
+	if len(hooks) == 0 {
+		return l
+	}
+	newHooks := make([]Hook, len(l.hooks), len(l.hooks)+len(hooks))
 	copy(newHooks, l.hooks)
-	l.hooks = append(newHooks, h)
+	l.hooks = append(newHooks, hooks...)
 	return l
 }
 


Test patch:
diff --git a/ctx_test.go b/ctx_test.go
index d024d54..397e966 100644
--- a/ctx_test.go
+++ b/ctx_test.go
@@ -1,6 +1,7 @@
 package zerolog
 
 import (
+	"bytes"
 	"context"
 	"io"
 	"reflect"
@@ -68,3 +69,31 @@ func TestCtxDisabled(t *testing.T) {
 		t.Error("WithContext did not override logger with a disabled logger")
 	}
 }
+
+type logObjectMarshalerImpl struct {
+	name string
+	age  int
+}
+
+func (t logObjectMarshalerImpl) MarshalZerologObject(e *Event) {
+	e.Str("name", "custom_value").Int("age", t.age)
+}
+
+func Test_InterfaceLogObjectMarshaler(t *testing.T) {
+	var buf bytes.Buffer
+	log := New(&buf)
+	ctx := log.WithContext(context.Background())
+
+	log2 := Ctx(ctx)
+
+	withLog := log2.With().Interface("obj", &logObjectMarshalerImpl{
+		name: "foo",
+		age:  29,
+	}).Logger()
+
+	withLog.Info().Msg("test")
+
+	if got, want := buf.String(), `{"level":"info","obj":{"name":"custom_value","age":29},"message":"test"}`+"\n"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
diff --git a/diode/diode_test.go b/diode/diode_test.go
index 87f62bc..29b3f86 100644
--- a/diode/diode_test.go
+++ b/diode/diode_test.go
@@ -3,6 +3,7 @@ package diode_test
 import (
 	"bytes"
 	"fmt"
+	"io"
 	"log"
 	"os"
 	"testing"
diff --git a/hlog/hlog_test.go b/hlog/hlog_test.go
index 0d6b31e..445d6b6 100644
--- a/hlog/hlog_test.go
+++ b/hlog/hlog_test.go
@@ -7,6 +7,7 @@ import (
 	"bytes"
 	"context"
 	"fmt"
+	"io"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
diff --git a/log_example_test.go b/log_example_test.go
index 81e2c20..c48a1e3 100644
--- a/log_example_test.go
+++ b/log_example_test.go
@@ -72,7 +72,7 @@ func ExampleLogger_Hook() {
 	var levelNameHook LevelNameHook
 	var messageHook MessageHook = "The message"
 
-	log := zerolog.New(os.Stdout).Hook(levelNameHook).Hook(messageHook)
+	log := zerolog.New(os.Stdout).Hook(levelNameHook, messageHook)
 
 	log.Info().Msg("hello world")
 
diff --git a/pkgerrors/stacktrace_test.go b/pkgerrors/stacktrace_test.go
index a09b986..787a096 100644
--- a/pkgerrors/stacktrace_test.go
+++ b/pkgerrors/stacktrace_test.go
@@ -60,6 +60,22 @@ func TestLogStackFromContext(t *testing.T) {
 	}
 }
 
+func TestLogStackFromContextWith(t *testing.T) {
+	zerolog.ErrorStackMarshaler = MarshalStack
+
+	err := fmt.Errorf("from error: %w", errors.New("error message"))
+	out := &bytes.Buffer{}
+	log := zerolog.New(out).With().Stack().Err(err).Logger() // calling Stack() on log context instead of event
+
+	log.Error().Msg("")
+
+	got := out.String()
+	want := `\{"level":"error","stack":\[\{"func":"TestLogStackFromContextWith","line":"66","source":"stacktrace_test.go"\},.*\],"error":"from error: error message"\}\n`
+	if ok, _ := regexp.MatchString(want, got); !ok {
+		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
+	}
+}
+
 func BenchmarkLogStack(b *testing.B) {
 	zerolog.ErrorStackMarshaler = MarshalStack
 	out := &bytes.Buffer{}