59

I have some heavily instrumented code that makes use of the log package. Now it's come time to turn off the logging, and I can't determine how to turn off the standard logger.

Have I missed something? Should I be checking a flag before making log calls, or commenting them out in production?

6 Answers 6

137

No reason to create your own type for a common io.Writer when one exists in the io/ioutil package.

import ( "log" "io/ioutil" ) func init() { log.SetOutput(ioutil.Discard) } 

From Go 1.16, just use io.Discard

package ioutil // import "io/ioutil"

var Discard io.Writer = io.Discard Discard is an io.Writer on which all Write calls succeed without doing anything.

Deprecated: As of Go 1.16, this value is simply io.Discard.

log.SetOutput(io.Discard) 
Sign up to request clarification or add additional context in comments.

7 Comments

Great find! I looked for such a thing in the standard libraries, but didn't check io/ioutil.
Will this disable the logger only for the package, or the whole application?
Since this skips writing to disk but still does all the formatting, a call to log.SetFlags(0) should ease it a bit, I think
This is really nice to have when you need to suppress logging for tests/benchmarks. Was about to mock out my own logger before I came across this.
@Joril indeed I confirm with this benchmark that a non-zero flag does incur a performance penalty (in Go 1.17): gist.github.com/Deleplace/ad33f8c6a96e9938e83e4f9cbcc608e0
|
42

For completely disabling logs, it's actually better to call log.SetFlags(0)Joril and set the output to a no-op io.Writer (i.e., log.SetOutput(ioutil.Discard))

But even after this, the operations will idle around 500-600 ns/op1

This can still be cut short (to around 100 ns/op) by using a custom Logger implementation, and implementing all the functions to be no-op -- as demonstrated here (only overriding Println for bervity).

The alternative to all these is to use a custom logging framework with levels and set it to complete OFF.

Note though, one of the commonly used library for logging (logrus) has performance implications -- the same can be found in the benchmarks where it perform with 3K+ ns/op, regardless.

Biased opinion: from the benchmarks, the library go-logging performs in par with the custom Logger implementation when setting the Level to -1, regardless of the backend and formatting

(the benchmark source can be found here)

the output of the benchmark is as follows:

testing: warning: no tests to run PASS BenchmarkGoLogging-4 1000000 2068 ns/op BenchmarkGoLoggingNullBackend-4 5000000 308 ns/op BenchmarkGoLoggingNullBackendWithFancyFormatter-4 3000000 435 ns/op BenchmarkGoLoggingOffLevel-4 20000000 109 ns/op BenchmarkGoLoggingNullBackendAndOffLevel-4 20000000 108 ns/op BenchmarkGoLoggingNullBackendWithFancyFormatterAndOffLevel-4 20000000 109 ns/op BenchmarkLog15-4 200000 7359 ns/op BenchmarkLog15WithDiscardHandler-4 2000000 922 ns/op BenchmarkLog15WithDiscardHandlerAndOffLevel-4 2000000 926 ns/op BenchmarkLog15WithNopLogger-4 20000000 108 ns/op BenchmarkLog15WithNopLoggerDiscardHandlerA-4 20000000 112 ns/op BenchmarkLog15WithNopLoggerAndDiscardHandlerAndOffLevel-4 20000000 112 ns/op BenchmarkLog-4 1000000 1217 ns/op BenchmarkLogIoDiscardWriter-4 2000000 724 ns/op BenchmarkLogIoDiscardWriterWithoutFlags-4 3000000 543 ns/op BenchmarkLogCustomNullWriter-4 2000000 731 ns/op BenchmarkLogCustomNullWriterWithoutFlags-4 3000000 549 ns/op BenchmarkNopLogger-4 20000000 113 ns/op BenchmarkNopLoggerWithoutFlags-4 20000000 112 ns/op BenchmarkLogrus-4 300000 3832 ns/op BenchmarkLogrusWithDiscardWriter-4 500000 3032 ns/op BenchmarkLogrusWithNullFormatter-4 500000 3814 ns/op BenchmarkLogrusWithPanicLevel-4 500000 3872 ns/op BenchmarkLogrusWithDiscardWriterAndPanicLevel-4 500000 3085 ns/op BenchmarkLogrusWithDiscardWriterAndNullFormatterAndPanicLevel-4 500000 3064 ns/op ok log-benchmarks 51.378s go test -bench . 62.17s user 3.90s system 126% cpu 52.065 total 

#1: YMMV, tested on i7-4500U CPU @ 1.80GHz

3 Comments

The answer is somewhat out of date. CPUs are different, Golang is different. You can do better than 15 ns/operation in Golang with a custom log. This is a simple POC of a binary log github.com/larytet/binlog When outputting only integral types it hits 30-40 ns/op range
@Larytet please feel free to edit the answer with new information (or add a new answer). The above benchmark is meant give a fair idea on the performance of each w.r.t each other; not w.r.t the CPU and Golang version. There may be newer libraries more efficient (as it should be), I cannot keep up each of them. Since the source of the above benchmark is given, I expect the reader to make that comparison for themselves (since almost always YMMV).
Avinash R, the stated number 500ns/op is not representative of the current state of art. See for example, github.com/PlatformLab/NanoLog - this is a sub 10ns log.
6
type NullWriter int func (NullWriter) Write([]byte) (int, error) { return 0, nil } // ... log.SetOutput(new(NullWriter)) 

7 Comments

Could it possibly be the case that some users of the Writer will attempt to continue writing if the int return value is 0? (i.e. maybe should we return len(b) when the argument is b []byte?)
Generally you're right, but log package just ignores int return value, so we better not waste time in such a frequent function in production code.
Is there any best practises on use of log in Go? Are most logging calls eventually removed (or commented out) if they're for tracing purposes?
Have to go for now, but have you seen this?
this is not a good way to do it as it does all the work of formatting the output, only to destroy it. if you want logging calls that you can turn off, it's better to stem the flow at source, by avoiding the call to log.Printf.
|
3

This approach allows you to turn logging on and off at runtime:

type LogWriter struct{ enabled bool } func (l *LogWriter) Enable() { l.enabled = true } func (l *LogWriter) Disable() { l.enabled = false } func (l *LogWriter) Write([]byte) (int, error) { if l.enabled { //... } return 0, nil } 

And this approach enables or disables logging for the entire runtime:

type LogWriter struct{} func (l *LogWriter) Write([]byte) (int, error) { if some.Constant { //... } return 0, nil } 

Where some.Constant would be either a constant that you set before compiling (producing a "production" binary) or a variable that is set only once when running the program via command-line flags (something like myprogram --enable-logging=true)

With both approaches you can leave your current code almost entirely untouched.

Comments

2

Since SetOutput() is only defined for the global logger, a custom writer is still handy for other loggers. A short way of writing one is like this:

type LogWriter struct{ io.Writer } func (w *LogWriter) Enable() { w.Writer = os.Stdout } func (w *LogWriter) Disable() { w.Writer = ioutil.Discard } 

1 Comment

Today, anyway, SetOutput is also a method of the Logger type: golang.org/pkg/log/#Logger.SetOutput
0

A note for others coming here looking for this and other logging facilities: have a look at the log4go package as that covers turning off logging, setting log levels, log rotation, redirection to a file etc which might be useful.

See the doc at http://godoc.org/code.google.com/p/log4go

2 Comments

This link appears to be broken.
I think the pkg has been abandoned now unfortunately. Latest copy here : github.com/alecthomas/log4go

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.