Skip to content

Report levels

Which slog level a report is logged at is decided by three things, in this order: the method you called, the Quietly option, and any Outcome the error carries. The last one wins.

Whether the process ends is a separate decision, and not this module's: Fatal returns an exit code and leaves acting on it to main.

What each method logs at

Method Logs at Returns a code Honours Quietly
Fatal ERROR yes yes
Error ERROR no no
Warn WARN no no

A nil error reports nothing at any of them. Fatal(ctx, nil) returns 0 without logging, so a fatal path guarded by a nil check needs no second guard.

Quietly only applies to Fatal

Quietly demotes the line to DEBUG without changing the exit code. It is read on the fatal path only — Error(ctx, err, Quietly()) still logs at ERROR, silently ignoring the option.

That is deliberate rather than an oversight: the option exists for a termination whose whole meaning is the exit code, and a non-terminal report has no code to carry the meaning instead.

An outcome overrides the level

If the error carries an Outcome, its Level replaces whatever the method and Quietly decided, and its Code replaces the attached exit code:

h.Error(ctx, errorhandling.ErrRunSubCommand)   // logs at WARN, not ERROR
h.Fatal(ctx, errorhandling.ErrRunSubCommand, errorhandling.Quietly())
                                               // logs at WARN, not DEBUG; returns 2

The error knows what kind of ending it is; the reporting site usually does not. A caller that needs a different level has to replace the outcome, not argue with it — see Limitations.

What the sentinels do

The three sentinels this module ships all carry an outcome, so all three report at WARN whichever method reports them, and all three make Fatal return 2:

Error Message Prints usage Fatal returns
ErrRunSubCommand subcommand required yes 2
ErrUnknownSubCommand unknown subcommand yes 2
ErrNotImplemented command not yet implemented no 2

Usage is printed through the SetUsage seam, before the report. With no printer registered the error is still reported and the code is still 2; there is simply no usage output.

Everything the error carries is still reported. Wrapping one of these does not lose the wrap's message, and hints, details, attributes, the prefix, the help message and the stack at debug all arrive as usual:

errors.Wrap(errorhandling.ErrRunSubCommand, "config")
// WARN config: subcommand required

What an assertion failure does

An error from NewAssertionFailure is reported like any other error: one record, at the level the method asked for, identified by the kind errorhandling.assertion_failure inside the err group.

h.Warn(ctx, errorhandling.NewAssertionFailure("bad: %s", "x<0"))
// WARN bad: x<0: internal invariant violated   err.kind=errorhandling.assertion_failure

It carries no outcome, so it does not override the level and Fatal returns 1 unless a code is attached. The kind is what a query filters on — which is why the second, fixed ERROR Internal error (assertion failure) line earlier versions emitted was dropped: it said in prose what the record already says in a field.