From e3287143d9416c6246b27ef10a35eee036360531 Mon Sep 17 00:00:00 2001 From: Peter Edge Date: Fri, 10 Mar 2017 03:31:34 -0500 Subject: [PATCH] Add RegisterEncoder functionality (#348) --- config.go | 9 +---- encoder.go | 73 ++++++++++++++++++++++++++++++++++++++++ encoder_test.go | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 8 deletions(-) create mode 100644 encoder.go create mode 100644 encoder_test.go diff --git a/config.go b/config.go index 0709fc25e..099d1b34c 100644 --- a/config.go +++ b/config.go @@ -21,7 +21,6 @@ package zap import ( - "fmt" "sort" "time" @@ -229,11 +228,5 @@ func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error) } func (cfg Config) buildEncoder() (zapcore.Encoder, error) { - switch cfg.Encoding { - case "json": - return zapcore.NewJSONEncoder(cfg.EncoderConfig), nil - case "console": - return zapcore.NewConsoleEncoder(cfg.EncoderConfig), nil - } - return nil, fmt.Errorf("unknown encoding %q", cfg.Encoding) + return newEncoder(cfg.Encoding, cfg.EncoderConfig) } diff --git a/encoder.go b/encoder.go new file mode 100644 index 000000000..c5a844544 --- /dev/null +++ b/encoder.go @@ -0,0 +1,73 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "fmt" + "sync" + + "go.uber.org/zap/zapcore" +) + +var ( + errNoEncoderNameSpecified = errors.New("no encoder name specified") + + _encoderNameToConstructor = map[string]func(zapcore.EncoderConfig) (zapcore.Encoder, error){ + "console": func(encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { + return zapcore.NewConsoleEncoder(encoderConfig), nil + }, + "json": func(encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { + return zapcore.NewJSONEncoder(encoderConfig), nil + }, + } + _encoderMutex sync.RWMutex +) + +// RegisterEncoder registers an encoder constructor for the given name. +// +// If an encoder with the same name already exists, this will return an error. +// By default, the encoders "json" and "console" are registered. +func RegisterEncoder(name string, constructor func(zapcore.EncoderConfig) (zapcore.Encoder, error)) error { + _encoderMutex.Lock() + defer _encoderMutex.Unlock() + if name == "" { + return errNoEncoderNameSpecified + } + if _, ok := _encoderNameToConstructor[name]; ok { + return fmt.Errorf("encoder already registered for name %q", name) + } + _encoderNameToConstructor[name] = constructor + return nil +} + +func newEncoder(name string, encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { + _encoderMutex.RLock() + defer _encoderMutex.RUnlock() + if name == "" { + return nil, errNoEncoderNameSpecified + } + constructor, ok := _encoderNameToConstructor[name] + if !ok { + return nil, fmt.Errorf("no encoder registered for name %q", name) + } + return constructor(encoderConfig) +} diff --git a/encoder_test.go b/encoder_test.go new file mode 100644 index 000000000..f6be665b1 --- /dev/null +++ b/encoder_test.go @@ -0,0 +1,88 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "testing" + + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" +) + +func TestRegisterDefaultEncoders(t *testing.T) { + testEncodersRegistered(t, "console", "json") +} + +func TestRegisterEncoder(t *testing.T) { + testEncoders(func() { + assert.NoError(t, RegisterEncoder("foo", newNilEncoder), "expected to be able to register the encoder foo") + testEncodersRegistered(t, "foo") + }) +} + +func TestDuplicateRegisterEncoder(t *testing.T) { + testEncoders(func() { + RegisterEncoder("foo", newNilEncoder) + assert.Error(t, RegisterEncoder("foo", newNilEncoder), "expected an error when registering an encoder with the same name twice") + }) +} + +func TestRegisterEncoderNoName(t *testing.T) { + assert.Equal(t, errNoEncoderNameSpecified, RegisterEncoder("", newNilEncoder), "expected an error when registering an encoder with no name") +} + +func TestNewEncoder(t *testing.T) { + testEncoders(func() { + RegisterEncoder("foo", newNilEncoder) + encoder, err := newEncoder("foo", zapcore.EncoderConfig{}) + assert.NoError(t, err, "could not create an encoder for the registered name foo") + assert.Nil(t, encoder, "the encoder from newNilEncoder is not nil") + }) +} + +func TestNewEncoderNotRegistered(t *testing.T) { + _, err := newEncoder("foo", zapcore.EncoderConfig{}) + assert.Error(t, err, "expected an error when trying to create an encoder of an unregistered name") +} + +func TestNewEncoderNoName(t *testing.T) { + _, err := newEncoder("", zapcore.EncoderConfig{}) + assert.Equal(t, errNoEncoderNameSpecified, err, "expected an error when creating an encoder with no name") +} + +func testEncoders(f func()) { + existing := _encoderNameToConstructor + _encoderNameToConstructor = make(map[string]func(zapcore.EncoderConfig) (zapcore.Encoder, error)) + defer func() { _encoderNameToConstructor = existing }() + f() +} + +func testEncodersRegistered(t *testing.T, names ...string) { + assert.Len(t, _encoderNameToConstructor, len(names), "the expected number of registered encoders does not match the actual number") + for _, name := range names { + assert.NotNil(t, _encoderNameToConstructor[name], "no encoder is registered for name %s", name) + } +} + +func newNilEncoder(_ zapcore.EncoderConfig) (zapcore.Encoder, error) { + return nil, nil +}