Skip to content

Commit

Permalink
feat(field): add zap.Dict to Field constructor list (#1297)
Browse files Browse the repository at this point in the history
  • Loading branch information
hhk7734 committed Aug 14, 2023
1 parent 5da8736 commit fcfd368
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 0 deletions.
13 changes: 13 additions & 0 deletions example_test.go
Expand Up @@ -358,3 +358,16 @@ func ExampleWrapCore_wrap() {
// {"level":"info","msg":"doubled"}
// {"level":"info","msg":"doubled"}
}

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

logger.Info("login event",
zap.Dict("event",
zap.Int("id", 123),
zap.String("name", "jane"),
zap.String("status", "pending")))
// Output:
// {"level":"info","msg":"login event","event":{"id":123,"name":"jane","status":"pending"}}
}
22 changes: 22 additions & 0 deletions field.go
Expand Up @@ -410,6 +410,26 @@ func Inline(val zapcore.ObjectMarshaler) Field {
}
}

// Dict constructs a field containing the provided key-value pairs.
// It acts similar to [Object], but with the fields specified as arguments.
func Dict(key string, val ...Field) Field {
return dictField(key, val)
}

// We need a function with the signature (string, T) for zap.Any.
func dictField(key string, val []Field) Field {
return Object(key, dictObject(val))
}

type dictObject []Field

func (d dictObject) MarshalLogObject(enc zapcore.ObjectEncoder) error {
for _, f := range d {
f.AddTo(enc)
}
return nil
}

// We discovered an issue where zap.Any can cause a performance degradation
// when used in new goroutines.
//
Expand Down Expand Up @@ -462,6 +482,8 @@ func Any(key string, value interface{}) Field {
c = anyFieldC[zapcore.ObjectMarshaler](Object)
case zapcore.ArrayMarshaler:
c = anyFieldC[zapcore.ArrayMarshaler](Array)
case []Field:
c = anyFieldC[[]Field](dictField)
case bool:
c = anyFieldC[bool](Bool)
case *bool:
Expand Down
25 changes: 25 additions & 0 deletions field_test.go
Expand Up @@ -127,6 +127,7 @@ func TestFieldConstructors(t *testing.T) {
{"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:Dict", Any("k", []Field{String("k", "v")}), Dict("k", String("k", "v"))},
{"Any:Stringer", Any("k", addr), Stringer("k", addr)},
{"Any:Bool", Any("k", true), Bool("k", true)},
{"Any:Bools", Any("k", []bool{true}), Bools("k", []bool{true})},
Expand Down Expand Up @@ -288,3 +289,27 @@ func TestStackSkipFieldWithSkip(t *testing.T) {
assert.Equal(t, takeStacktrace(1), f.String, "Unexpected stack trace")
assertCanBeReused(t, f)
}

func TestDict(t *testing.T) {
tests := []struct {
desc string
field Field
expected any
}{
{"empty", Dict(""), map[string]any{}},
{"single", Dict("", String("k", "v")), map[string]any{"k": "v"}},
{"multiple", Dict("", String("k", "v"), String("k2", "v2")), map[string]any{"k": "v", "k2": "v2"}},
}

for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
enc := zapcore.NewMapObjectEncoder()
tt.field.Key = "k"
tt.field.AddTo(enc)
assert.Equal(t, tt.expected, enc.Fields["k"], "unexpected map contents")
assert.Len(t, enc.Fields, 1, "found extra keys in map: %v", enc.Fields)

assertCanBeReused(t, tt.field)
})
}
}
10 changes: 10 additions & 0 deletions zapcore/field_test.go
Expand Up @@ -309,6 +309,16 @@ func TestEquals(t *testing.T) {
b: zap.Any("k", map[string]string{"a": "d"}),
want: false,
},
{
a: zap.Dict("k", zap.String("a", "b")),
b: zap.Dict("k", zap.String("a", "b")),
want: true,
},
{
a: zap.Dict("k", zap.String("a", "b")),
b: zap.Dict("k", zap.String("a", "d")),
want: false,
},
}

for _, tt := range tests {
Expand Down

0 comments on commit fcfd368

Please sign in to comment.