How to do trace logging in Go with very low cost for disabled log statements -
it useful leave low-level debug/trace logging statements in critical paths can enabled runtime configuration. idea never turn such logging on in production (it cripple performance) can turn on in production environment (e.g. production system taken offline debugging or test system set production system.)
this type of logging has special requirement: cost of hitting disabled log statement on critical path must very low: ideally single boolean test.
in c/c++ log macro not evaluate of arguments until has checked flag. if enabled call helper function format & deliver log message.
so how in go?
using io.discard log.logger non starter: formats log message every time before throwing away if disabled.
my first thought
type enabledlogger struct { enabled bool; delegate *log.logger;... } // implement log.logger interface methods as: func (e enabledlogger) print(...) { if e.enabled { e.delegate.output(...) } }
this close. if say:
myenabledlogger.printf("foo %v: %v", x, y)
it won't format or log if disabled will evaluate arguments x , y. that's ok basic types or pointers, not ok arbitrary function calls - e.g. stringify values don't have string() method.
i see 2 ways around that:
wrapper types defer call:
type stringify { x *thing } func (s stringify) string() { return somestringfn(s.x) } enabledlogger.printf("foo %v", stringify{&athing})
wrap whole thing in manual enabled check:
if enabledlog.enabled { enabledlog.printf("foo %v", somestringfn(x)) }
both verbose , error prone, easy forget step , quietly introduce nasty performance regression.
i'm starting go. please tell me can solve problem :)
all arguments in go guaranteed evaluated, , there's no defined preprocessor macros in language, there's couple things can do.
to avoid expensive function calls in logging arguments, make use of fmt.stringer
, fmt.gostringer
interfaces delay formatting until function executed. way can still pass plain types printf
functions. can extend pattern custom logger checks various interfaces well. you're using in stringify
example, , can enforce code review , unit tests.
type logformatter interface { logformat() string } // inside logger if i, ok := i.(logformatter); ok { fmt.println(i.logformat()) }
you can swap out entire logger out @ runtime via logger interface, or replace entirely @ build time using build constraints, still requires ensuring no expensive calls inserted logging arguments.
another pattern used packages glog make logger bool. doesn't eliminate verbosity completely, makes little more terse.
type log bool func (l log) println(args ...interface{}) { fmt.println(args...) } var debug log = false if debug { debug.println("debugging") }
the closest can macro pre-processing in go use code generation. isn't going work runtime configurable tracing, @ least provide separate debug build can dropped place when needed. can simple gofmt -r
, building file using text/template
, or full generation parsing code , building ast.
Comments
Post a Comment