How to Implement Verbose Mode in a Go CLI App Using the Standard Library
A step-by-step guide to adding verbose mode to your Go CLI app using the standard library.
Table of contents
A lot of CLI tools provide a verbose mode to get logs with additional information, which is helpful for scenarios when something breaks or when a user wants to understand in more detail what is happening in the background.
It is an essential part of the standard experience expected of a CLI app.
One such example is the cURL command. Following is a gif of the difference in output when using the curl
command with the verbose flag and without.
Today we'll try to see how to implement such a feature in a CLI app using Go.
Defining the feature
Let's define the flag we are going to implement
It should work for all commands and subcommands in the application
When provided, it should show additional logs of all the commands, in addition, to the output that is shown by default.
It should be easy to maintain, every command or every log statement must not need to handle or know about this flag
Luckily for us, Go has amazing standard packages we can compose to create this effect with ease.
Tres Amigos
We will use three packages to accomplish this.
flag: We will use it to easily parse the arguments provided by the user
log: We will use the standard package to log stuff
io: We will utilise a very special Writer provided by this package.
flag
The flag parsing works in three steps which need to be followed in the exact same order
Define the expected flags
Invoke the
Parse()
methodStart accessing the values
func main() {
// Define the flag that is expected
// We can pass the name, a default value & a description
// It will return a pointer which is guranteed to have value
// Only after flag.Parse is called
verboseP := flag.Bool("v", false, "Enable verbosity")
// From the docs: Must be called after all flags are defined
// and before flags are accessed by the program.
flag.Parse()
// Access the value after
isVerbose := *verboseP
}
log
Log package contains a function called SetOutput
which can be set to anything that implements the Writer
interface.
We can use this to redirect all the logs to a specific writer.
log.setOutput(SomeWriter)
io
IO will provide us with the last missing piece, Writer
which does nothing.
Basically discards any logs sent to it. It's called io.Discard
Putting it all together
package main
import (
"flag"
"fmt"
"io"
"log"
)
func main() {
// Define the flags
verboseP := flag.Bool("v", false, "Enable verbosity")
// Invoke the parser
flag.Parse()
// Access it
isVerbose := *verboseP
// If verbosity is not required discard all upcoming logs
if !isVerbose {
log.SetOutput(io.Discard)
}
fmt.Println("Hello world!")
log.Println("I'm tired of printing this")
}
Try it on repl.it