Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multi-field encoding using zap.Inline #912

Merged
merged 4 commits into from Mar 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 39 additions & 0 deletions example_test.go
Expand Up @@ -165,6 +165,45 @@ func ExampleNamespace() {
// {"level":"info","msg":"tracked some metrics","metrics":{"counter":1}}
}

type addr struct {
IP string
Port int
}

type request struct {
URL string
Listen addr
Remote addr
}

func (a addr) MarshalLogObject(enc zapcore.ObjectEncoder) error {
enc.AddString("ip", a.IP)
enc.AddInt("port", a.Port)
return nil
}

func (r request) MarshalLogObject(enc zapcore.ObjectEncoder) error {
enc.AddString("url", r.URL)
zap.Inline(r.Listen).AddTo(enc)
return enc.AddObject("remote", r.Remote)
}

func ExampleObject() {
logger := zap.NewExample()
defer logger.Sync()

req := &request{
URL: "/test",
Listen: addr{"127.0.0.1", 8080},
Remote: addr{"127.0.0.1", 31200},
}
logger.Info("new request, in nested object", zap.Object("req", req))
logger.Info("new request, inline", zap.Inline(req))
// Output:
// {"level":"info","msg":"new request, in nested object","req":{"url":"/test","ip":"127.0.0.1","port":8080,"remote":{"ip":"127.0.0.1","port":31200}}}
// {"level":"info","msg":"new request, inline","url":"/test","ip":"127.0.0.1","port":8080,"remote":{"ip":"127.0.0.1","port":31200}}
}

func ExampleNewStdLog() {
logger := zap.NewExample()
defer logger.Sync()
Expand Down
10 changes: 10 additions & 0 deletions field.go
Expand Up @@ -400,6 +400,16 @@ func Object(key string, val zapcore.ObjectMarshaler) Field {
return Field{Key: key, Type: zapcore.ObjectMarshalerType, Interface: val}
}

// Inline constructs a Field that is similar to Object, but it
// will add the elements of the provided ObjectMarshaler to the
// current namespace.
func Inline(val zapcore.ObjectMarshaler) Field {
return zapcore.Field{
Type: zapcore.InlineMarshalerType,
Interface: val,
}
}

// Any takes a key and an arbitrary value and chooses the best way to represent
// them as a field, falling back to a reflection-based approach only if
// necessary.
Expand Down
1 change: 1 addition & 0 deletions field_test.go
Expand Up @@ -123,6 +123,7 @@ func TestFieldConstructors(t *testing.T) {
{"Reflect", Field{Key: "k", Type: zapcore.ReflectType}, Reflect("k", nil)},
{"Stringer", Field{Key: "k", Type: zapcore.StringerType, Interface: addr}, Stringer("k", addr)},
{"Object", Field{Key: "k", Type: zapcore.ObjectMarshalerType, Interface: name}, Object("k", name)},
{"Inline", Field{Type: zapcore.InlineMarshalerType, Interface: name}, Inline(name)},
{"Any:ObjectMarshaler", Any("k", name), Object("k", name)},
{"Any:ArrayMarshaler", Any("k", bools([]bool{true})), Array("k", bools([]bool{true}))},
{"Any:Stringer", Any("k", addr), Stringer("k", addr)},
Expand Down
5 changes: 5 additions & 0 deletions zapcore/field.go
Expand Up @@ -39,6 +39,9 @@ const (
ArrayMarshalerType
// ObjectMarshalerType indicates that the field carries an ObjectMarshaler.
ObjectMarshalerType
// InlineMarshalerType indicates that the field carries an ObjectMarshaler
// that should be inlined.
InlineMarshalerType
// BinaryType indicates that the field carries an opaque binary blob.
BinaryType
// BoolType indicates that the field carries a bool.
Expand Down Expand Up @@ -115,6 +118,8 @@ func (f Field) AddTo(enc ObjectEncoder) {
err = enc.AddArray(f.Key, f.Interface.(ArrayMarshaler))
case ObjectMarshalerType:
err = enc.AddObject(f.Key, f.Interface.(ObjectMarshaler))
case InlineMarshalerType:
err = f.Interface.(ObjectMarshaler).MarshalLogObject(enc)
case BinaryType:
enc.AddBinary(f.Key, f.Interface.([]byte))
case BoolType:
Expand Down
23 changes: 22 additions & 1 deletion zapcore/field_test.go
Expand Up @@ -111,6 +111,7 @@ func TestFieldAddingError(t *testing.T) {
}{
{t: ArrayMarshalerType, iface: users(-1), want: []interface{}{}, err: "too few users"},
{t: ObjectMarshalerType, iface: users(-1), want: map[string]interface{}{}, err: "too few users"},
{t: InlineMarshalerType, iface: users(-1), want: nil, err: "too few users"},
{t: StringerType, iface: obj{}, want: empty, err: "PANIC=interface conversion: zapcore_test.obj is not fmt.Stringer: missing method String"},
{t: StringerType, iface: &obj{1}, want: empty, err: "PANIC=panic with string"},
{t: StringerType, iface: &obj{2}, want: empty, err: "PANIC=panic with error"},
Expand All @@ -136,7 +137,6 @@ func TestFields(t *testing.T) {
}{
{t: ArrayMarshalerType, iface: users(2), want: []interface{}{"user", "user"}},
{t: ObjectMarshalerType, iface: users(2), want: map[string]interface{}{"users": 2}},
{t: BinaryType, iface: []byte("foo"), want: []byte("foo")},
{t: BoolType, i: 0, want: false},
{t: ByteStringType, iface: []byte("foo"), want: "foo"},
{t: Complex128Type, iface: 1 + 2i, want: 1 + 2i},
Expand Down Expand Up @@ -180,6 +180,27 @@ func TestFields(t *testing.T) {
}
}

func TestInlineMarshaler(t *testing.T) {
enc := NewMapObjectEncoder()

topLevelStr := Field{Key: "k", Type: StringType, String: "s"}
topLevelStr.AddTo(enc)

inlineObj := Field{Key: "ignored", Type: InlineMarshalerType, Interface: users(10)}
inlineObj.AddTo(enc)

nestedObj := Field{Key: "nested", Type: ObjectMarshalerType, Interface: users(11)}
nestedObj.AddTo(enc)

assert.Equal(t, map[string]interface{}{
"k": "s",
"users": 10,
"nested": map[string]interface{}{
"users": 11,
},
}, enc.Fields)
}

func TestEquals(t *testing.T) {
// Values outside the UnixNano range were encoded incorrectly (#737, #803).
timeOutOfRangeHigh := time.Unix(0, math.MaxInt64).Add(time.Nanosecond)
Expand Down