diff --git a/increase_level_test.go b/increase_level_test.go new file mode 100644 index 000000000..d3e111464 --- /dev/null +++ b/increase_level_test.go @@ -0,0 +1,85 @@ +// Copyright (c) 2020 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 ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" +) + +func newLoggedEntry(level zapcore.Level, msg string) observer.LoggedEntry { + return observer.LoggedEntry{ + Entry: zapcore.Entry{Level: level, Message: msg}, + Context: []zapcore.Field{}, + } +} + +func TestIncreaseLevelTryDecrease(t *testing.T) { + errorOut := &bytes.Buffer{} + opts := []Option{ + ErrorOutput(zapcore.AddSync(errorOut)), + } + withLogger(t, WarnLevel, opts, func(logger *Logger, logs *observer.ObservedLogs) { + logger.Warn("original warn log") + + debugLogger := logger.WithOptions(IncreaseLevel(DebugLevel)) + debugLogger.Debug("ignored debug log") + debugLogger.Warn("increase level warn log") + debugLogger.Error("increase level error log") + + assert.Equal(t, []observer.LoggedEntry{ + newLoggedEntry(WarnLevel, "original warn log"), + newLoggedEntry(WarnLevel, "increase level warn log"), + newLoggedEntry(ErrorLevel, "increase level error log"), + }, logs.AllUntimed(), "unexpected logs") + assert.Equal(t, + `failed to IncreaseLevel: invalid increase level, as level "info" is allowed by increased level, but not by existing core`, + errorOut.String(), + "unexpected error output", + ) + }) +} + +func TestIncreaseLevel(t *testing.T) { + errorOut := &bytes.Buffer{} + opts := []Option{ + ErrorOutput(zapcore.AddSync(errorOut)), + } + withLogger(t, WarnLevel, opts, func(logger *Logger, logs *observer.ObservedLogs) { + logger.Warn("original warn log") + + errorLogger := logger.WithOptions(IncreaseLevel(ErrorLevel)) + errorLogger.Debug("ignored debug log") + errorLogger.Warn("ignored warn log") + errorLogger.Error("increase level error log") + + assert.Equal(t, []observer.LoggedEntry{ + newLoggedEntry(WarnLevel, "original warn log"), + newLoggedEntry(ErrorLevel, "increase level error log"), + }, logs.AllUntimed(), "unexpected logs") + + assert.Empty(t, errorOut.String(), "expect no error output") + }) +} diff --git a/logger_test.go b/logger_test.go index a9f705788..dabcb95a3 100644 --- a/logger_test.go +++ b/logger_test.go @@ -392,6 +392,21 @@ func TestLoggerReplaceCore(t *testing.T) { }) } +func TestLoggerIncreaseLevel(t *testing.T) { + withLogger(t, DebugLevel, opts(IncreaseLevel(WarnLevel)), func(logger *Logger, logs *observer.ObservedLogs) { + logger.Info("logger.Info") + logger.Warn("logger.Warn") + logger.Error("logger.Error") + require.Equal(t, 2, logs.Len(), "expected only warn + error logs due to IncreaseLevel.") + assert.Equal( + t, + logs.AllUntimed()[0].Entry.Message, + "logger.Warn", + "Expected first logged message to be warn level message", + ) + }) +} + func TestLoggerHooks(t *testing.T) { hook, seen := makeCountingHook() withLogger(t, DebugLevel, opts(Hooks(hook)), func(logger *Logger, logs *observer.ObservedLogs) { diff --git a/options.go b/options.go index 7a6b0fca1..dd3b6b2b2 100644 --- a/options.go +++ b/options.go @@ -20,7 +20,11 @@ package zap -import "go.uber.org/zap/zapcore" +import ( + "fmt" + + "go.uber.org/zap/zapcore" +) // An Option configures a Logger. type Option interface { @@ -107,3 +111,16 @@ func AddStacktrace(lvl zapcore.LevelEnabler) Option { log.addStack = lvl }) } + +// IncreaseLevel increase the level of the logger. It has no effect if +// the passed in level tries to decrease the level of the logger. +func IncreaseLevel(lvl zapcore.LevelEnabler) Option { + return optionFunc(func(log *Logger) { + core, err := zapcore.NewIncreaseLevelCore(log.core, lvl) + if err != nil { + fmt.Fprintf(log.errorOutput, "failed to IncreaseLevel: %v", err) + } else { + log.core = core + } + }) +} diff --git a/zapcore/increase_level.go b/zapcore/increase_level.go new file mode 100644 index 000000000..a42135c15 --- /dev/null +++ b/zapcore/increase_level.go @@ -0,0 +1,55 @@ +// Copyright (c) 2020 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 zapcore + +import "fmt" + +type levelFilterCore struct { + Core + + level LevelEnabler +} + +// NewIncreaseLevelCore creates a core that can be used to increase the level of +// an existing Core. It cannot be used to decrease the logging level, as it acts +// as a filter before calling the underlying core. If level decreases the log level, +// an error is returned. +func NewIncreaseLevelCore(core Core, level LevelEnabler) (Core, error) { + for l := _maxLevel; l >= _minLevel; l-- { + if !core.Enabled(l) && level.Enabled(l) { + return nil, fmt.Errorf("invalid increase level, as level %q is allowed by increased level, but not by existing core", l) + } + } + + return &levelFilterCore{core, level}, nil +} + +func (c *levelFilterCore) Enabled(lvl Level) bool { + return c.level.Enabled(lvl) +} + +func (c *levelFilterCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + if !c.Enabled(ent.Level) { + return ce + } + + return c.Core.Check(ent, ce) +} diff --git a/zapcore/increase_level_test.go b/zapcore/increase_level_test.go new file mode 100644 index 000000000..8b8613375 --- /dev/null +++ b/zapcore/increase_level_test.go @@ -0,0 +1,101 @@ +// Copyright (c) 2020 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 zapcore_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + . "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" +) + +func TestIncreaseLevel(t *testing.T) { + tests := []struct { + coreLevel Level + increaseLevel Level + wantErr bool + }{ + { + coreLevel: InfoLevel, + increaseLevel: DebugLevel, + wantErr: true, + }, + { + coreLevel: InfoLevel, + increaseLevel: InfoLevel, + }, + { + coreLevel: InfoLevel, + increaseLevel: ErrorLevel, + }, + { + coreLevel: ErrorLevel, + increaseLevel: DebugLevel, + wantErr: true, + }, + { + coreLevel: ErrorLevel, + increaseLevel: InfoLevel, + wantErr: true, + }, + { + coreLevel: ErrorLevel, + increaseLevel: WarnLevel, + wantErr: true, + }, + { + coreLevel: ErrorLevel, + increaseLevel: PanicLevel, + }, + } + + for _, tt := range tests { + msg := fmt.Sprintf("increase %v to %v", tt.coreLevel, tt.increaseLevel) + t.Run(msg, func(t *testing.T) { + logger, _ := observer.New(tt.coreLevel) + + filteredLogger, err := NewIncreaseLevelCore(logger, tt.increaseLevel) + if tt.wantErr { + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid increase level") + return + } + + require.NoError(t, err) + + for l := DebugLevel; l <= FatalLevel; l++ { + enabled := filteredLogger.Enabled(l) + ce := filteredLogger.Check(Entry{Level: l}, nil) + + if l >= tt.increaseLevel { + assert.True(t, enabled, "expect %v to be enabled", l) + assert.NotNil(t, ce, "expect non-nil Check") + } else { + assert.False(t, enabled, "expect %v to be disabled", l) + assert.Nil(t, ce, "expect nil Check") + } + } + }) + } +}