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 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
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
9 changes: 9 additions & 0 deletions field.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,15 @@ func Object(key string, val zapcore.ObjectMarshaler) Field {
return Field{Key: key, Type: zapcore.ObjectMarshalerType, Interface: val}
}

// Inline is similar to Object, but does not nest the object under a field
// name, but adds the fields to the current namespace inline.
prashantv marked this conversation as resolved.
Show resolved Hide resolved
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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