Skip to content

Commit

Permalink
Merge pull request #14 from uber-common/ajs-zap
Browse files Browse the repository at this point in the history
Introduce a bark-to-zap compatibility wrapper
  • Loading branch information
akshayjshah authored Sep 18, 2017
2 parents aa8f498 + 002b6ce commit dfa0983
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 16 deletions.
23 changes: 14 additions & 9 deletions zbark/zap_logger.go → zbark/barkify.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,21 @@ import (
"go.uber.org/zap"
)

// New builds a Bark logger from a Zap logger.
func New(l *zap.Logger) bark.Logger {
return barkZapLogger{l.Sugar()}
// Barkify wraps a zap logger in a compatibility layer so that it satisfies
// the bark.Logger interface. Note that the wrapper always returns nil from
// the Fields method, since zap doesn't support this functionality.
func Barkify(l *zap.Logger) bark.Logger {
return barker{l.Sugar()}
}

type barkZapLogger struct{ *zap.SugaredLogger }
type barker struct{ *zap.SugaredLogger }

func (l barkZapLogger) WithField(key string, value interface{}) bark.Logger {
func (l barker) WithField(key string, value interface{}) bark.Logger {
l.SugaredLogger = l.SugaredLogger.With(key, value) // safe to change because we pass-by-value
return l
}

func (l barkZapLogger) WithFields(keyValues bark.LogFields) bark.Logger {
func (l barker) WithFields(keyValues bark.LogFields) bark.Logger {
barkFields := keyValues.Fields()

// Deterministic ordering of fields.
Expand All @@ -57,12 +59,15 @@ func (l barkZapLogger) WithFields(keyValues bark.LogFields) bark.Logger {
return l
}

func (l barkZapLogger) WithError(err error) bark.Logger {
func (l barker) WithError(err error) bark.Logger {
l.SugaredLogger = l.SugaredLogger.With(zap.Error(err)) // safe to change because we pass-by-value
return l
}

func (l barkZapLogger) Fields() bark.Fields {
l.SugaredLogger.Warn("Fields() call to bark logger is not supported by Zap")
func (l barker) Fields() bark.Fields {
// Zap has already marshaled the accumulated logger context to []byte, so we
// can't reconstruct the original objects. To satisfy this interface, just
// return nil.
l.SugaredLogger.Warn("zap-to-bark compatibility wrapper does not support Fields method")
return nil
}
14 changes: 7 additions & 7 deletions zbark/zap_logger_test.go → zbark/barkify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,16 @@ import (
"go.uber.org/zap/zaptest/observer"
)

func newTestLogger() (bark.Logger, *observer.ObservedLogs) {
func newTestBarker() (bark.Logger, *observer.ObservedLogs) {
core, logs := observer.New(zap.LevelEnablerFunc(func(zapcore.Level) bool {
return true
}))
return zbark.New(zap.New(core)), logs
return zbark.Barkify(zap.New(core)), logs
}

func TestBarkLoggerWith(t *testing.T) {
t.Run("WithField", func(t *testing.T) {
l, logs := newTestLogger()
l, logs := newTestBarker()
l = l.WithField("foo", "bar")

l.Info("hello")
Expand All @@ -57,7 +57,7 @@ func TestBarkLoggerWith(t *testing.T) {
})

t.Run("WithFields", func(t *testing.T) {
l, logs := newTestLogger()
l, logs := newTestBarker()
l = l.WithFields(bark.Fields{
"foo": "bar",
"baz": int8(42),
Expand Down Expand Up @@ -87,7 +87,7 @@ func TestBarkLoggerWith(t *testing.T) {
})

t.Run("WithError", func(t *testing.T) {
l, logs := newTestLogger()
l, logs := newTestBarker()
l = l.WithError(errors.New("great sadness"))

l.Info("hello")
Expand All @@ -103,15 +103,15 @@ func TestBarkLoggerWith(t *testing.T) {
})

t.Run("Fields", func(t *testing.T) {
l, logs := newTestLogger()
l, logs := newTestBarker()
l = l.WithField("foo", "bar").WithError(errors.New("great sadness"))
assert.Empty(t, l.Fields(), "expected empty field list")

require.Equal(t, 1, logs.Len(), "message count did not match")

entry := logs.All()[0]
assert.Equal(t,
"Fields() call to bark logger is not supported by Zap", entry.Message,
"zap-to-bark compatibility wrapper does not support Fields method", entry.Message,
"message did not match")
assert.Equal(t, zapcore.WarnLevel, entry.Level, "message level did not match")
})
Expand Down
94 changes: 94 additions & 0 deletions zbark/zapify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright (c) 2017 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 zbark

import (
"fmt"

"github.com/uber-common/bark"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

// Zapify wraps a bark logger in a compatibility layer to produce a
// *zap.Logger. Note that the wrapper always treats zap's DPanicLevel as an
// error (even in development).
func Zapify(l bark.Logger) *zap.Logger {
return zap.New(&zapper{l})
}

type zapper struct {
l bark.Logger
}

func (z *zapper) Enabled(lvl zapcore.Level) bool {
// Enabled allows zap to short-circuit some logging calls early, which
// improves performance. Since we're not sure what levels are enabled on the
// underlying bark logger, always return true; this hurts performance but
// not correctness.
return true
}

func (z *zapper) With(fs []zapcore.Field) zapcore.Core {
return &zapper{z.with(fs)}
}

func (z *zapper) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
// Since Enabled is always true, there's no need to check it here.
return ce.AddCore(ent, z)
}

func (z *zapper) Write(ent zapcore.Entry, fs []zapcore.Field) error {
logger := z.with(fs)

var logFunc func(string, ...interface{})
switch ent.Level {
case zapcore.DebugLevel:
logFunc = logger.Debugf
case zapcore.InfoLevel:
logFunc = logger.Infof
case zapcore.WarnLevel:
logFunc = logger.Warnf
case zapcore.ErrorLevel, zapcore.DPanicLevel:
logFunc = logger.Errorf
case zapcore.PanicLevel:
logFunc = logger.Panicf
case zapcore.FatalLevel:
logFunc = logger.Fatalf
default:
return fmt.Errorf("bark-to-zap compatibility wrapper got unknown level %v", ent.Level)
}
// The underlying bark logger timestamps the entry, so we can drop
// everything but the message.
logFunc(ent.Message)
return nil
}

func (z *zapper) Sync() error {
// Bark doesn't expose a way to flush buffered messages.
return nil
}

func (z *zapper) with(fs []zapcore.Field) bark.Logger {
me := zapcore.NewMapObjectEncoder()
for _, f := range fs {
f.AddTo(me)
}
return z.l.WithFields(bark.Fields(me.Fields))
}
104 changes: 104 additions & 0 deletions zbark/zapify_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) 2017 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 zbark_test

import (
"bytes"
"encoding/json"
"testing"

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

"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/uber-common/bark"
"github.com/uber-common/bark/zbark"
)

func newTestZapper() (*zap.Logger, *bytes.Buffer) {
buf := bytes.NewBuffer(nil)
barkLogger := &logrus.Logger{
Out: buf,
Hooks: make(logrus.LevelHooks),
Formatter: &logrus.JSONFormatter{
DisableTimestamp: true,
},
Level: logrus.DebugLevel,
}
return zbark.Zapify(bark.NewLoggerFromLogrus(barkLogger)), buf
}

func assertJSON(t testing.TB, expected map[string]interface{}, buf *bytes.Buffer) {
line := bytes.TrimSpace(buf.Bytes())
msg := make(map[string]interface{})
if err := json.Unmarshal(line, &msg); err != nil {
t.Fatalf("can't unmarshal JSON log: %s", string(line))
}
assert.Equal(t, expected, msg, "unexpected log message")
}

func TestZapLoggerWith(t *testing.T) {
log, buf := newTestZapper()
log.With(zap.String("foo", "bar")).Info("hello", zap.String("baz", "quux"))
assertJSON(t, map[string]interface{}{
"foo": "bar",
"baz": "quux",
"msg": "hello",
"level": "info",
}, buf)
}

func TestZapLoggerLogging(t *testing.T) {
const msg = "hello"

log, buf := newTestZapper()
assertLogged := func(expected string) {
assertJSON(t, map[string]interface{}{
"msg": msg,
"level": expected,
}, buf)
}

tests := []struct {
f func(string, ...zapcore.Field)
level zapcore.Level
want string
}{
{log.Debug, zapcore.DebugLevel, "debug"},
{log.Info, zapcore.InfoLevel, "info"},
{log.Warn, zapcore.WarnLevel, "warning"},
{log.Error, zapcore.ErrorLevel, "error"},
{log.DPanic, zapcore.DPanicLevel, "error"},
}

for _, tt := range tests {
t.Run(tt.want, func(t *testing.T) {
tt.f(msg)
assertLogged(tt.want)
buf.Reset()

if ce := log.Check(tt.level, msg); ce != nil {
ce.Write()
}
assertLogged(tt.want)
buf.Reset()
})
}
}

0 comments on commit dfa0983

Please sign in to comment.