Debug logging in Go

Last updated: May 15, 2022
1 min read

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!