My project, lifelight, is written in Go and designed to run on low-powered Raspberry Pis. I wanted minimal overhead in the production build, so I implemented a logging system that is compiled out in non-debug builds. This is made easy using Go's build constraints.
The code
This implementation utilizes three files: the main file, and two logger implementation files with build constraints.
The main file includes the interface definition and a global variable:
package main
type Logger interface {
// format string, and objects to format
Log(string, ...interface{})
}
var logger Logger
One of the logger implementations, the dummy, satisfies this interface with methods that do nothing:
//go:build !debug
package main
type DummyLogger struct {}
func (dl DummyLogger) log(format string, v ...interface{}) {}
func init() {
logger = DummyLogger{}
}
The //go:build !debug
build constraint means that it will only be included in
the production build when the debug
tag is absent.
The init
function will be called at runtime, setting the global logger
variable for use in the rest of the program.
The debug logger is implemented in the same way, using the //go:build debug
build constraint:
//go:build debug
package main
import (
"log"
)
type DebugLogger struct {}
func (dl *DebugLogger) log(format string, v ...interface{}) {
log.Printf(format, v...)
}
func init() {
logger = &DebugLogger{}
}
The build
Specifying tags at build time is simple:
go build -tags debug .
This can be integrated into a Makefile
, for example:
main:
go build .
debug:
go build -tags debug .
.PHONY: debug
Now, logging does nothing unless the debug
tag is specified!