Skip to content

Commit

Permalink
Add Stringers field constructor for slices of Stringer-compatible obj…
Browse files Browse the repository at this point in the history
…ects (#1155)

Add a new Zap field constructor that encodes a slice of zero or more
objects which implement fmt.Stringer interface into a Zap array.

Usage:

```go
//	type Request struct{ ... }
//	func (a Request) String() string
//
//	var requests []Request = ...
//	logger.Info("sending requests", zap.Stringers("requests", requests))
```

This API uses a type parameter so that users don't have to
build a `[]fmt.Stringer` out of a `[]Request`,
and can pass the `[]Request` directly to the API.

We may need a StringerValues constructor to mirror ObjectValues in the future.

Credits: @zmanji, @abhinav for the suggestions
  • Loading branch information
saurabh95 authored Aug 23, 2022
1 parent 1e46f5e commit 23d6cc7
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 1 deletion.
34 changes: 33 additions & 1 deletion array_go118.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@

package zap

import "go.uber.org/zap/zapcore"
import (
"fmt"

"go.uber.org/zap/zapcore"
)

// Objects constructs a field with the given key, holding a list of the
// provided objects that can be marshaled by Zap.
Expand Down Expand Up @@ -122,3 +126,31 @@ func (os objectValues[T, P]) MarshalLogArray(arr zapcore.ArrayEncoder) error {
}
return nil
}

// Stringers constructs a field with the given key, holding a list of the
// output provided by the value's String method
//
// Given an object that implements String on the value receiver, you
// can log a slice of those objects with Objects like so:
//
// type Request struct{ ... }
// func (a Request) String() string
//
// var requests []Request = ...
// logger.Info("sending requests", zap.Stringers("requests", requests))
//
// Note that these objects must implement fmt.Stringer directly.
// That is, if you're trying to marshal a []Request, the String method
// must be declared on the Request type, not its pointer (*Request).
func Stringers[T fmt.Stringer](key string, values []T) Field {
return Array(key, stringers[T](values))
}

type stringers[T fmt.Stringer] []T

func (os stringers[T]) MarshalLogArray(arr zapcore.ArrayEncoder) error {
for _, o := range os {
arr.AppendString(o.String())
}
return nil
}
59 changes: 59 additions & 0 deletions array_go118_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ package zap

import (
"errors"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -179,3 +180,61 @@ func TestObjectsAndObjectValues_marshalError(t *testing.T) {
})
}
}

type stringerObject struct {
value string
}

func (s stringerObject) String() string {
return s.value
}

func TestStringers(t *testing.T) {
t.Parallel()

tests := []struct {
desc string
give Field
want []any
}{
{
desc: "Stringers",
give: Stringers("", []stringerObject{
{value: "foo"},
{value: "bar"},
{value: "baz"},
}),
want: []any{
"foo",
"bar",
"baz",
},
},
{
desc: "Stringers with []fmt.Stringer",
give: Stringers("", []fmt.Stringer{
stringerObject{value: "foo"},
stringerObject{value: "bar"},
stringerObject{value: "baz"},
}),
want: []any{
"foo",
"bar",
"baz",
},
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.desc, func(t *testing.T) {
t.Parallel()

tt.give.Key = "k"

enc := zapcore.NewMapObjectEncoder()
tt.give.AddTo(enc)
assert.Equal(t, tt.want, enc.Fields["k"])
})
}
}

0 comments on commit 23d6cc7

Please sign in to comment.