From 323c6f74d35ec29baac2a1aba4270f89b022815a Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Thu, 9 Sep 2021 14:49:41 -0700 Subject: log: don't format if writing to io.Discard Fixes #47164 Change-Id: Ied03842360be4c86f1d9ead816f12c057a1f8dad Reviewed-on: https://go-review.googlesource.com/c/go/+/348741 Trust: Ian Lance Taylor Run-TryBot: Ian Lance Taylor TryBot-Result: Go Bot Reviewed-by: Emmanuel Odeke Reviewed-by: Valentin Deleplace --- src/log/log.go | 53 ++++++++++++++++++++++++++++++++++++++++++----------- src/log/log_test.go | 14 +++++++++++++- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/log/log.go b/src/log/log.go index b77af29032..3172384718 100644 --- a/src/log/log.go +++ b/src/log/log.go @@ -20,6 +20,7 @@ import ( "os" "runtime" "sync" + "sync/atomic" "time" ) @@ -50,11 +51,12 @@ const ( // the Writer's Write method. A Logger can be used simultaneously from // multiple goroutines; it guarantees to serialize access to the Writer. type Logger struct { - mu sync.Mutex // ensures atomic writes; protects the following fields - prefix string // prefix on each line to identify the logger (but see Lmsgprefix) - flag int // properties - out io.Writer // destination for output - buf []byte // for accumulating text to write + mu sync.Mutex // ensures atomic writes; protects the following fields + prefix string // prefix on each line to identify the logger (but see Lmsgprefix) + flag int // properties + out io.Writer // destination for output + buf []byte // for accumulating text to write + isDiscard int32 // atomic boolean: whether out == io.Discard } // New creates a new Logger. The out variable sets the @@ -63,7 +65,11 @@ type Logger struct { // after the log header if the Lmsgprefix flag is provided. // The flag argument defines the logging properties. func New(out io.Writer, prefix string, flag int) *Logger { - return &Logger{out: out, prefix: prefix, flag: flag} + l := &Logger{out: out, prefix: prefix, flag: flag} + if out == io.Discard { + l.isDiscard = 1 + } + return l } // SetOutput sets the output destination for the logger. @@ -71,6 +77,11 @@ func (l *Logger) SetOutput(w io.Writer) { l.mu.Lock() defer l.mu.Unlock() l.out = w + isDiscard := int32(0) + if w == io.Discard { + isDiscard = 1 + } + atomic.StoreInt32(&l.isDiscard, isDiscard) } var std = New(os.Stderr, "", LstdFlags) @@ -188,16 +199,29 @@ func (l *Logger) Output(calldepth int, s string) error { // Printf calls l.Output to print to the logger. // Arguments are handled in the manner of fmt.Printf. func (l *Logger) Printf(format string, v ...interface{}) { + if atomic.LoadInt32(&l.isDiscard) != 0 { + return + } l.Output(2, fmt.Sprintf(format, v...)) } // Print calls l.Output to print to the logger. // Arguments are handled in the manner of fmt.Print. -func (l *Logger) Print(v ...interface{}) { l.Output(2, fmt.Sprint(v...)) } +func (l *Logger) Print(v ...interface{}) { + if atomic.LoadInt32(&l.isDiscard) != 0 { + return + } + l.Output(2, fmt.Sprint(v...)) +} // Println calls l.Output to print to the logger. // Arguments are handled in the manner of fmt.Println. -func (l *Logger) Println(v ...interface{}) { l.Output(2, fmt.Sprintln(v...)) } +func (l *Logger) Println(v ...interface{}) { + if atomic.LoadInt32(&l.isDiscard) != 0 { + return + } + l.Output(2, fmt.Sprintln(v...)) +} // Fatal is equivalent to l.Print() followed by a call to os.Exit(1). func (l *Logger) Fatal(v ...interface{}) { @@ -277,9 +301,7 @@ func (l *Logger) Writer() io.Writer { // SetOutput sets the output destination for the standard logger. func SetOutput(w io.Writer) { - std.mu.Lock() - defer std.mu.Unlock() - std.out = w + std.SetOutput(w) } // Flags returns the output flags for the standard logger. @@ -314,18 +336,27 @@ func Writer() io.Writer { // Print calls Output to print to the standard logger. // Arguments are handled in the manner of fmt.Print. func Print(v ...interface{}) { + if atomic.LoadInt32(&std.isDiscard) != 0 { + return + } std.Output(2, fmt.Sprint(v...)) } // Printf calls Output to print to the standard logger. // Arguments are handled in the manner of fmt.Printf. func Printf(format string, v ...interface{}) { + if atomic.LoadInt32(&std.isDiscard) != 0 { + return + } std.Output(2, fmt.Sprintf(format, v...)) } // Println calls Output to print to the standard logger. // Arguments are handled in the manner of fmt.Println. func Println(v ...interface{}) { + if atomic.LoadInt32(&std.isDiscard) != 0 { + return + } std.Output(2, fmt.Sprintln(v...)) } diff --git a/src/log/log_test.go b/src/log/log_test.go index 5be8e82258..938ed42357 100644 --- a/src/log/log_test.go +++ b/src/log/log_test.go @@ -9,6 +9,7 @@ package log import ( "bytes" "fmt" + "io" "os" "regexp" "strings" @@ -20,7 +21,7 @@ const ( Rdate = `[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9]` Rtime = `[0-9][0-9]:[0-9][0-9]:[0-9][0-9]` Rmicroseconds = `\.[0-9][0-9][0-9][0-9][0-9][0-9]` - Rline = `(60|62):` // must update if the calls to l.Printf / l.Print below move + Rline = `(61|63):` // must update if the calls to l.Printf / l.Print below move Rlongfile = `.*/[A-Za-z0-9_\-]+\.go:` + Rline Rshortfile = `[A-Za-z0-9_\-]+\.go:` + Rline ) @@ -179,6 +180,17 @@ func TestEmptyPrintCreatesLine(t *testing.T) { } } +func TestDiscard(t *testing.T) { + l := New(io.Discard, "", 0) + s := strings.Repeat("a", 102400) + c := testing.AllocsPerRun(100, func() { l.Printf("%s", s) }) + // One allocation for slice passed to Printf, + // but none for formatting of long string. + if c > 1 { + t.Errorf("got %v allocs, want at most 1", c) + } +} + func BenchmarkItoa(b *testing.B) { dst := make([]byte, 0, 64) for i := 0; i < b.N; i++ { -- cgit v1.2.3-54-g00ecf