diff --git a/bridges/otelzap/encoder.go b/bridges/otelzap/encoder.go index f59c97646f7..c29995ca31a 100644 --- a/bridges/otelzap/encoder.go +++ b/bridges/otelzap/encoder.go @@ -40,8 +40,11 @@ func (m *objectEncoder) AddArray(key string, v zapcore.ArrayMarshaler) error { } func (m *objectEncoder) AddObject(k string, v zapcore.ObjectMarshaler) error { - // TODO - return nil + // TODO: Use objectEncoder from a pool. + newobj := newObjectEncoder(2) + err := v.MarshalLogObject(newobj) + m.kv = append(m.kv, log.Map(k, newobj.kv...)) + return err } func (m *objectEncoder) AddBinary(k string, v []byte) { @@ -158,14 +161,20 @@ type arrayEncoder struct { elems []log.Value // nolint:unused } -// TODO. func (a *arrayEncoder) AppendArray(v zapcore.ArrayMarshaler) error { - return nil + // TODO: Use arrayEncoder from a pool. + arr := &arrayEncoder{} + err := v.MarshalLogArray(arr) + a.elems = append(a.elems, log.SliceValue(arr.elems...)) + return err } -// TODO. func (a *arrayEncoder) AppendObject(v zapcore.ObjectMarshaler) error { - return nil + // TODO: Use objectEncoder from a pool. + m := newObjectEncoder(2) + err := v.MarshalLogObject(m) + a.elems = append(a.elems, log.MapValue(m.kv...)) + return err } // TODO. diff --git a/bridges/otelzap/encoder_test.go b/bridges/otelzap/encoder_test.go index d4e51db2825..358e9bda632 100644 --- a/bridges/otelzap/encoder_test.go +++ b/bridges/otelzap/encoder_test.go @@ -6,11 +6,13 @@ package otelzap import ( + "errors" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uber.org/multierr" "go.uber.org/zap/zapcore" "go.opentelemetry.io/otel/log" @@ -18,11 +20,33 @@ import ( // Copied from https://github.com/uber-go/zap/blob/b39f8b6b6a44d8371a87610be50cce58eeeaabcb/zapcore/memory_encoder_test.go. func TestObjectEncoder(t *testing.T) { + // Expected output of a turducken. + wantTurducken := map[string]interface{}{ + "ducks": []interface{}{ + map[string]interface{}{"in": "chicken"}, + map[string]interface{}{"in": "chicken"}, + }, + } + tests := []struct { desc string f func(zapcore.ObjectEncoder) expected interface{} }{ + { + desc: "AddObject", + f: func(e zapcore.ObjectEncoder) { + assert.NoError(t, e.AddObject("k", loggable{true}), "Expected AddObject to succeed.") + }, + expected: map[string]interface{}{"loggable": "yes"}, + }, + { + desc: "AddObject (nested)", + f: func(e zapcore.ObjectEncoder) { + assert.NoError(t, e.AddObject("k", turducken{}), "Expected AddObject to succeed.") + }, + expected: wantTurducken, + }, { desc: "AddArray", f: func(e zapcore.ObjectEncoder) { @@ -35,6 +59,13 @@ func TestObjectEncoder(t *testing.T) { }, expected: []interface{}{true, false, true}, }, + { + desc: "AddArray (nested)", + f: func(e zapcore.ObjectEncoder) { + assert.NoError(t, e.AddArray("k", turduckens(2)), "Expected AddArray to succeed.") + }, + expected: []interface{}{wantTurducken, wantTurducken}, + }, { desc: "AddBinary", f: func(e zapcore.ObjectEncoder) { e.AddBinary("k", []byte("foo")) }, @@ -164,6 +195,8 @@ func TestArrayEncoder(t *testing.T) { f func(zapcore.ArrayEncoder) expected interface{} }{ + // AppendObject and AppendArray are covered by the AddObject (nested) and + // AddArray (nested) cases above. {"AppendBool", func(e zapcore.ArrayEncoder) { e.AppendBool(true) }, true}, {"AppendByteString", func(e zapcore.ArrayEncoder) { e.AppendByteString([]byte("foo")) }, "foo"}, {"AppendFloat64", func(e zapcore.ArrayEncoder) { e.AppendFloat64(3.14) }, 3.14}, @@ -187,6 +220,52 @@ func TestArrayEncoder(t *testing.T) { } } +type turducken struct{} + +func (t turducken) MarshalLogObject(enc zapcore.ObjectEncoder) error { + return enc.AddArray("ducks", zapcore.ArrayMarshalerFunc(func(arr zapcore.ArrayEncoder) error { + for i := 0; i < 2; i++ { + err := arr.AppendObject(zapcore.ObjectMarshalerFunc(func(inner zapcore.ObjectEncoder) error { + inner.AddString("in", "chicken") + return nil + })) + if err != nil { + return err + } + } + return nil + })) +} + +type turduckens int + +func (t turduckens) MarshalLogArray(enc zapcore.ArrayEncoder) error { + var err error + tur := turducken{} + for i := 0; i < int(t); i++ { + err = multierr.Append(err, enc.AppendObject(tur)) + } + return err +} + +type loggable struct{ bool } + +func (l loggable) MarshalLogObject(enc zapcore.ObjectEncoder) error { + if !l.bool { + return errors.New("can't marshal") + } + enc.AddString("loggable", "yes") + return nil +} + +func (l loggable) MarshalLogArray(enc zapcore.ArrayEncoder) error { + if !l.bool { + return errors.New("can't marshal") + } + enc.AppendBool(true) + return nil +} + func value2Result(v log.Value) any { switch v.Kind() { case log.KindBool: diff --git a/bridges/otelzap/go.mod b/bridges/otelzap/go.mod index 4daadc84d72..a8014330deb 100644 --- a/bridges/otelzap/go.mod +++ b/bridges/otelzap/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel/log v0.3.0 + go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 ) @@ -16,6 +17,5 @@ require ( go.opentelemetry.io/otel v1.27.0 // indirect go.opentelemetry.io/otel/metric v1.27.0 // indirect go.opentelemetry.io/otel/trace v1.27.0 // indirect - go.uber.org/multierr v1.11.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect )