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

Added zapcore.TimeEncoderOfLayout #629

Merged
merged 11 commits into from Jul 7, 2020
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -8,5 +8,6 @@ require (
go.uber.org/atomic v1.6.0
go.uber.org/multierr v1.5.0
golang.org/x/lint v0.0.0-20190930215403-16217165b5de
gopkg.in/yaml.v2 v2.2.2
honnef.co/go/tools v0.0.1-2019.2.3
)
38 changes: 38 additions & 0 deletions zapcore/encoder.go
Expand Up @@ -21,6 +21,7 @@
package zapcore

import (
"encoding/json"
"time"

"go.uber.org/zap/buffer"
Expand Down Expand Up @@ -151,6 +152,14 @@ func RFC3339NanoTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) {
encodeTimeLayout(t, time.RFC3339Nano, enc)
}

// TimeEncoderOfLayout returns TimeEncoder which serializes a time.Time using
// given layout.
func TimeEncoderOfLayout(layout string) TimeEncoder {
return func(t time.Time, enc PrimitiveArrayEncoder) {
tmshn marked this conversation as resolved.
Show resolved Hide resolved
encodeTimeLayout(t, layout, enc)
}
}

// UnmarshalText unmarshals text to a TimeEncoder.
// "rfc3339nano" and "RFC3339Nano" are unmarshaled to RFC3339NanoTimeEncoder.
// "rfc3339" and "RFC3339" are unmarshaled to RFC3339TimeEncoder.
Expand All @@ -176,6 +185,35 @@ func (e *TimeEncoder) UnmarshalText(text []byte) error {
return nil
}

// UnmarshalYAML unmarshals YAML to a TimeEncoder.
// If value is an object with a "layout" field, it will be unmarshaled to TimeEncoder with given layout.
// timeEncoder:
// layout: 06/01/02 03:04pm
// If value is string, it uses UnmarshalText.
// timeEncoder: iso8601
func (e *TimeEncoder) UnmarshalYAML(unmarshal func(interface{}) error) error {
var o struct {
Layout string `json:"layout" yaml:"layout"`
}
if err := unmarshal(&o); err == nil {
*e = TimeEncoderOfLayout(o.Layout)
return nil
}

var s string
if err := unmarshal(&s); err != nil {
return err
tmshn marked this conversation as resolved.
Show resolved Hide resolved
}
return e.UnmarshalText([]byte(s))
}

// UnmarshalJSON unmarshals JSON to a TimeEncoder as same way UnmarshalYAML does.
func (e *TimeEncoder) UnmarshalJSON(data []byte) error {
return e.UnmarshalYAML(func(v interface{}) error {
return json.Unmarshal(data, v)
})
}

// A DurationEncoder serializes a time.Duration to a primitive type.
type DurationEncoder func(time.Duration, PrimitiveArrayEncoder)

Expand Down
68 changes: 53 additions & 15 deletions zapcore/encoder_test.go
Expand Up @@ -21,12 +21,14 @@
package zapcore_test

import (
"encoding/json"
"strings"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"

. "go.uber.org/zap/zapcore"
)
Expand Down Expand Up @@ -523,29 +525,65 @@ func TestLevelEncoders(t *testing.T) {
func TestTimeEncoders(t *testing.T) {
moment := time.Unix(100, 50005000).UTC()
tests := []struct {
name string
yamlDoc string
expected interface{} // output of serializing moment
}{
{"timeEncoder: iso8601", "1970-01-01T00:01:40.050Z"},
{"timeEncoder: ISO8601", "1970-01-01T00:01:40.050Z"},
{"timeEncoder: millis", 100050.005},
{"timeEncoder: nanos", int64(100050005000)},
{"timeEncoder: {layout: 06/01/02 03:04pm}", "70/01/01 12:01am"},
{"timeEncoder: ''", 100.050005},
{"timeEncoder: something-random", 100.050005},
{"timeEncoder: rfc3339", "1970-01-01T00:01:40Z"},
{"timeEncoder: RFC3339", "1970-01-01T00:01:40Z"},
{"timeEncoder: rfc3339nano", "1970-01-01T00:01:40.050005Z"},
{"timeEncoder: RFC3339Nano", "1970-01-01T00:01:40.050005Z"},
}

for _, tt := range tests {
cfg := EncoderConfig{}
require.NoError(t, yaml.Unmarshal([]byte(tt.yamlDoc), &cfg), "Unexpected error unmarshaling %q.", tt.yamlDoc)
require.NotNil(t, cfg.EncodeTime, "Unmashalled timeEncoder is nil for %q.", tt.yamlDoc)
assertAppended(
t,
tt.expected,
func(arr ArrayEncoder) { cfg.EncodeTime(moment, arr) },
"Unexpected output serializing %v with %q.", moment, tt.yamlDoc,
)
}
}

func TestTimeEncodersWrongYAML(t *testing.T) {
tests := []string{
"timeEncoder: [1, 2, 3]", // wrong type
"timeEncoder: {foo:bar", // broken yaml
}
for _, tt := range tests {
cfg := EncoderConfig{}
assert.Error(t, yaml.Unmarshal([]byte(tt), &cfg), "Expected unmarshaling %q to become error, but not.", tt)
}
}

func TestTimeEncodersParseFromJSON(t *testing.T) {
moment := time.Unix(100, 50005000).UTC()
tests := []struct {
jsonDoc string
expected interface{} // output of serializing moment
}{
{"iso8601", "1970-01-01T00:01:40.050Z"},
{"ISO8601", "1970-01-01T00:01:40.050Z"},
{"millis", 100050.005},
{"nanos", int64(100050005000)},
{"", 100.050005},
{"something-random", 100.050005},
{"rfc3339", "1970-01-01T00:01:40Z"},
{"RFC3339", "1970-01-01T00:01:40Z"},
{"rfc3339nano", "1970-01-01T00:01:40.050005Z"},
{"RFC3339Nano", "1970-01-01T00:01:40.050005Z"},
{`{"timeEncoder": "iso8601"}`, "1970-01-01T00:01:40.050Z"},
{`{"timeEncoder": {"layout": "06/01/02 03:04pm"}}`, "70/01/01 12:01am"},
}

for _, tt := range tests {
var te TimeEncoder
require.NoError(t, te.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name)
cfg := EncoderConfig{}
require.NoError(t, json.Unmarshal([]byte(tt.jsonDoc), &cfg), "Unexpected error unmarshaling %q.", tt.jsonDoc)
require.NotNil(t, cfg.EncodeTime, "Unmashalled timeEncoder is nil for %q.", tt.jsonDoc)
assertAppended(
t,
tt.expected,
func(arr ArrayEncoder) { te(moment, arr) },
"Unexpected output serializing %v with %q.", moment, tt.name,
func(arr ArrayEncoder) { cfg.EncodeTime(moment, arr) },
"Unexpected output serializing %v with %q.", moment, tt.jsonDoc,
)
}
}
Expand Down