Logging
V3 provides a logging interface exposed via cli.Base. By embedding cli.Base in your command struct, you get access to a Logger instance.
Interface
The Logger follows this interface:
type Logger interface {
Info(format string, a ...any)
Success(format string, a ...any)
Warning(format string, a ...any)
Error(format string, a ...any)
}
Usage
func (c *MyCommand) Run() error {
c.Logger.Info("Starting process...")
if err := doSomething(); err != nil {
c.Logger.Error("Something went wrong: %v", err)
return err
}
c.Logger.Success("Done!")
return nil
}
What the default logger does
The default logger is built for a person reading a terminal:
InfoandSuccessare written to stdout,WarningandErrorto stderr, so that piping a command’s output does not hide its errors.
- Each line is prefixed with a symbol for its severity, colored when the destination can show colors.
- Colors are decided per stream. Redirecting stdout to a file leaves the errors on the terminal colored, and redirecting stderr to a file writes no escape codes into it.
NO_COLORandTERM=dumbdisable colors entirely.
- A message logged without arguments is printed verbatim, so a line containing a percent sign survives.
Structured output
When the output is read by a machine rather than a person, swap the logger for
the structured one. It emits one JSON object per line through the go-foundation
logger:
import "github.com/mirkobrombin/go-cli-builder/v3/pkg/log/fdn"
cli.Run(app, cli.WithLogger(fdn.NewStructured(os.Stdout)))
Success has no equivalent severity in a structured logger, so it is written as
an info entry carrying a status: success field. The distinction is kept
rather than lost.
Using a logger the application already has
If your application already builds a go-foundation logger, with its own level
and sinks, hand it over instead of building a second one:
fl := logger.New(logger.WithLevel(logger.DebugLevel), logger.WithSink(mySink))
cli.Run(app, cli.WithLogger(fdn.Wrap(fl)))
Anything satisfying the four method interface is accepted by cli.WithLogger,
so a logger from another library only needs a small adapter.
Testing command output
log.NewWriter returns a logger that writes to the writers you give it, with no
colors, which is what you want in a test:
var out, errOut bytes.Buffer
cmd := &MyCommand{}
cmd.Logger = log.NewWriter(&out, &errOut)
A note on v2
Between v2.1.0 and v2.2.1 the logger was backed directly by the go-foundation
console sink, which serializes entries as JSON. The practical effect was that
every CLI built on those versions printed lines like
{"level":"info","time":"...","msg":"Installing..."} to its users, and
Success was indistinguishable from Info. V3 fixes both.