]> Cypherpunks.ru repositories - gostls13.git/commitdiff
log/slog: add LogLoggerLevel to enable setting level on the default logger
authorAndy Pan <panjf2000@gmail.com>
Mon, 4 Sep 2023 06:37:13 +0000 (14:37 +0800)
committerJonathan Amsterdam <jba@google.com>
Fri, 10 Nov 2023 21:25:30 +0000 (21:25 +0000)
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>
api/next/62418.txt [new file with mode: 0644]
src/log/slog/example_log_level_test.go [new file with mode: 0644]
src/log/slog/handler.go
src/log/slog/logger.go
src/log/slog/logger_test.go

diff --git a/api/next/62418.txt b/api/next/62418.txt
new file mode 100644 (file)
index 0000000..fd482f4
--- /dev/null
@@ -0,0 +1 @@
+pkg log/slog, func SetLogLoggerLevel(Level) Level #62418
diff --git a/src/log/slog/example_log_level_test.go b/src/log/slog/example_log_level_test.go
new file mode 100644 (file)
index 0000000..ca8db41
--- /dev/null
@@ -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
+}
index a6c643cdb94b4fd22ebb0ad2fca687e9898c911f..9f6d88b1696bf71b2f046f8527e4bf1b1c34cc25 100644 (file)
@@ -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
index fceafe0cbad87ff27864a9822b88f2802bec36b2..f03aeec295cb08ea71fb2958d03c09812a1a09e5 100644 (file)
@@ -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)
 }
 
index 88aa38ee0c973ba8c1aafdeecba5c60daf9c7d69..bb1c8a16ea46ac4b988c9a0ab27645f7e8c93440 100644 (file)
@@ -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