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

Alternate, map-based sugared logger #247

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ccd2c79
Refs: #138
mikluko Sep 22, 2016
2f71959
added benchmark
mikluko Sep 22, 2016
f83f8a7
improved test coverage
mikluko Sep 22, 2016
d19b6d2
improved test coverage even more
mikluko Sep 22, 2016
3e840ad
accept error as the first of variadic arguments
mikluko Sep 23, 2016
e05c023
cleanup
mikluko Sep 23, 2016
f2a8f56
improved coverage
mikluko Sep 23, 2016
9c9b46b
mirrored relevant core logger benchmarks
mikluko Sep 23, 2016
73a65b2
use CheckedMessage to cut off expensive argument parsing
mikluko Sep 23, 2016
859883a
used another form of type switch
mikluko Oct 4, 2016
f869f0d
fixed typo
mikluko Oct 4, 2016
3786a4a
getSugarFields errors are logged rather then returned
mikluko Oct 5, 2016
276f677
added support for Marshaler field, changed fallback from error to obj…
mikluko Oct 5, 2016
79c05a6
pre-allocate fields slice
mikluko Oct 5, 2016
a0a3ee6
made test to fail if Object field is used in place of Marshaler
mikluko Oct 5, 2016
7bb19f1
Removed LogMarshaler handling
mikluko Oct 10, 2016
ef67c88
added some benchamrk helpers to reduce repeats
mikluko Oct 10, 2016
3311b28
updated comments
mikluko Oct 10, 2016
3cc2db3
handle errors same way core logger does
mikluko Oct 10, 2016
40bb388
relay DFatal directly to core logger
mikluko Oct 10, 2016
6e41219
benchmark fix
mikluko Oct 10, 2016
a5a3dac
WIP, addressing comments on @akabos's PR
Nov 21, 2016
55b8718
Finish sugared logger, update tests & benchmarks
Jan 13, 2017
96da776
Update the benchmarks
Jan 13, 2017
ceba454
Fix benchmarks
Jan 13, 2017
3c9b89e
Add test coverage for zap.Any
Jan 13, 2017
c4c498f
With should dispatch to WithFields
Jan 13, 2017
1e2740d
Update to use internal observer package
Jan 13, 2017
2698eca
Some CR
Jan 14, 2017
bc38c5c
Pivot to a map-based sugared logger
Jan 16, 2017
989ecab
Keep skip level constant
Jan 17, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions benchmarks/zap_sugar_bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// 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 benchmarks

import (
"testing"
"time"

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

func fakeSugarFields() zap.Ctx {
return zap.Ctx{
"error": errExample,
"int": 1,
"int64": 2,
"float": 3.0,
"string": "four!",
"stringer": zap.DebugLevel,
"bool": true,
"time": time.Unix(0, 0),
"duration": time.Second,
"another string": "done!",
}
}

func newSugarLogger(lvl zapcore.Level, options ...zap.Option) *zap.SugaredLogger {
return zap.Sugar(zap.New(zapcore.WriterFacility(
benchEncoder(),
&testutils.Discarder{},
lvl,
), options...))
}

func BenchmarkZapSugarDisabledLevelsWithoutFields(b *testing.B) {
logger := newSugarLogger(zap.ErrorLevel)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.Info("Should be discarded.")
}
})
}

func BenchmarkZapSugarDisabledLevelsAccumulatedContext(b *testing.B) {
logger := newSugarLogger(zap.ErrorLevel, zap.Fields(fakeFields()...))
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.Info("Should be discarded.")
}
})
}

func BenchmarkZapSugarDisabledLevelsAddingFields(b *testing.B) {
logger := newSugarLogger(zap.ErrorLevel)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.InfoWith("Should be discarded.", fakeSugarFields())
}
})
}

func BenchmarkZapSugarAddingFields(b *testing.B) {
logger := newSugarLogger(zap.DebugLevel)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.InfoWith("Go fast.", fakeSugarFields())
}
})
}

func BenchmarkZapSugarWithAccumulatedContext(b *testing.B) {
logger := newSugarLogger(zap.DebugLevel, zap.Fields(fakeFields()...))
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.Info("Go really fast.")
}
})
}

func BenchmarkZapSugarWithoutFields(b *testing.B) {
logger := newSugarLogger(zap.DebugLevel)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.Info("Go fast.")
}
})
}
4 changes: 4 additions & 0 deletions common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ func withLogger(t testing.TB, e zapcore.LevelEnabler, opts []Option, f func(Logg
f(log, &logs)
}

func withSugar(t testing.TB, e zapcore.LevelEnabler, opts []Option, f func(*SugaredLogger, *observer.ObservedLogs)) {
withLogger(t, e, opts, func(logger Logger, logs *observer.ObservedLogs) { f(Sugar(logger), logs) })
}

func runConcurrently(goroutines, iterations int, wg *sync.WaitGroup, f func()) {
wg.Add(goroutines)
for g := 0; g < goroutines; g++ {
Expand Down
52 changes: 52 additions & 0 deletions field.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,55 @@ func Reflect(key string, val interface{}) zapcore.Field {
func Nest(key string, fields ...zapcore.Field) zapcore.Field {
return zapcore.Field{Key: key, Type: zapcore.ObjectMarshalerType, Interface: zapcore.Fields(fields)}
}

// Any takes a key and an arbitrary value and chooses the best way to represent
// them as a field, falling back to a reflection-based approach only if
// necessary.
func Any(key string, value interface{}) zapcore.Field {
switch val := value.(type) {
case zapcore.ObjectMarshaler:
return Object(key, val)
case zapcore.ArrayMarshaler:
return Array(key, val)
case bool:
return Bool(key, val)
case float64:
return Float64(key, val)
case float32:
return Float64(key, float64(val))
case int:
return Int(key, val)
case int64:
return Int64(key, val)
case int32:
return Int64(key, int64(val))
case int16:
return Int64(key, int64(val))
case int8:
return Int64(key, int64(val))
case uint:
return Uint(key, val)
case uint64:
return Uint64(key, val)
case uint32:
return Uint64(key, uint64(val))
case uint16:
return Uint64(key, uint64(val))
case uint8:
return Uint64(key, uint64(val))
case uintptr:
return Uintptr(key, val)
case string:
return String(key, val)
case time.Time:
return Time(key, val)
case time.Duration:
return Duration(key, val)
case error:
return String(key, val.Error())
case fmt.Stringer:
return Stringer(key, val)
default:
return Reflect(key, val)
}
}
31 changes: 27 additions & 4 deletions field_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,14 @@ package zap

import (
"errors"
"math"
"net"
"sync"
"testing"
"time"

"go.uber.org/zap/zapcore"

"math"

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

var (
Expand Down Expand Up @@ -99,6 +97,31 @@ func TestFieldConstructors(t *testing.T) {
{"Object", zapcore.Field{Key: "k", Type: zapcore.ObjectMarshalerType, Interface: name}, Object("k", name)},
{"Reflect", zapcore.Field{Key: "k", Type: zapcore.ReflectType, Interface: ints}, Reflect("k", ints)},
{"Nest", zapcore.Field{Key: "k", Type: zapcore.ObjectMarshalerType, Interface: nested}, Nest("k", nested...)},
{"Any:ObjectMarshaler", Any("k", name), Object("k", name)},
{"Any:ArrayMarshaler", Any("k", bools([]bool{true})), Array("k", bools([]bool{true}))},
{"Any:Bool", Any("k", true), Bool("k", true)},
{"Any:Float64", Any("k", 3.14), Float64("k", 3.14)},
// TODO (v1.0): We could use some approximately-equal logic here, but it's
// not worth it to test this one line. Before 1.0, we'll need to support
// float32s explicitly, which will make this test pass.
// {"Any:Float32", Any("k", float32(3.14)), Float32("k", 3.14)},
{"Any:Int", Any("k", 1), Int("k", 1)},
{"Any:Int64", Any("k", int64(1)), Int64("k", 1)},
{"Any:Int32", Any("k", int32(1)), Int64("k", 1)},
{"Any:Int16", Any("k", int16(1)), Int64("k", 1)},
{"Any:Int8", Any("k", int8(1)), Int64("k", 1)},
{"Any:Uint", Any("k", uint(1)), Uint("k", 1)},
{"Any:Uint64", Any("k", uint64(1)), Uint64("k", 1)},
{"Any:Uint32", Any("k", uint32(1)), Uint64("k", 1)},
{"Any:Uint16", Any("k", uint16(1)), Uint64("k", 1)},
{"Any:Uint8", Any("k", uint8(1)), Uint64("k", 1)},
{"Any:Uintptr", Any("k", uintptr(1)), Uintptr("k", 1)},
{"Any:String", Any("k", "v"), String("k", "v")},
{"Any:Error", Any("k", errors.New("v")), String("k", "v")},
{"Any:Time", Any("k", time.Unix(0, 0)), Time("k", time.Unix(0, 0))},
{"Any:Duration", Any("k", time.Second), Duration("k", time.Second)},
{"Any:Stringer", Any("k", addr), Stringer("k", addr)},
{"Any:Fallback", Any("k", struct{}{}), Reflect("k", struct{}{})},
}

for _, tt := range tests {
Expand Down
4 changes: 2 additions & 2 deletions level.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ const (
// ErrorLevel logs are high-priority. If an application is running smoothly,
// it shouldn't generate any error-level logs.
ErrorLevel = zapcore.ErrorLevel
// DPanicLevel logs are particularly important errors. In development the
// logger panics after writing the message.
// DPanicLevel logs are particularly important errors. In development mode,
// the logger panics after writing the message.
DPanicLevel = zapcore.DPanicLevel
// PanicLevel logs a message, then panics.
PanicLevel = zapcore.PanicLevel
Expand Down
Loading