-
Notifications
You must be signed in to change notification settings - Fork 18.6k
Description
Edit: as discussed in the comments, the proposal is not anymore about using a nil receiver. It is still about making it cheaper to call a disabled (muted) Logger.
What
Make this nil pointer variable
var nilLogger *log.Logger
a valid Logger, where all the calls to nilLogger.Print, etc. are valid (do not crash), and do not produce any actual logs.
This would serve as an adequate replacement of, and almost equivalent to:
var discardLogger = log.New(io.Discard, "", 0)
Why
Performance.
This is an important concern because logging is often expensive, and it is a reasonable expectation that it becomes free (or very cheap) when logging is turned off. Currently, discardLogger comes with a quite expensive overhead:
- all of the
discardLogger.Printvariadic arguments are evaluated, even if they are not really needed; - a non-inlined function call to
(*Logger).Printis executed fmt.Sprintis called, which incurs some memory allocation, and some work to use the variadic arguments in order to produce a strings;- the mutex
discardLogger.muisLocked, then laterUnlocked; sis copied to the internal bufferdiscardLogger.buf;- a dynamic function dispatch is executed for
discardLogger.out.Write, which resolves toio.discard.Write.
The proposed implementation consists of checking for nil receiver inside all of the exported methods and returning immediately, thus avoiding steps 3, 4, 5, 6.
In the proposed implementation, the steps 1, 2 still happen, as the arguments are still evaluated. In theory this could be mitigated in some side-effect-free cases, with advanced escape analysis and inlining. This mitigation is out of reach of the current inlining system, and out of scope of the current proposal.
Benefits
In a sample benchmark, nilLogger.Printf is 35x faster with an int arg, and 320x faster with a map arg, than discardLogger.Printf.
The speedup would be higher with more arguments, or more complex arguments.
As a side effect, the zero value for the pointer type *log.Logger becomes useful and safe, without initialization.
Backwards compatibility
- No changes to all the non-
nil*log.Logger instances. log.New(io.Discard, "", 0)still works.nilLogger.Printwould not panic anymore. A program relying on the panic behavior would break. It is however not a promise (no guarantee) from the log package to havenilLogger.Printpanic.nilLogger.Printwould not callfmt.Sprintanymore, resulting in some customStringmethods to not be called. A program relying the side effects of custom String methods would break. It is however not a promise (no guarantee) from the log package to have valid Loggers always callfmt.Sprint.- A program may expect
l.SetOutput(w)to always succeeds for any valid loggerl. Such a program would break. - A program may expect
l.SetPrefix(prefix)to force the subsequent value returned byl.Prefix(), for any valid loggerl. Such a program would break. - A program may expect
l.SetFlags(flag)to force the subsequent value returned byl.Flags(), for any valid loggerl. Such a program would break.
Concurrency
It is my understanding that the proposed implementation does not introduce any new risks of data races.
For a nil receiver, all of the Print methods are thread-safe, and do not require a mutex.
Detailed behavior of each method
When the receiver l is nil:
(l *Logger).Fatal(v ...interface{})(l *Logger).Fatalf(format string, v ...interface{})(l *Logger).Fatalln(v ...interface{}):- don't log anything. Call
os.Exit(1).
- don't log anything. Call
(l *Logger).Flags() int:- return
0.
- return
(l *Logger).Output(calldepth int, s string) error:- do nothing. Return
nil.
- do nothing. Return
(l *Logger).Panic(v ...interface{})(l *Logger).Panicf(format string, v ...interface{})(l *Logger).Panicln(v ...interface{}):- don't log anything. Build the string
sand callpanic(s).
- don't log anything. Build the string
(l *Logger).Prefix() string:- return
"".
- return
(l *Logger).Print(v ...interface{})(l *Logger).Printf(format string, v ...interface{})(l *Logger).Println(v ...interface{}):- do nothing.
(l *Logger).SetFlags(flag int):- do nothing.
(l *Logger).SetOutput(w io.Writer):- panic. The caller would expect
wto be subsequently used, which is not possible.
- panic. The caller would expect
(l *Logger).SetPrefix(prefix string):- do nothing.
(l *Logger).Writer() io.Writer:- return
nil.
- return