How to disable a log.Logger
Asked Answered
S

6

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?

Superfluous answered 13/5, 2012 at 11:0 Comment(0)
P
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

Pap answered 24/12, 2015 at 21:10 Comment(3)
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 rangeSchlosser
@Schlosser 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).Pap
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.Schlosser
D
136

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)
Donnelldonnelly answered 21/5, 2012 at 15:25 Comment(7)
Great find! I looked for such a thing in the standard libraries, but didn't check io/ioutil.Conlen
Will this disable the logger only for the package, or the whole application?Viridian
Since this skips writing to disk but still does all the formatting, a call to log.SetFlags(0) should ease it a bit, I thinkFanti
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.Sappington
As of Go 1.16, this helper has moved to the package io : see io.Discard at pkg.go.dev/io#Writer . The ioutil one still works.Kussell
A proposal to improve perf when using io.Discard: github.com/golang/go/issues/47164Kussell
@Fanti indeed I confirm with this benchmark that a non-zero flag does incur a performance penalty (in Go 1.17): gist.github.com/Deleplace/ad33f8c6a96e9938e83e4f9cbcc608e0Kussell
P
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

Pap answered 24/12, 2015 at 21:10 Comment(3)
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 rangeSchlosser
@Schlosser 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).Pap
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.Schlosser
C
6
type NullWriter int
func (NullWriter) Write([]byte) (int, error) { return 0, nil }

// ...

log.SetOutput(new(NullWriter))
Conlen answered 13/5, 2012 at 11:6 Comment(7)
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?)Married
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.Conlen
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?Superfluous
Have to go for now, but have you seen this?Conlen
@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.Conlen
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.Epifocal
The stdlib already provides such a device: io.DiscardKussell
T
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.

Titania answered 13/5, 2012 at 20:3 Comment(0)
G
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 }
Gasper answered 21/11, 2014 at 22:48 Comment(1)
Today, anyway, SetOutput is also a method of the Logger type: golang.org/pkg/log/#Logger.SetOutputMab
M
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

Montreal answered 29/9, 2012 at 7:50 Comment(2)
This link appears to be broken.Flanna
I think the pkg has been abandoned now unfortunately. Latest copy here : github.com/alecthomas/log4goMontreal

© 2022 - 2024 — McMap. All rights reserved.