Repository: hashicorp/go-hclog
Pull request #40 (2019-04-25 00:32:32)

Allow Standard Logger to force log level
Allow forcing the log level when using the standard logger. This is
useful for scenarios when the caller of the standard logger will only
ever emit logs at a known level. An example of this is in the stdlib
with the HTTP reverse proxy:
https://golang.org/src/net/http/httputil/reverseproxy.go?s=1370:1578


Patch:
diff --git a/intlogger.go b/intlogger.go
index 6d2a87a..219656c 100644
--- a/intlogger.go
+++ b/intlogger.go
@@ -519,5 +519,9 @@ func (l *intLogger) StandardLogger(opts *StandardLoggerOptions) *log.Logger {
 }
 
 func (l *intLogger) StandardWriter(opts *StandardLoggerOptions) io.Writer {
-	return &stdlogAdapter{l, opts.InferLevels}
+	return &stdlogAdapter{
+		log:         l,
+		inferLevels: opts.InferLevels,
+		forceLevel:  opts.ForceLevel,
+	}
 }
diff --git a/logger.go b/logger.go
index 0f1f826..080ed79 100644
--- a/logger.go
+++ b/logger.go
@@ -143,6 +143,12 @@ type StandardLoggerOptions struct {
 	// This supports the strings like [ERROR], [ERR] [TRACE], [WARN], [INFO],
 	// [DEBUG] and strip it off before reapplying it.
 	InferLevels bool
+
+	// ForceLevel is used to force all output from the standard logger to be at
+	// the specified level. Similar to InferLevels, this will strip any level
+	// prefix contained in the logged string before applying the forced level.
+	// If set, this override InferLevels.
+	ForceLevel Level
 }
 
 // LoggerOptions can be used to configure a new logger.
diff --git a/stdlog.go b/stdlog.go
index 913d523..044a469 100644
--- a/stdlog.go
+++ b/stdlog.go
@@ -11,6 +11,7 @@ import (
 type stdlogAdapter struct {
 	log         Logger
 	inferLevels bool
+	forceLevel  Level
 }
 
 // Take the data, infer the levels if configured, and send it through
@@ -18,7 +19,27 @@ type stdlogAdapter struct {
 func (s *stdlogAdapter) Write(data []byte) (int, error) {
 	str := string(bytes.TrimRight(data, " \t\n"))
 
-	if s.inferLevels {
+	if s.forceLevel != NoLevel {
+		// Use pickLevel to strip log levels included in the line since we are
+		// forcing the level
+		_, str := s.pickLevel(str)
+
+		// Log at the forced level
+		switch s.forceLevel {
+		case Trace:
+			s.log.Trace(str)
+		case Debug:
+			s.log.Debug(str)
+		case Info:
+			s.log.Info(str)
+		case Warn:
+			s.log.Warn(str)
+		case Error:
+			s.log.Error(str)
+		default:
+			s.log.Info(str)
+		}
+	} else if s.inferLevels {
 		level, str := s.pickLevel(str)
 		switch level {
 		case Trace:


Test patch:
diff --git a/stdlog_test.go b/stdlog_test.go
index 8de180c..1de8757 100644
--- a/stdlog_test.go
+++ b/stdlog_test.go
@@ -1,6 +1,8 @@
 package hclog
 
 import (
+	"bytes"
+	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -61,3 +63,78 @@ func TestStdlogAdapter(t *testing.T) {
 		assert.Equal(t, "coffee?", rest)
 	})
 }
+
+func TestStdlogAdapter_ForceLevel(t *testing.T) {
+	cases := []struct {
+		name        string
+		forceLevel  Level
+		inferLevels bool
+		write       string
+		expect      string
+	}{
+		{
+			name:       "force error",
+			forceLevel: Error,
+			write:      "this is a test",
+			expect:     "[ERROR] test: this is a test\n",
+		},
+		{
+			name:        "force error overrides infer",
+			forceLevel:  Error,
+			inferLevels: true,
+			write:       "[DEBUG] this is a test",
+			expect:      "[ERROR] test: this is a test\n",
+		},
+		{
+			name:       "force error and strip debug",
+			forceLevel: Error,
+			write:      "[DEBUG] this is a test",
+			expect:     "[ERROR] test: this is a test\n",
+		},
+		{
+			name:       "force trace",
+			forceLevel: Trace,
+			write:      "this is a test",
+			expect:     "[TRACE] test: this is a test\n",
+		},
+		{
+			name:       "force trace and strip higher level error",
+			forceLevel: Trace,
+			write:      "[WARN] this is a test",
+			expect:     "[TRACE] test: this is a test\n",
+		},
+		{
+			name:       "force with invalid level",
+			forceLevel: -10,
+			write:      "this is a test",
+			expect:     "[INFO]  test: this is a test\n",
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			var stderr bytes.Buffer
+
+			logger := New(&LoggerOptions{
+				Name:   "test",
+				Output: &stderr,
+				Level:  Trace,
+			})
+
+			s := &stdlogAdapter{
+				log:         logger,
+				forceLevel:  c.forceLevel,
+				inferLevels: c.inferLevels,
+			}
+
+			_, err := s.Write([]byte(c.write))
+			assert.NoError(t, err)
+
+			errStr := stderr.String()
+			errDataIdx := strings.IndexByte(errStr, ' ')
+			errRest := errStr[errDataIdx+1:]
+
+			assert.Equal(t, c.expect, errRest)
+		})
+	}
+}