Repository: hashicorp/go-hclog
Pull request #80 (2020-10-22 17:50:05)

add IndependentLevels
Add an option so that subloggers are created with independent levels,
and using SetLevel does not effect other loggers.


Patch:
diff --git a/intlogger.go b/intlogger.go
index ea2b91b..f961ed9 100644
--- a/intlogger.go
+++ b/intlogger.go
@@ -66,6 +66,9 @@ type intLogger struct {
 	implied []interface{}
 
 	exclude func(level Level, msg string, args ...interface{}) bool
+
+	// create subloggers with their own level setting
+	independentLevels bool
 }
 
 // New returns a configured logger.
@@ -100,14 +103,15 @@ func newLogger(opts *LoggerOptions) *intLogger {
 	}
 
 	l := &intLogger{
-		json:       opts.JSONFormat,
-		caller:     opts.IncludeLocation,
-		name:       opts.Name,
-		timeFormat: TimeFormat,
-		mutex:      mutex,
-		writer:     newWriter(output, opts.Color),
-		level:      new(int32),
-		exclude:    opts.Exclude,
+		json:              opts.JSONFormat,
+		caller:            opts.IncludeLocation,
+		name:              opts.Name,
+		timeFormat:        TimeFormat,
+		mutex:             mutex,
+		writer:            newWriter(output, opts.Color),
+		level:             new(int32),
+		exclude:           opts.Exclude,
+		independentLevels: opts.IndependentLevels,
 	}
 
 	l.setColorization(opts)
@@ -514,7 +518,7 @@ func (l *intLogger) With(args ...interface{}) Logger {
 		args = args[:len(args)-1]
 	}
 
-	sl := *l
+	sl := l.copy()
 
 	result := make(map[string]interface{}, len(l.implied)+len(args))
 	keys := make([]string, 0, len(l.implied)+len(args))
@@ -548,13 +552,13 @@ func (l *intLogger) With(args ...interface{}) Logger {
 		sl.implied = append(sl.implied, MissingKey, extra)
 	}
 
-	return &sl
+	return sl
 }
 
 // Create a new sub-Logger that a name decending from the current name.
 // This is used to create a subsystem specific Logger.
 func (l *intLogger) Named(name string) Logger {
-	sl := *l
+	sl := l.copy()
 
 	if sl.name != "" {
 		sl.name = sl.name + "." + name
@@ -562,18 +566,18 @@ func (l *intLogger) Named(name string) Logger {
 		sl.name = name
 	}
 
-	return &sl
+	return sl
 }
 
 // Create a new sub-Logger with an explicit name. This ignores the current
 // name. This is used to create a standalone logger that doesn't fall
 // within the normal hierarchy.
 func (l *intLogger) ResetNamed(name string) Logger {
-	sl := *l
+	sl := l.copy()
 
 	sl.name = name
 
-	return &sl
+	return sl
 }
 
 func (l *intLogger) ResetOutput(opts *LoggerOptions) error {
@@ -660,3 +664,16 @@ func (i *intLogger) ImpliedArgs() []interface{} {
 func (i *intLogger) Name() string {
 	return i.name
 }
+
+// copy returns a shallow copy of the intLogger, replacing the level pointer
+// when necessary
+func (l *intLogger) copy() *intLogger {
+	sl := *l
+
+	if l.independentLevels {
+		sl.level = new(int32)
+		*sl.level = *l.level
+	}
+
+	return &sl
+}
diff --git a/logger.go b/logger.go
index 68da277..83eafc1 100644
--- a/logger.go
+++ b/logger.go
@@ -186,7 +186,8 @@ type Logger interface {
 	// the current name as well.
 	ResetNamed(name string) Logger
 
-	// Updates the level. This should affect all sub-loggers as well. If an
+	// Updates the level. This should affect all related loggers as well,
+	// unless they were created with IndependentLevels. If an
 	// implementation cannot update the level on the fly, it should no-op.
 	SetLevel(level Level)
 
@@ -250,6 +251,12 @@ type LoggerOptions struct {
 	// This is useful when interacting with a system that you wish to suppress the log
 	// message for (because it's too noisy, etc)
 	Exclude func(level Level, msg string, args ...interface{}) bool
+
+	// IndependentLevels causes subloggers to be created with an independent
+	// copy of this logger's level. This means that using SetLevel on this
+	// logger will not effect any subloggers, and SetLevel on any subloggers
+	// will not effect the parent or sibling loggers.
+	IndependentLevels bool
 }
 
 // InterceptLogger describes the interface for using a logger


Test patch:
diff --git a/logger_test.go b/logger_test.go
index 8c0453e..aac7233 100644
--- a/logger_test.go
+++ b/logger_test.go
@@ -419,8 +419,9 @@ func TestLogger(t *testing.T) {
 
 		logger := New(&LoggerOptions{
 			// No name!
-			Output: &buf,
-			Level:  Off,
+			Output:            &buf,
+			Level:             Off,
+			IndependentLevels: true,
 		})
 
 		logger.Info("this is test")
@@ -438,6 +439,13 @@ func TestLogger(t *testing.T) {
 		dataIdx := strings.IndexByte(str, ' ')
 		rest := str[dataIdx+1:]
 		assert.Equal(t, "[INFO]  sublogger: this is test\n", rest)
+
+		buf.Reset()
+		logger.Info("parent should still be quiet")
+		str = buf.String()
+		if len(str) > 0 {
+			t.Fatal("output from disabled logger:", str)
+		}
 	})
 }