Centralized Error Handling¶
GTB implements a centralized error handling strategy to ensure consistent terminal output, standardized logging, and helpful support information across all CLI tools.
History & Rationale¶
The design of the ErrorHandler was driven by two primary requirements:
- Observability: We needed a way to display detailed debugging information, specifically full stack traces, whenever an error occurs in a development or troubleshooting context. This led to the adoption of
github.com/cockroachdb/errorsfor error creation/wrapping โ providing stack traces, user-facing hints, and structured details โ alongsidecharmbracelet/logfor rich, structured terminal output. - Consistent Output: We route all errors โ runtime errors, flag parse errors, and pre-run failures โ through a single
Execute()wrapper that callsErrorHandler.Check. This suppresses Cobra's own error printing and ensures all output is produced by GTB's structured logger.
The ErrorHandler Interface¶
At the core of this pattern is the ErrorHandler interface (found in pkg/errorhandling):
type ErrorHandler interface {
Check(err error, prefix string, level string, cmd ...*cobra.Command)
Fatal(err error, prefixes ...string)
Error(err error, prefixes ...string)
Warn(err error, prefixes ...string)
SetUsage(usage func() error)
}
Key Methods¶
Check: The primary engine for error processing. It handles special error types, extracts stack traces, hints, and details (viacockroachdb/errors), and logs the result. Called by theExecute()wrapper for all errors returned fromRunE.Fatal: Logs an error at the fatal level and terminates the application with an exit code of 1.Error/Warn: Logs errors at their respective levels without terminating the process.
The Execute Wrapper¶
GTB commands use Cobra's RunE and return errors idiomatically. The Execute() function in pkg/cmd/root acts as the central dispatcher โ it silences Cobra's own error output, adds a --help hint to flag parse errors, and routes any error returned from RunE through ErrorHandler.Check at LevelFatal.
// In your generated main.go:
func main() {
rootCmd, p := root.NewCmdRoot(version.Get())
pkgRoot.Execute(rootCmd, p)
}
This means commands simply return errors:
Special Error Types¶
The framework defines several "sentinel" errors that trigger specific cross-cutting behaviors:
ErrNotImplemented: Automatically logs a warning indicating that the command is still under development.ErrRunSubCommand: Triggered when a parent command is run without a required subcommand. The framework automatically prints the command's usage instructions.
Help Integration¶
The errorhandling package supports pluggable help configuration through the HelpConfig interface. When an error occurs, ErrorHandler calls HelpConfig.SupportMessage() and appends the result to the error output, directing users to the appropriate support channel.
Two built-in implementations are provided:
// Slack support channel
props.Tool.Help = errorhandling.SlackHelp{
Team: "Engineering",
Channel: "#mytool-support",
}
// Microsoft Teams support channel
props.Tool.Help = errorhandling.TeamsHelp{
Team: "Engineering",
Channel: "Support",
}
When Tool.Help is nil, no support message is shown.
Usage in Commands¶
Commands return errors via RunE; the Execute() wrapper routes them through ErrorHandler:
func NewMyCommand(props *props.Props) *cobra.Command {
return &cobra.Command{
Use: "mycommand",
Short: "Does work",
RunE: func(cmd *cobra.Command, args []string) error {
return runMyCommand(cmd.Context(), props)
},
}
}
func runMyCommand(ctx context.Context, props *props.Props) error {
if err := props.ErrorHandler.SetUsage(cmd.Usage); err != nil { ... }
// return errors directly โ Execute() handles fatal routing
return doWork(ctx)
}
For non-fatal errors (log and continue), call ErrorHandler.Error or ErrorHandler.Warn inside the function body and return nil.
Best Practices¶
- Wrap Errors: Use
github.com/cockroachdb/errorsto wrap errors with stack traces and user-facing hints. TheErrorHandlerextracts and logs traces, hints, and details automatically when debug mode is enabled. - Return from RunE: Return errors from
RunEinstead of callingos.Exitdirectly. TheExecute()wrapper ensures they reachErrorHandler. - Avoid os.Exit: Do not call
os.Exitdirectly in your business logic. OnlyErrorHandler.Fatalshould callos.Exit, and only when there is no other option (e.g., early termination before Cobra has set up). - Consistent Prefixes: Use descriptive prefix/wrap messages (e.g.,
errors.Wrap(err, "git clone")) to help users identify where the failure occurred. - Attach Hints: Use
errors.WithHintto attach actionable recovery suggestions that will be surfaced byErrorHandlerregardless of log level.