log: fix SetDefault for custom loggers #31368 (#1511)

Currently, even though it takes in a `Logger` interface,
`log.SetDefualt` enforces that the concrete type of the provided logger
is `*logger` because:
1. in `init` `root.Store` is called with a `*logger`
2. `atomic.Value` panics if the concrete type provided in `Store` is not
consistent across calls.
([ref](https://pkg.go.dev/sync/atomic#Value.Store))

> All calls to Store for a given Value must use values of the same
concrete type.

This PR changes to use `sync.RWMutex` and adds a test that panics on
`master`.

Co-authored-by: Stephen Buttolph <stephen@avalabs.org>
This commit is contained in:
Daniel Liu 2025-09-17 08:28:13 +08:00 committed by GitHub
parent 99e2885b03
commit 915c71f2c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 33 additions and 5 deletions

View file

@ -4,18 +4,24 @@ import (
"context" "context"
"log/slog" "log/slog"
"os" "os"
"sync/atomic" "sync"
) )
var root atomic.Value var (
rootLock sync.RWMutex
root Logger
)
func init() { func init() {
root.Store(&logger{slog.New(DiscardHandler())}) root = &logger{slog.New(DiscardHandler())}
} }
// SetDefault sets the default global logger // SetDefault sets the default global logger
func SetDefault(l Logger) { func SetDefault(l Logger) {
root.Store(l) rootLock.Lock()
defer rootLock.Unlock()
root = l
if lg, ok := l.(*logger); ok { if lg, ok := l.(*logger); ok {
slog.SetDefault(lg.inner) slog.SetDefault(lg.inner)
} }
@ -23,7 +29,10 @@ func SetDefault(l Logger) {
// Root returns the root logger // Root returns the root logger
func Root() Logger { func Root() Logger {
return root.Load().(Logger) rootLock.RLock()
defer rootLock.RUnlock()
return root
} }
// The following functions bypass the exported logger methods (logger.Debug, // The following functions bypass the exported logger methods (logger.Debug,

19
log/root_test.go Normal file
View file

@ -0,0 +1,19 @@
package log
import (
"testing"
)
// SetDefault should properly set the default logger when custom loggers are
// provided.
func TestSetDefaultCustomLogger(t *testing.T) {
type customLogger struct {
Logger // Implement the Logger interface
}
customLog := &customLogger{}
SetDefault(customLog)
if Root() != customLog {
t.Error("expected custom logger to be set as default")
}
}