From 311a4d0077708c64d290eeadae9401a181f79a04 Mon Sep 17 00:00:00 2001 From: khushijain21 Date: Tue, 4 Jun 2024 15:27:39 +0530 Subject: [PATCH 1/7] otelzap: Implement namespace method --- bridges/otelzap/encoder.go | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/bridges/otelzap/encoder.go b/bridges/otelzap/encoder.go index eed14b0798f..d32e6b4e170 100644 --- a/bridges/otelzap/encoder.go +++ b/bridges/otelzap/encoder.go @@ -16,10 +16,19 @@ var ( _ zapcore.ArrayEncoder = (*arrayEncoder)(nil) ) +type newNameSpace struct { + ns string + kv []log.KeyValue + next *newNameSpace +} + // objectEncoder implements zapcore.ObjectEncoder. // It encodes given fields to OTel key-values. type objectEncoder struct { - kv []log.KeyValue + // root is a pointer to the default namespace + root *newNameSpace + // cur is a pointer to the namespace we're currently writing to. + cur *newNameSpace } // nolint:unused @@ -31,6 +40,15 @@ func newObjectEncoder(len int) *objectEncoder { } } +// It iterates to the end of the linked list and appends namespace data. +func (m *objectEncoder) getObjValue(o *newNameSpace) { + if o.next == nil { + return + } + m.getObjValue(o.next) + o.kv = append(o.kv, log.Map(o.next.ns, o.next.kv...)) +} + func (m *objectEncoder) AddArray(key string, v zapcore.ArrayMarshaler) error { // TODO: Use arrayEncoder from a pool. arr := &arrayEncoder{} @@ -101,7 +119,14 @@ func (m *objectEncoder) AddReflected(k string, v interface{}) error { // OpenNamespace opens an isolated namespace where all subsequent fields will // be added. func (m *objectEncoder) OpenNamespace(k string) { - // TODO + + keyValue := make([]log.KeyValue, 0, 5) + s := &newNameSpace{ + ns: k, + kv: keyValue, + } + m.cur.next = s + m.cur = s } func (m *objectEncoder) AddComplex64(k string, v complex64) { From 69c938363f3d0dbfca56c55e0073770efc6a5246 Mon Sep 17 00:00:00 2001 From: khushijain21 Date: Fri, 7 Jun 2024 14:42:36 +0530 Subject: [PATCH 2/7] implement opennamespace --- bridges/otelzap/core.go | 3 +- bridges/otelzap/encoder.go | 42 ++++++++------- bridges/otelzap/encoder_test.go | 94 +++++++++++++++++++++++++++++++-- 3 files changed, 115 insertions(+), 24 deletions(-) diff --git a/bridges/otelzap/core.go b/bridges/otelzap/core.go index f90be80de70..64d0cf64b5c 100644 --- a/bridges/otelzap/core.go +++ b/bridges/otelzap/core.go @@ -183,7 +183,8 @@ func convertField(fields []zapcore.Field) (context.Context, []log.KeyValue) { field.AddTo(enc) } - return ctx, enc.kv + enc.getEncodedResult(enc.root) + return ctx, enc.root.kv } func convertLevel(level zapcore.Level) log.Severity { diff --git a/bridges/otelzap/encoder.go b/bridges/otelzap/encoder.go index 9caa8b13581..6f75e3ff913 100644 --- a/bridges/otelzap/encoder.go +++ b/bridges/otelzap/encoder.go @@ -33,21 +33,24 @@ type objectEncoder struct { cur *newNameSpace } -// nolint:unused func newObjectEncoder(len int) *objectEncoder { keyval := make([]log.KeyValue, 0, len) - - return &objectEncoder{ + m := &newNameSpace{ kv: keyval, } + return &objectEncoder{ + root: m, + cur: m, + } } // It iterates to the end of the linked list and appends namespace data. -func (m *objectEncoder) getObjValue(o *newNameSpace) { +// Run this function before accessing complete result. +func (m *objectEncoder) getEncodedResult(o *newNameSpace) { if o.next == nil { return } - m.getObjValue(o.next) + m.getEncodedResult(o.next) o.kv = append(o.kv, log.Map(o.next.ns, o.next.kv...)) } @@ -55,7 +58,7 @@ func (m *objectEncoder) AddArray(key string, v zapcore.ArrayMarshaler) error { // TODO: Use arrayEncoder from a pool. arr := &arrayEncoder{} err := v.MarshalLogArray(arr) - m.kv = append(m.kv, log.Slice(key, arr.elems...)) + m.cur.kv = append(m.cur.kv, log.Slice(key, arr.elems...)) return err } @@ -63,20 +66,21 @@ func (m *objectEncoder) AddObject(k string, v zapcore.ObjectMarshaler) error { // TODO: Use objectEncoder from a pool. newobj := newObjectEncoder(2) err := v.MarshalLogObject(newobj) - m.kv = append(m.kv, log.Map(k, newobj.kv...)) + newobj.getEncodedResult(newobj.root) + m.cur.kv = append(m.cur.kv, log.Map(k, newobj.root.kv...)) return err } func (m *objectEncoder) AddBinary(k string, v []byte) { - m.kv = append(m.kv, log.Bytes(k, v)) + m.cur.kv = append(m.cur.kv, log.Bytes(k, v)) } func (m *objectEncoder) AddByteString(k string, v []byte) { - m.kv = append(m.kv, log.String(k, string(v))) + m.cur.kv = append(m.cur.kv, log.String(k, string(v))) } func (m *objectEncoder) AddBool(k string, v bool) { - m.kv = append(m.kv, log.Bool(k, v)) + m.cur.kv = append(m.cur.kv, log.Bool(k, v)) } func (m *objectEncoder) AddDuration(k string, v time.Duration) { @@ -86,27 +90,27 @@ func (m *objectEncoder) AddDuration(k string, v time.Duration) { func (m *objectEncoder) AddComplex128(k string, v complex128) { r := log.Float64("r", real(v)) i := log.Float64("i", imag(v)) - m.kv = append(m.kv, log.Map(k, r, i)) + m.cur.kv = append(m.cur.kv, log.Map(k, r, i)) } func (m *objectEncoder) AddFloat64(k string, v float64) { - m.kv = append(m.kv, log.Float64(k, v)) + m.cur.kv = append(m.cur.kv, log.Float64(k, v)) } func (m *objectEncoder) AddInt64(k string, v int64) { - m.kv = append(m.kv, log.Int64(k, v)) + m.cur.kv = append(m.cur.kv, log.Int64(k, v)) } func (m *objectEncoder) AddInt(k string, v int) { - m.kv = append(m.kv, log.Int(k, v)) + m.cur.kv = append(m.cur.kv, log.Int(k, v)) } func (m *objectEncoder) AddString(k string, v string) { - m.kv = append(m.kv, log.String(k, v)) + m.cur.kv = append(m.cur.kv, log.String(k, v)) } func (m *objectEncoder) AddUint64(k string, v uint64) { - m.kv = append(m.kv, + m.cur.kv = append(m.cur.kv, log.KeyValue{ Key: k, Value: assignUintValue(v), @@ -114,7 +118,7 @@ func (m *objectEncoder) AddUint64(k string, v uint64) { } func (m *objectEncoder) AddReflected(k string, v interface{}) error { - m.kv = append(m.kv, + m.cur.kv = append(m.cur.kv, log.KeyValue{ Key: k, Value: convertValue(v), @@ -125,7 +129,6 @@ func (m *objectEncoder) AddReflected(k string, v interface{}) error { // OpenNamespace opens an isolated namespace where all subsequent fields will // be added. func (m *objectEncoder) OpenNamespace(k string) { - keyValue := make([]log.KeyValue, 0, 5) s := &newNameSpace{ ns: k, @@ -204,7 +207,8 @@ func (a *arrayEncoder) AppendObject(v zapcore.ObjectMarshaler) error { // TODO: Use objectEncoder from a pool. m := newObjectEncoder(2) err := v.MarshalLogObject(m) - a.elems = append(a.elems, log.MapValue(m.kv...)) + m.getEncodedResult(m.root) + a.elems = append(a.elems, log.MapValue(m.root.kv...)) return err } diff --git a/bridges/otelzap/encoder_test.go b/bridges/otelzap/encoder_test.go index 944136e4af1..763e2795b29 100644 --- a/bridges/otelzap/encoder_test.go +++ b/bridges/otelzap/encoder_test.go @@ -182,14 +182,52 @@ func TestObjectEncoder(t *testing.T) { f: func(e zapcore.ObjectEncoder) { e.AddComplex64("k", 1+2i) }, expected: map[string]interface{}{"i": float64(2), "r": float64(1)}, }, + { + desc: "OpenNamespace", + f: func(e zapcore.ObjectEncoder) { + e.OpenNamespace("k") + e.AddInt("foo", 1) + e.OpenNamespace("middle") + e.AddInt("foo", 2) + e.OpenNamespace("inner") + e.AddInt("foo", 3) + }, + expected: map[string]interface{}{ + "foo": int64(1), + "middle": map[string]interface{}{ + "foo": int64(2), + "inner": map[string]interface{}{ + "foo": int64(3), + }, + }, + }, + }, + { + desc: "object (with nested namespace) then string", + f: func(e zapcore.ObjectEncoder) { + e.OpenNamespace("k") + assert.NoError(t, e.AddObject("obj", maybeNamespace{true})) + e.AddString("not-obj", "should-be-outside-obj") + }, + expected: map[string]interface{}{ + "obj": map[string]interface{}{ + "obj-out": "obj-outside-namespace", + "obj-namespace": map[string]interface{}{ + "obj-in": "obj-inside-namespace", + }, + }, + "not-obj": "should-be-outside-obj", + }, + }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { enc := newObjectEncoder(1) tt.f(enc) - require.Len(t, enc.kv, 1) - assert.Equal(t, tt.expected, value2Result((enc.kv[0].Value)), "Unexpected encoder output.") + enc.getEncodedResult(enc.root) + require.Len(t, enc.root.kv, 1) + assert.Equal(t, tt.expected, value2Result((enc.root.kv[0].Value)), "Unexpected encoder output.") }) } } @@ -221,6 +259,42 @@ func TestArrayEncoder(t *testing.T) { }, expected: map[string]interface{}{"foo": int64(5)}, }, + {desc: "object (no nested namespace) then string", + f: func(e zapcore.ArrayEncoder) { + err := e.AppendArray(zapcore.ArrayMarshalerFunc(func(inner zapcore.ArrayEncoder) error { + err := inner.AppendObject(maybeNamespace{false}) + inner.AppendString("should-be-outside-obj") + return err + })) + assert.NoError(t, err) + }, + expected: []interface{}{ + map[string]interface{}{ + "obj-out": "obj-outside-namespace", + }, + "should-be-outside-obj", + }, + }, + { + desc: "object (with nested namespace) then string", + f: func(e zapcore.ArrayEncoder) { + err := e.AppendArray(zapcore.ArrayMarshalerFunc(func(inner zapcore.ArrayEncoder) error { + err := inner.AppendObject(maybeNamespace{true}) + inner.AppendString("should-be-outside-obj") + return err + })) + assert.NoError(t, err) + }, + expected: []interface{}{ + map[string]interface{}{ + "obj-out": "obj-outside-namespace", + "obj-namespace": map[string]interface{}{ + "obj-in": "obj-inside-namespace", + }, + }, + "should-be-outside-obj", + }, + }, {"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}, @@ -252,8 +326,8 @@ func TestArrayEncoder(t *testing.T) { tt.f(arr) return nil })), "Expected AddArray to succeed.") - - assert.Equal(t, []interface{}{tt.expected, tt.expected}, value2Result(enc.kv[0].Value), "Unexpected encoder output.") + enc.getEncodedResult(enc.root) + assert.Equal(t, []interface{}{tt.expected, tt.expected}, value2Result(enc.root.kv[0].Value), "Unexpected encoder output.") }) } } @@ -304,6 +378,18 @@ func (l loggable) MarshalLogArray(enc zapcore.ArrayEncoder) error { return nil } +// maybeNamespace is an ObjectMarshaler that sometimes opens a namespace. +type maybeNamespace struct{ bool } + +func (m maybeNamespace) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("obj-out", "obj-outside-namespace") + if m.bool { + enc.OpenNamespace("obj-namespace") + enc.AddString("obj-in", "obj-inside-namespace") + } + return nil +} + func value2Result(v log.Value) any { switch v.Kind() { case log.KindBool: From 69148df4104c5aabc0675027ab4b2ebcff91d26f Mon Sep 17 00:00:00 2001 From: khushijain21 Date: Tue, 18 Jun 2024 17:23:00 +0530 Subject: [PATCH 3/7] fixt linting error --- bridges/otelzap/encoder_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bridges/otelzap/encoder_test.go b/bridges/otelzap/encoder_test.go index 763e2795b29..d1624a55b20 100644 --- a/bridges/otelzap/encoder_test.go +++ b/bridges/otelzap/encoder_test.go @@ -259,7 +259,8 @@ func TestArrayEncoder(t *testing.T) { }, expected: map[string]interface{}{"foo": int64(5)}, }, - {desc: "object (no nested namespace) then string", + { + desc: "object (no nested namespace) then string", f: func(e zapcore.ArrayEncoder) { err := e.AppendArray(zapcore.ArrayMarshalerFunc(func(inner zapcore.ArrayEncoder) error { err := inner.AppendObject(maybeNamespace{false}) From 4fbc5d89b1eba4caae9bdf9f516c975b5735182c Mon Sep 17 00:00:00 2001 From: Khushi Jain Date: Tue, 18 Jun 2024 17:56:32 +0530 Subject: [PATCH 4/7] Update bridges/otelzap/encoder.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert Pająk --- bridges/otelzap/encoder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/otelzap/encoder.go b/bridges/otelzap/encoder.go index 6f75e3ff913..2d85b644b5b 100644 --- a/bridges/otelzap/encoder.go +++ b/bridges/otelzap/encoder.go @@ -18,7 +18,7 @@ var ( _ zapcore.ArrayEncoder = (*arrayEncoder)(nil) ) -type newNameSpace struct { +type namespace struct { ns string kv []log.KeyValue next *newNameSpace From fdf42f9f7159dd0c4e866d3f6d8d47162f5d58d5 Mon Sep 17 00:00:00 2001 From: Khushi Jain Date: Tue, 18 Jun 2024 17:56:39 +0530 Subject: [PATCH 5/7] Update bridges/otelzap/encoder.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert Pająk --- bridges/otelzap/encoder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/otelzap/encoder.go b/bridges/otelzap/encoder.go index 2d85b644b5b..6f4fe339111 100644 --- a/bridges/otelzap/encoder.go +++ b/bridges/otelzap/encoder.go @@ -19,7 +19,7 @@ var ( ) type namespace struct { - ns string + name string kv []log.KeyValue next *newNameSpace } From 7d0adb302fecc8e2fda35a361ceecbbe2a787b14 Mon Sep 17 00:00:00 2001 From: Khushi Jain Date: Tue, 18 Jun 2024 17:56:48 +0530 Subject: [PATCH 6/7] Update bridges/otelzap/encoder.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert Pająk --- bridges/otelzap/encoder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/otelzap/encoder.go b/bridges/otelzap/encoder.go index 6f4fe339111..9cc23c32ab4 100644 --- a/bridges/otelzap/encoder.go +++ b/bridges/otelzap/encoder.go @@ -20,7 +20,7 @@ var ( type namespace struct { name string - kv []log.KeyValue + attrs []log.KeyValue next *newNameSpace } From 56f213cb30d489376544537422d86a27b7ea91d2 Mon Sep 17 00:00:00 2001 From: khushijain21 Date: Tue, 18 Jun 2024 18:03:07 +0530 Subject: [PATCH 7/7] feedback accomodated --- bridges/otelzap/core.go | 4 +-- bridges/otelzap/encoder.go | 54 ++++++++++++++++----------------- bridges/otelzap/encoder_test.go | 10 +++--- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/bridges/otelzap/core.go b/bridges/otelzap/core.go index 64d0cf64b5c..2b9da0892f9 100644 --- a/bridges/otelzap/core.go +++ b/bridges/otelzap/core.go @@ -183,8 +183,8 @@ func convertField(fields []zapcore.Field) (context.Context, []log.KeyValue) { field.AddTo(enc) } - enc.getEncodedResult(enc.root) - return ctx, enc.root.kv + enc.calculate(enc.root) + return ctx, enc.root.attrs } func convertLevel(level zapcore.Level) log.Severity { diff --git a/bridges/otelzap/encoder.go b/bridges/otelzap/encoder.go index 9cc23c32ab4..220d113aff1 100644 --- a/bridges/otelzap/encoder.go +++ b/bridges/otelzap/encoder.go @@ -19,24 +19,24 @@ var ( ) type namespace struct { - name string + name string attrs []log.KeyValue - next *newNameSpace + next *namespace } // objectEncoder implements zapcore.ObjectEncoder. // It encodes given fields to OTel key-values. type objectEncoder struct { // root is a pointer to the default namespace - root *newNameSpace + root *namespace // cur is a pointer to the namespace we're currently writing to. - cur *newNameSpace + cur *namespace } func newObjectEncoder(len int) *objectEncoder { keyval := make([]log.KeyValue, 0, len) - m := &newNameSpace{ - kv: keyval, + m := &namespace{ + attrs: keyval, } return &objectEncoder{ root: m, @@ -46,19 +46,19 @@ func newObjectEncoder(len int) *objectEncoder { // It iterates to the end of the linked list and appends namespace data. // Run this function before accessing complete result. -func (m *objectEncoder) getEncodedResult(o *newNameSpace) { +func (m *objectEncoder) calculate(o *namespace) { if o.next == nil { return } - m.getEncodedResult(o.next) - o.kv = append(o.kv, log.Map(o.next.ns, o.next.kv...)) + m.calculate(o.next) + o.attrs = append(o.attrs, log.Map(o.next.name, o.next.attrs...)) } func (m *objectEncoder) AddArray(key string, v zapcore.ArrayMarshaler) error { // TODO: Use arrayEncoder from a pool. arr := &arrayEncoder{} err := v.MarshalLogArray(arr) - m.cur.kv = append(m.cur.kv, log.Slice(key, arr.elems...)) + m.cur.attrs = append(m.cur.attrs, log.Slice(key, arr.elems...)) return err } @@ -66,21 +66,21 @@ func (m *objectEncoder) AddObject(k string, v zapcore.ObjectMarshaler) error { // TODO: Use objectEncoder from a pool. newobj := newObjectEncoder(2) err := v.MarshalLogObject(newobj) - newobj.getEncodedResult(newobj.root) - m.cur.kv = append(m.cur.kv, log.Map(k, newobj.root.kv...)) + newobj.calculate(newobj.root) + m.cur.attrs = append(m.cur.attrs, log.Map(k, newobj.root.attrs...)) return err } func (m *objectEncoder) AddBinary(k string, v []byte) { - m.cur.kv = append(m.cur.kv, log.Bytes(k, v)) + m.cur.attrs = append(m.cur.attrs, log.Bytes(k, v)) } func (m *objectEncoder) AddByteString(k string, v []byte) { - m.cur.kv = append(m.cur.kv, log.String(k, string(v))) + m.cur.attrs = append(m.cur.attrs, log.String(k, string(v))) } func (m *objectEncoder) AddBool(k string, v bool) { - m.cur.kv = append(m.cur.kv, log.Bool(k, v)) + m.cur.attrs = append(m.cur.attrs, log.Bool(k, v)) } func (m *objectEncoder) AddDuration(k string, v time.Duration) { @@ -90,27 +90,27 @@ func (m *objectEncoder) AddDuration(k string, v time.Duration) { func (m *objectEncoder) AddComplex128(k string, v complex128) { r := log.Float64("r", real(v)) i := log.Float64("i", imag(v)) - m.cur.kv = append(m.cur.kv, log.Map(k, r, i)) + m.cur.attrs = append(m.cur.attrs, log.Map(k, r, i)) } func (m *objectEncoder) AddFloat64(k string, v float64) { - m.cur.kv = append(m.cur.kv, log.Float64(k, v)) + m.cur.attrs = append(m.cur.attrs, log.Float64(k, v)) } func (m *objectEncoder) AddInt64(k string, v int64) { - m.cur.kv = append(m.cur.kv, log.Int64(k, v)) + m.cur.attrs = append(m.cur.attrs, log.Int64(k, v)) } func (m *objectEncoder) AddInt(k string, v int) { - m.cur.kv = append(m.cur.kv, log.Int(k, v)) + m.cur.attrs = append(m.cur.attrs, log.Int(k, v)) } func (m *objectEncoder) AddString(k string, v string) { - m.cur.kv = append(m.cur.kv, log.String(k, v)) + m.cur.attrs = append(m.cur.attrs, log.String(k, v)) } func (m *objectEncoder) AddUint64(k string, v uint64) { - m.cur.kv = append(m.cur.kv, + m.cur.attrs = append(m.cur.attrs, log.KeyValue{ Key: k, Value: assignUintValue(v), @@ -118,7 +118,7 @@ func (m *objectEncoder) AddUint64(k string, v uint64) { } func (m *objectEncoder) AddReflected(k string, v interface{}) error { - m.cur.kv = append(m.cur.kv, + m.cur.attrs = append(m.cur.attrs, log.KeyValue{ Key: k, Value: convertValue(v), @@ -130,9 +130,9 @@ func (m *objectEncoder) AddReflected(k string, v interface{}) error { // be added. func (m *objectEncoder) OpenNamespace(k string) { keyValue := make([]log.KeyValue, 0, 5) - s := &newNameSpace{ - ns: k, - kv: keyValue, + s := &namespace{ + name: k, + attrs: keyValue, } m.cur.next = s m.cur = s @@ -207,8 +207,8 @@ func (a *arrayEncoder) AppendObject(v zapcore.ObjectMarshaler) error { // TODO: Use objectEncoder from a pool. m := newObjectEncoder(2) err := v.MarshalLogObject(m) - m.getEncodedResult(m.root) - a.elems = append(a.elems, log.MapValue(m.root.kv...)) + m.calculate(m.root) + a.elems = append(a.elems, log.MapValue(m.root.attrs...)) return err } diff --git a/bridges/otelzap/encoder_test.go b/bridges/otelzap/encoder_test.go index d1624a55b20..4e284b82d35 100644 --- a/bridges/otelzap/encoder_test.go +++ b/bridges/otelzap/encoder_test.go @@ -225,9 +225,9 @@ func TestObjectEncoder(t *testing.T) { t.Run(tt.desc, func(t *testing.T) { enc := newObjectEncoder(1) tt.f(enc) - enc.getEncodedResult(enc.root) - require.Len(t, enc.root.kv, 1) - assert.Equal(t, tt.expected, value2Result((enc.root.kv[0].Value)), "Unexpected encoder output.") + enc.calculate(enc.root) + require.Len(t, enc.root.attrs, 1) + assert.Equal(t, tt.expected, value2Result((enc.root.attrs[0].Value)), "Unexpected encoder output.") }) } } @@ -327,8 +327,8 @@ func TestArrayEncoder(t *testing.T) { tt.f(arr) return nil })), "Expected AddArray to succeed.") - enc.getEncodedResult(enc.root) - assert.Equal(t, []interface{}{tt.expected, tt.expected}, value2Result(enc.root.kv[0].Value), "Unexpected encoder output.") + enc.calculate(enc.root) + assert.Equal(t, []interface{}{tt.expected, tt.expected}, value2Result(enc.root.attrs[0].Value), "Unexpected encoder output.") }) } }