aboutsummaryrefslogtreecommitdiff
path: root/src/log
diff options
context:
space:
mode:
authorAndy Pan <panjf2000@gmail.com>2023-09-04 14:37:13 +0800
committerJonathan Amsterdam <jba@google.com>2023-11-10 21:25:30 +0000
commit3188758653fc7d2b229e234273d41878ddfdd5f2 (patch)
tree62e4e21f0486b232d8c0b161ad52523a6ebd8901 /src/log
parentea14b633627f467a2e35eb7f67df6f3ed60469c0 (diff)
downloadgo-3188758653fc7d2b229e234273d41878ddfdd5f2.tar.gz
go-3188758653fc7d2b229e234273d41878ddfdd5f2.zip
log/slog: add LogLoggerLevel to enable setting level on the default logger
Fixes #62418 Change-Id: I889a53d00c8a463b4d7ddb41893c000d7cd0e7b8 Reviewed-on: https://go-review.googlesource.com/c/go/+/525096 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Heschi Kreinick <heschi@google.com> Reviewed-by: Jonathan Amsterdam <jba@google.com>
Diffstat (limited to 'src/log')
-rw-r--r--src/log/slog/example_log_level_test.go58
-rw-r--r--src/log/slog/handler.go2
-rw-r--r--src/log/slog/logger.go42
-rw-r--r--src/log/slog/logger_test.go66
4 files changed, 162 insertions, 6 deletions
diff --git a/src/log/slog/example_log_level_test.go b/src/log/slog/example_log_level_test.go
new file mode 100644
index 0000000000..ca8db416e5
--- /dev/null
+++ b/src/log/slog/example_log_level_test.go
@@ -0,0 +1,58 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog_test
+
+import (
+ "log"
+ "log/slog"
+ "log/slog/internal/slogtest"
+ "os"
+)
+
+// This example shows how to use slog.SetLogLoggerLevel to change the minimal level
+// of the internal default handler for slog package before calling slog.SetDefault.
+func ExampleSetLogLoggerLevel_log() {
+ defer log.SetFlags(log.Flags()) // revert changes after the example
+ log.SetFlags(0)
+ defer log.SetOutput(log.Writer()) // revert changes after the example
+ log.SetOutput(os.Stdout)
+
+ // Default logging level is slog.LevelInfo.
+ log.Print("log debug") // log debug
+ slog.Debug("debug") // no output
+ slog.Info("info") // INFO info
+
+ // Set the default logging level to slog.LevelDebug.
+ currentLogLevel := slog.SetLogLoggerLevel(slog.LevelDebug)
+ defer slog.SetLogLoggerLevel(currentLogLevel) // revert changes after the example
+
+ log.Print("log debug") // log debug
+ slog.Debug("debug") // DEBUG debug
+ slog.Info("info") // INFO info
+
+ // Output:
+ // log debug
+ // INFO info
+ // log debug
+ // DEBUG debug
+ // INFO info
+}
+
+// This example shows how to use slog.SetLogLoggerLevel to change the minimal level
+// of the internal writer that uses the custom handler for log package after
+// calling slog.SetDefault.
+func ExampleSetLogLoggerLevel_slog() {
+ // Set the default logging level to slog.LevelError.
+ currentLogLevel := slog.SetLogLoggerLevel(slog.LevelError)
+ defer slog.SetLogLoggerLevel(currentLogLevel) // revert changes after the example
+
+ defer slog.SetDefault(slog.Default()) // revert changes after the example
+ slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: slogtest.RemoveTime})))
+
+ log.Print("error") // level=ERROR msg=error
+
+ // Output:
+ // level=ERROR msg=error
+}
diff --git a/src/log/slog/handler.go b/src/log/slog/handler.go
index a6c643cdb9..9f6d88b169 100644
--- a/src/log/slog/handler.go
+++ b/src/log/slog/handler.go
@@ -100,7 +100,7 @@ func newDefaultHandler(output func(uintptr, []byte) error) *defaultHandler {
}
func (*defaultHandler) Enabled(_ context.Context, l Level) bool {
- return l >= LevelInfo
+ return l >= logLoggerLevel.Level()
}
// Collect the level, attributes and message in a string and
diff --git a/src/log/slog/logger.go b/src/log/slog/logger.go
index fceafe0cba..f03aeec295 100644
--- a/src/log/slog/logger.go
+++ b/src/log/slog/logger.go
@@ -16,6 +16,36 @@ import (
var defaultLogger atomic.Pointer[Logger]
+var logLoggerLevel LevelVar
+
+// SetLogLoggerLevel controls the level for the bridge to the [log] package.
+//
+// Before [SetDefault] is called, slog top-level logging functions call the default [log.Logger].
+// In that mode, SetLogLoggerLevel sets the minimum level for those calls.
+// By default, the minimum level is Info, so calls to [Debug]
+// (as well as top-level logging calls at lower levels)
+// will not be passed to the log.Logger. After calling
+//
+// slog.SetLogLoggerLevel(slog.LevelDebug)
+//
+// calls to [Debug] will be passed to the log.Logger.
+//
+// After [SetDefault] is called, calls to the default [log.Logger] are passed to the
+// slog default handler. In that mode,
+// SetLogLoggerLevel sets the level at which those calls are logged.
+// That is, after calling
+//
+// slog.SetLogLoggerLevel(slog.LevelDebug)
+//
+// A call to [log.Printf] will result in output at level [LevelDebug].
+//
+// SetLogLoggerLevel returns the previous value.
+func SetLogLoggerLevel(level Level) (oldLevel Level) {
+ oldLevel = logLoggerLevel.Level()
+ logLoggerLevel.Set(level)
+ return
+}
+
func init() {
defaultLogger.Store(New(newDefaultHandler(loginternal.DefaultOutput)))
}
@@ -25,7 +55,8 @@ func Default() *Logger { return defaultLogger.Load() }
// SetDefault makes l the default [Logger].
// After this call, output from the log package's default Logger
-// (as with [log.Print], etc.) will be logged at [LevelInfo] using l's Handler.
+// (as with [log.Print], etc.) will be logged using l's Handler,
+// at a level controlled by [SetLogLoggerLevel].
func SetDefault(l *Logger) {
defaultLogger.Store(l)
// If the default's handler is a defaultHandler, then don't use a handleWriter,
@@ -36,7 +67,7 @@ func SetDefault(l *Logger) {
// See TestSetDefault.
if _, ok := l.Handler().(*defaultHandler); !ok {
capturePC := log.Flags()&(log.Lshortfile|log.Llongfile) != 0
- log.SetOutput(&handlerWriter{l.Handler(), LevelInfo, capturePC})
+ log.SetOutput(&handlerWriter{l.Handler(), &logLoggerLevel, capturePC})
log.SetFlags(0) // we want just the log message, no time or location
}
}
@@ -45,12 +76,13 @@ func SetDefault(l *Logger) {
// It is used to link the default log.Logger to the default slog.Logger.
type handlerWriter struct {
h Handler
- level Level
+ level Leveler
capturePC bool
}
func (w *handlerWriter) Write(buf []byte) (int, error) {
- if !w.h.Enabled(context.Background(), w.level) {
+ level := w.level.Level()
+ if !w.h.Enabled(context.Background(), level) {
return 0, nil
}
var pc uintptr
@@ -66,7 +98,7 @@ func (w *handlerWriter) Write(buf []byte) (int, error) {
if len(buf) > 0 && buf[len(buf)-1] == '\n' {
buf = buf[:len(buf)-1]
}
- r := NewRecord(time.Now(), w.level, string(buf), pc)
+ r := NewRecord(time.Now(), level, string(buf), pc)
return origLen, w.h.Handle(context.Background(), r)
}
diff --git a/src/log/slog/logger_test.go b/src/log/slog/logger_test.go
index 88aa38ee0c..bb1c8a16ea 100644
--- a/src/log/slog/logger_test.go
+++ b/src/log/slog/logger_test.go
@@ -191,6 +191,7 @@ func TestCallDepth(t *testing.T) {
}
}
+ defer SetDefault(Default()) // restore
logger := New(h)
SetDefault(logger)
@@ -363,6 +364,71 @@ func TestSetDefault(t *testing.T) {
}
}
+// Test defaultHandler minimum level without calling slog.SetDefault.
+func TestLogLoggerLevelForDefaultHandler(t *testing.T) {
+ // Revert any changes to the default logger, flags, and level of log and slog.
+ currentLogLoggerLevel := logLoggerLevel.Level()
+ currentLogWriter := log.Writer()
+ currentLogFlags := log.Flags()
+ t.Cleanup(func() {
+ logLoggerLevel.Set(currentLogLoggerLevel)
+ log.SetOutput(currentLogWriter)
+ log.SetFlags(currentLogFlags)
+ })
+
+ var logBuf bytes.Buffer
+ log.SetOutput(&logBuf)
+ log.SetFlags(0)
+
+ for _, test := range []struct {
+ logLevel Level
+ logFn func(string, ...any)
+ want string
+ }{
+ {LevelDebug, Debug, "DEBUG a"},
+ {LevelDebug, Info, "INFO a"},
+ {LevelInfo, Debug, ""},
+ {LevelInfo, Info, "INFO a"},
+ } {
+ SetLogLoggerLevel(test.logLevel)
+ test.logFn("a")
+ checkLogOutput(t, logBuf.String(), test.want)
+ logBuf.Reset()
+ }
+}
+
+// Test handlerWriter minimum level by calling slog.SetDefault.
+func TestLogLoggerLevelForHandlerWriter(t *testing.T) {
+ removeTime := func(_ []string, a Attr) Attr {
+ if a.Key == TimeKey {
+ return Attr{}
+ }
+ return a
+ }
+
+ // Revert any changes to the default logger. This is important because other
+ // tests might change the default logger using SetDefault. Also ensure we
+ // restore the default logger at the end of the test.
+ currentLogger := Default()
+ currentLogLoggerLevel := logLoggerLevel.Level()
+ currentLogWriter := log.Writer()
+ currentFlags := log.Flags()
+ t.Cleanup(func() {
+ SetDefault(currentLogger)
+ logLoggerLevel.Set(currentLogLoggerLevel)
+ log.SetOutput(currentLogWriter)
+ log.SetFlags(currentFlags)
+ })
+
+ var logBuf bytes.Buffer
+ log.SetOutput(&logBuf)
+ log.SetFlags(0)
+ SetLogLoggerLevel(LevelError)
+ SetDefault(New(NewTextHandler(&logBuf, &HandlerOptions{ReplaceAttr: removeTime})))
+ log.Print("error")
+ checkLogOutput(t, logBuf.String(), `level=ERROR msg=error`)
+}
+
func TestLoggerError(t *testing.T) {
var buf bytes.Buffer