diff --git a/logger.go b/logger.go index bdcc5d666..e5741cc4f 100644 --- a/logger.go +++ b/logger.go @@ -51,6 +51,9 @@ type Logger struct { addStack zapcore.LevelEnabler callerSkip int + + // now returns current time, default is time.Now. + now func() time.Time } // New constructs a new Logger from the provided zapcore.Core and Options. If @@ -71,6 +74,7 @@ func New(core zapcore.Core, options ...Option) *Logger { core: core, errorOutput: zapcore.Lock(os.Stderr), addStack: zapcore.FatalLevel + 1, + now: time.Now, } return log.WithOptions(options...) } @@ -270,7 +274,7 @@ func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { // log message will actually be written somewhere. ent := zapcore.Entry{ LoggerName: log.name, - Time: time.Now(), + Time: log.now(), Level: lvl, Message: msg, } @@ -307,7 +311,7 @@ func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { if log.addCaller { frame, defined := getCallerFrame(log.callerSkip + callerSkipOffset) if !defined { - fmt.Fprintf(log.errorOutput, "%v Logger.check error: failed to get caller\n", time.Now().UTC()) + fmt.Fprintf(log.errorOutput, "%v Logger.check error: failed to get caller\n", log.now().UTC()) log.errorOutput.Sync() } diff --git a/options.go b/options.go index 0135c2092..59c9387d9 100644 --- a/options.go +++ b/options.go @@ -22,6 +22,7 @@ package zap import ( "fmt" + "time" "go.uber.org/zap/zapcore" ) @@ -138,3 +139,11 @@ func OnFatal(action zapcore.CheckWriteAction) Option { log.onFatal = action }) } + +// WithTimeSource configures the Logger to get current time from provided +// function when creating entry. +func WithTimeSource(now func() time.Time) Option { + return optionFunc(func(log *Logger) { + log.now = now + }) +} diff --git a/time_source_test.go b/time_source_test.go new file mode 100644 index 000000000..eecf1599d --- /dev/null +++ b/time_source_test.go @@ -0,0 +1,40 @@ +// 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" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest/observer" +) + +func TestWithTimeSource(t *testing.T) { + date := time.Date(2077, 1, 23, 10, 15, 13, 441, time.UTC) + now := func() time.Time { return date } + withLogger(t, DebugLevel, []Option{WithTimeSource(now)}, func(log *Logger, logs *observer.ObservedLogs) { + log.Info("") + require.Equal(t, 1, logs.Len(), "Expected only one log entry to be written.") + assert.Equal(t, date, logs.All()[0].Entry.Time, "Unexpected entry time.") + }) +}