44

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?

Flimzy
  • 60,850
  • 13
  • 104
  • 147
Matt Joiner
  • 100,604
  • 94
  • 332
  • 495

6 Answers6

117

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)
}
Jeff Wendling
  • 1,171
  • 2
  • 6
  • 2
  • Great find! I looked for such a thing in the standard libraries, but didn't check `io/ioutil`. – Mostafa May 21 '12 at 15:27
  • 1
    Will this disable the logger only for the package, or the whole application? – Kiril Jun 11 '14 at 13:36
  • 5
    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 – Joril Dec 17 '14 at 15:03
  • 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. – Aaron Dec 19 '16 at 19:25
33

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

ruffrey
  • 4,879
  • 2
  • 20
  • 19
Avinash R
  • 2,951
  • 1
  • 21
  • 46
  • 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 https://github.com/larytet/binlog When outputting only integral types it hits 30-40 ns/op range – Larytet Nov 23 '18 at 09:14
  • @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 Dec 10 '18 at 07:15
  • Avinash R, the stated number 500ns/op is not representative of the current state of art. See for example, https://github.com/PlatformLab/NanoLog - this is a sub 10ns log. – Larytet Dec 12 '18 at 13:52
5
type NullWriter int
func (NullWriter) Write([]byte) (int, error) { return 0, nil }

// ...

log.SetOutput(new(NullWriter))
Mostafa
  • 22,896
  • 9
  • 51
  • 51
  • 1
    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`?) – Asherah May 13 '12 at 11:14
  • 2
    Generally you're right, but `log` package [just ignores](http://code.google.com/p/go/source/browse/src/pkg/log/log.go#153) `int` return value, so we better not waste time in such a frequent function in production code. – Mostafa May 13 '12 at 11:19
  • 2
    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? – Matt Joiner May 13 '12 at 11:23
  • 1
    Have to go for now, but have you seen [this](http://gosnip.posterous.com/more-informative-printf-style-debugging-with)? – Mostafa May 13 '12 at 11:29
  • @MattJoiner I don't keep logging calls that are for testing. Remove them as soon as I don't need them. But you can have multiple loggers in Go. I usually have two: `infoLogger` and `errLogger`, and set them to appropriate outputs at development and production (using command-line flags). You can have another, `debugLogger` or something like that, and disable it in production. – Mostafa May 20 '12 at 07:31
  • 5
    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. – rog May 21 '12 at 15:51
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.

thwd
  • 21,419
  • 4
  • 68
  • 99
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 }
TJM
  • 2,509
  • 1
  • 9
  • 5
  • Today, anyway, SetOutput is also a method of the Logger type: https://golang.org/pkg/log/#Logger.SetOutput – cristoper Jan 03 '21 at 05:42
1

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

Kenny Grant
  • 8,222
  • 2
  • 24
  • 43