From 19915a22218b65f602e19f82d37b14cdd5c2de7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bojan=20Deli=C4=87?= Date: Thu, 21 Sep 2017 18:52:19 +0200 Subject: [PATCH] Make level configurable for NewStdLog. (#439) (#487) With this change new function is added called NewStdLogAt. It has same behaviour as NewStdLog, but it allows providing log level to use by returned *log.Logger. --- global.go | 46 +++++++++++++++++++++++++++------ global_test.go | 70 ++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 98 insertions(+), 18 deletions(-) diff --git a/global.go b/global.go index fbf0f7589..d34545509 100644 --- a/global.go +++ b/global.go @@ -22,14 +22,17 @@ package zap import ( "bytes" + "fmt" "log" "os" "sync" + + "go.uber.org/zap/zapcore" ) const ( _stdLogDefaultDepth = 2 - _loggerWriterDepth = 1 + _loggerWriterDepth = 2 ) var ( @@ -71,9 +74,35 @@ func ReplaceGlobals(logger *Logger) func() { // InfoLevel. To redirect the standard library's package-global logging // functions, use RedirectStdLog instead. func NewStdLog(l *Logger) *log.Logger { - return log.New(&loggerWriter{l.WithOptions( - AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth), - )}, "" /* prefix */, 0 /* flags */) + logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth)) + f := logger.Info + return log.New(&loggerWriter{f}, "" /* prefix */, 0 /* flags */) +} + +// NewStdLogAt returns *log.Logger which writes to supplied zap logger at +// required level. +func NewStdLogAt(l *Logger, level zapcore.Level) (*log.Logger, error) { + logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth)) + var logFunc func(string, ...zapcore.Field) + switch level { + case DebugLevel: + logFunc = logger.Debug + case InfoLevel: + logFunc = logger.Info + case WarnLevel: + logFunc = logger.Warn + case ErrorLevel: + logFunc = logger.Error + case DPanicLevel: + logFunc = logger.DPanic + case PanicLevel: + logFunc = logger.Panic + case FatalLevel: + logFunc = logger.Fatal + default: + return nil, fmt.Errorf("unrecognized level: %q", level) + } + return log.New(&loggerWriter{logFunc}, "" /* prefix */, 0 /* flags */), nil } // RedirectStdLog redirects output from the standard library's package-global @@ -88,9 +117,10 @@ func RedirectStdLog(l *Logger) func() { prefix := log.Prefix() log.SetFlags(0) log.SetPrefix("") - log.SetOutput(&loggerWriter{l.WithOptions( + logFunc := l.WithOptions( AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth), - )}) + ).Info + log.SetOutput(&loggerWriter{logFunc}) return func() { log.SetFlags(flags) log.SetPrefix(prefix) @@ -99,11 +129,11 @@ func RedirectStdLog(l *Logger) func() { } type loggerWriter struct { - logger *Logger + logFunc func(msg string, fields ...zapcore.Field) } func (l *loggerWriter) Write(p []byte) (int, error) { p = bytes.TrimSpace(p) - l.logger.Info(string(p)) + l.logFunc(string(p)) return len(p), nil } diff --git a/global_test.go b/global_test.go index ed2d1447c..7da3b94ed 100644 --- a/global_test.go +++ b/global_test.go @@ -26,6 +26,8 @@ import ( "testing" "time" + "go.uber.org/zap/internal/exit" + "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest" "go.uber.org/zap/zaptest/observer" @@ -96,20 +98,55 @@ func TestNewStdLog(t *testing.T) { withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { std := NewStdLog(l) std.Print("redirected") + checkStdLogMessage(t, "redirected", logs) + }) +} - require.Equal(t, 1, logs.Len(), "Expected exactly one entry to be logged.") - entry := logs.AllUntimed()[0] - assert.Equal(t, []zapcore.Field{}, entry.Context, "Unexpected entry context.") - assert.Equal(t, "redirected", entry.Entry.Message, "Unexpected entry message.") - assert.Regexp( - t, - `go.uber.org/zap/global_test.go:\d+$`, - entry.Entry.Caller.String(), - "Unexpected caller annotation.", - ) +func TestNewStdLogAt(t *testing.T) { + // include DPanicLevel here, but do not include Development in options + levels := []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} + for _, level := range levels { + withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { + std, err := NewStdLogAt(l, level) + require.NoError(t, err, "Unexpected error.") + std.Print("redirected") + checkStdLogMessage(t, "redirected", logs) + }) + } +} + +func TestNewStdLogAtPanics(t *testing.T) { + // include DPanicLevel here and enable Development in options + levels := []zapcore.Level{DPanicLevel, PanicLevel} + for _, level := range levels { + withLogger(t, DebugLevel, []Option{AddCaller(), Development()}, func(l *Logger, logs *observer.ObservedLogs) { + std, err := NewStdLogAt(l, level) + require.NoError(t, err, "Unexpected error") + assert.Panics(t, func() { std.Print("redirected") }, "Expected log to panic.") + checkStdLogMessage(t, "redirected", logs) + }) + } +} + +func TestNewStdLogAtFatal(t *testing.T) { + withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { + stub := exit.WithStub(func() { + std, err := NewStdLogAt(l, FatalLevel) + require.NoError(t, err, "Unexpected error.") + std.Print("redirected") + checkStdLogMessage(t, "redirected", logs) + }) + assert.True(t, true, stub.Exited, "Expected Fatal logger call to terminate process.") + stub.Unstub() }) } +func TestNewStdLogAtInvalid(t *testing.T) { + _, err := NewStdLogAt(NewNop(), zapcore.Level(99)) + assert.Error(t, err, "Expected to get error.") + assert.Contains(t, err.Error(), "99", "Expected level code in error message") +} + func TestRedirectStdLog(t *testing.T) { initialFlags := log.Flags() initialPrefix := log.Prefix() @@ -137,3 +174,16 @@ func TestRedirectStdLogCaller(t *testing.T) { assert.Contains(t, entries[0].Entry.Caller.File, "global_test.go", "Unexpected caller annotation.") }) } + +func checkStdLogMessage(t *testing.T, msg string, logs *observer.ObservedLogs) { + require.Equal(t, 1, logs.Len(), "Expected exactly one entry to be logged") + entry := logs.AllUntimed()[0] + assert.Equal(t, []zapcore.Field{}, entry.Context, "Unexpected entry context.") + assert.Equal(t, "redirected", entry.Entry.Message, "Unexpected entry message.") + assert.Regexp( + t, + `go.uber.org/zap/global_test.go:\d+$`, + entry.Entry.Caller.String(), + "Unexpected caller annotation.", + ) +}