Skip to content

Commit

Permalink
Introduce Str + Strs generic string helpers
Browse files Browse the repository at this point in the history
Implements #1275 by adding new `zapfield.Str` and `zapfield.Strs` types, which operate on generic string-like types and use the underlying `zap.String` and `zap.Strings` codepaths.

In #1275, the decision was to put these in a separate `zap/exp/zapfield` package and I didn't see an existing one, so I made a new `zapfield` package.
  • Loading branch information
bcspragu committed May 1, 2023
1 parent 4451b47 commit 974774f
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 0 deletions.
26 changes: 26 additions & 0 deletions exp/zapfield/zapfield.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Package zapfield provides experimental zap.Field helpers whose APIs may be unstable.
package zapfield

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

// Str constructs a field with the given string-like key and value.
func Str[K ~string, V ~string](k K, v V) zap.Field {
return zap.String(string(k), string(v))
}

type stringArray[T ~string] []T

func (a stringArray[T]) MarshalLogArray(enc zapcore.ArrayEncoder) error {
for i := range a {
enc.AppendString(string(a[i]))
}
return nil
}

// Strs constructs a field that carries a slice of string-like values.
func Strs[K ~string, V ~[]S, S ~string](k K, v V) zap.Field {
return zap.Array(string(k), stringArray[S](v))
}
64 changes: 64 additions & 0 deletions exp/zapfield/zapfield_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package zapfield

import (
"sync"
"testing"

"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

type (
MyKey string
MyValue string
MyValues []MyValue
)

func TestFieldConstructors(t *testing.T) {
var (
key = MyKey("test key")
value = MyValue("test value")
values = []MyValue{
MyValue("test value 1"),
MyValue("test value 2"),
}
)

tests := []struct {
name string
field zap.Field
expect zap.Field
}{
{"Str", zap.Field{Type: zapcore.StringType, Key: "test key", String: "test value"}, Str(key, value)},
{"Strs", zap.Array("test key", stringArray[MyValue]{"test value 1", "test value 2"}), Strs(key, values)},
}

for _, tt := range tests {
if !assert.Equal(t, tt.expect, tt.field, "Unexpected output from convenience field constructor %s.", tt.name) {
t.Logf("type expected: %T\nGot: %T", tt.expect.Interface, tt.field.Interface)
}
assertCanBeReused(t, tt.field)
}

}

func assertCanBeReused(t testing.TB, field zap.Field) {
var wg sync.WaitGroup

for i := 0; i < 100; i++ {
enc := zapcore.NewMapObjectEncoder()

// Ensure using the field in multiple encoders in separate goroutines
// does not cause any races or panics.
wg.Add(1)
go func() {
defer wg.Done()
assert.NotPanics(t, func() {
field.AddTo(enc)
}, "Reusing a field should not cause issues")
}()
}

wg.Wait()
}

0 comments on commit 974774f

Please sign in to comment.