errorhandling

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 7 Imported by: 0

README

errorhandling

Structured, user-friendly error reporting for Go CLIs — actionable hints, exit codes carried on the error, debug-gated stack traces, and a pluggable support-channel message

Go Reference Pipeline Coverage phpboyscout Go toolkit

Part of the phpboyscout Go toolkit — small, framework-free Go modules extracted from go-tool-base. Docs: errorhandling.go.phpboyscout.uk


gitlab.com/phpboyscout/go/errorhandling turns an error into output a user can act on. It wraps cockroachdb/errors with a small reporting pipeline: attach a hint telling the user what to do, attach an exit code to the error itself, route by level, and surface stack traces only when debug logging is on.

Design

  • Framework-free. The only dependency is cockroachdb/errors. No CLI framework, no config system, no logging library — the logging seam is a plain *slog.Logger and a depfootprint_test.go guard enforces the boundary.
  • The error carries the exit code. WithExitCode(err, 3) travels with the error, so the code that knows why something failed decides how the process exits, and main stays a one-liner.
  • Hints are for humans. A wrapped error says what broke; a hint says what to do about it. Both are rendered, with the hint kept out of the machine-facing message.
  • Quiet on purpose. Stack traces and details appear only when the logger has debug enabled; LevelFatalQuiet exits with the attached code while logging at debug, for expected terminations such as a SIGINT where an error line would be noise.
  • Bring your own usage printer. Printing usage for a parent command goes through the SetUsage(func() error) seam — with Cobra that is SetUsage(cmd.Usage) — so this module never imports a CLI framework.

Install

go get gitlab.com/phpboyscout/go/errorhandling

Quick start

package main

import (
	"log/slog"
	"os"

	"github.com/cockroachdb/errors"

	"gitlab.com/phpboyscout/go/errorhandling"
)

func main() {
	handler := errorhandling.New(slog.Default(), nil)

	if err := run(); err != nil {
		handler.Fatal(err) // logs message + hint, exits with the attached code
	}
}

func run() error {
	err := errors.New("config file not found")

	return errorhandling.WithExitCode(
		errorhandling.WithUserHint(err, "Run 'mytool init' to create one"),
		3,
	)
}

What's inside

  • ReportingErrorHandler (Check / Fatal / Error / Warn), New, and the WithExitFunc / WithWriter options.
  • HintsWithUserHint, WithUserHintf, WrapWithHint.
  • Exit codesWithExitCode, ExitCode.
  • LevelsLevelFatal, LevelFatalQuiet, LevelError, LevelWarn.
  • SentinelsErrNotImplemented (with NewErrNotImplemented for an issue link) and ErrRunSubCommand, which triggers the usage printer.
  • Help channels — the HelpConfig interface; you supply the implementation.
  • mocks — published testify mocks of ErrorHandler and HelpConfig.

Documentation

Full guides and the reporting model: errorhandling.go.phpboyscout.uk. API reference: pkg.go.dev.

License

See LICENSE.

Documentation

Overview

Package errorhandling provides structured, user-friendly error reporting for command-line tools.

The ErrorHandler interface offers Check (route an error through the reporting pipeline), Fatal (report and exit), Error (non-terminating report), and Warn. Errors are rendered with any user-facing hints attached via cockroachdb/errors (WithHint/WithHintf, or the WithUserHint helpers here), plus an optional support-channel message supplied through HelpConfig.

An exit code can be attached to an error value with WithExitCode and is honoured when the error is reported at Fatal level, so the code that knows *why* something failed decides how the process exits.

Stack traces and error details are extracted from cockroachdb/errors and emitted only when the logger has debug enabled, giving rich diagnostics on demand without cluttering normal output.

The package carries no CLI-framework dependency. Where a parent command needs to print usage (an error wrapping ErrRunSubCommand), the caller supplies the printer through SetUsage — with Cobra, that is cmd.Usage.

Index

Examples

Constants

View Source
const (
	LevelFatal = "fatal"
	// LevelFatalQuiet exits the process exactly like LevelFatal — honouring any
	// exit code attached via WithExitCode — but logs the message at debug rather
	// than error. It exists for expected, user-initiated terminations (e.g. a
	// SIGINT/SIGTERM interrupt) where the non-zero exit code is the signal and an
	// error-level log line would be noise. The message is still emitted at debug
	// so `--debug` continues to surface it.
	LevelFatalQuiet = "fatal-quiet"
	LevelError      = "error"
	LevelWarn       = "warn"
	KeyStacktrace   = "stacktrace"
	KeyHelp         = "help"
	KeyHints        = "hints"
	KeyDetails      = "details"
)

Variables

View Source
var (
	ErrNotImplemented = errors.New("command not yet implemented")
	ErrRunSubCommand  = errors.New("subcommand required")
)

Functions

func ExitCode

func ExitCode(err error) int

ExitCode returns the exit code attached to err via WithExitCode. It returns 0 for a nil error and 1 for any error without an attached code. When codes are attached at multiple levels, the outermost (most recently applied) attachment wins, matching errors.As traversal order.

func NewAssertionFailure

func NewAssertionFailure(format string, args ...any) error

NewAssertionFailure creates an error denoting a programming bug.

func NewErrNotImplemented

func NewErrNotImplemented(issueURL string) error

NewErrNotImplemented creates an unimplemented error with an optional issue tracker link.

func WithExitCode

func WithExitCode(err error, code int) error

WithExitCode attaches a process exit code to err. The ErrorHandler's fatal path uses the attached code instead of the default 1, letting callers thread non-standard exit codes (for example the Unix 128+signum convention for signal-terminated runs) through the single exit path without a parallel os.Exit call site. Returns nil when err is nil.

func WithUserHint

func WithUserHint(err error, hint string) error

WithUserHint attaches a user-facing recovery suggestion to an error.

Example
package main

import (
	"fmt"

	"github.com/cockroachdb/errors"

	"gitlab.com/phpboyscout/go/errorhandling"
)

func main() {
	err := errors.New("connection refused")
	hinted := errorhandling.WithUserHint(err, "Check that the server is running and the port is correct")

	fmt.Println(errors.FlattenHints(hinted))
}
Output:
Check that the server is running and the port is correct

func WithUserHintf

func WithUserHintf(err error, format string, args ...any) error

WithUserHintf attaches a formatted user-facing recovery suggestion.

func WrapWithHint

func WrapWithHint(err error, msg string, hint string) error

WrapWithHint wraps an error with a message and attaches a user-facing hint.

Example
package main

import (
	"fmt"

	"github.com/cockroachdb/errors"

	"gitlab.com/phpboyscout/go/errorhandling"
)

func main() {
	err := errors.New("file not found")
	wrapped := errorhandling.WrapWithHint(err, "loading config", "Run 'mytool init' to create the config file")

	fmt.Println(wrapped.Error())
	fmt.Println(errors.FlattenHints(wrapped))
}
Output:
loading config: file not found
Run 'mytool init' to create the config file

Types

type ErrorHandler

type ErrorHandler interface {
	Check(err error, prefix string, level string)
	Fatal(err error, prefixes ...string)
	Error(err error, prefixes ...string)
	Warn(err error, prefixes ...string)

	// SetUsage registers the function used to print usage when an error
	// wrapping [ErrRunSubCommand] is reported. CLI frameworks supply their
	// own printer — with Cobra, that is `SetUsage(cmd.Usage)`, typically set
	// in each command's pre-run so the usage shown belongs to the command
	// that actually failed.
	SetUsage(usage func() error)
}

ErrorHandler defines the interface for structured error reporting. It formats errors with hints, stack traces, and help channel information, then routes them to the appropriate output (logger, writer, or exit).

func New

func New(l *slog.Logger, help HelpConfig, opts ...Option) ErrorHandler

New creates an ErrorHandler with the given logger and help config. Options can override the exit function, output writer, or other defaults.

Example
package main

import (
	"gitlab.com/phpboyscout/go/errorhandling"
)

func main() {
	handler := errorhandling.New(nil, nil)
	_ = handler // Use handler.Check, handler.Error, handler.Fatal, handler.Warn
}

type ExitFunc

type ExitFunc func(code int)

ExitFunc is the function called to terminate the process. Defaults to os.Exit. Override via WithExitFunc for testing.

type HelpConfig

type HelpConfig interface {
	SupportMessage() string
}

HelpConfig supplies contextual support information to attach to reported errors — typically "contact <team> via <channel>" for whatever support channel your organisation uses.

It is deliberately the only help-related type in this module: the extension point, with no opinion about where a team's support channel lives. Slack, Teams, an on-call rota, a wiki URL, or a message assembled from configuration are all just implementations you supply.

Returning an empty string suppresses the help output entirely, so an implementation can stay silent when it has nothing useful to say (for example when its channel is not configured yet):

type slackHelp struct{ team, channel string }

func (s slackHelp) SupportMessage() string {
	if s.team == "" || s.channel == "" {
		return "" // not configured — say nothing
	}

	return fmt.Sprintf("For assistance, contact %s via Slack channel %s", s.team, s.channel)
}

Pass an implementation to New; passing nil is valid and disables help output.

Example
package main

import (
	"fmt"

	"gitlab.com/phpboyscout/go/errorhandling"
)

// slackHelp is an example HelpConfig implementation. The module ships only the
// interface, so an application defines whatever support channel it actually
// uses — Slack here, but equally Teams, an on-call rota, or a wiki URL.
type slackHelp struct {
	Team    string
	Channel string
}

func (s slackHelp) SupportMessage() string {
	if s.Team == "" || s.Channel == "" {
		return ""
	}

	return fmt.Sprintf("For assistance, contact %s via Slack channel %s", s.Team, s.Channel)
}

func main() {
	var help errorhandling.HelpConfig = slackHelp{
		Team:    "mycompany",
		Channel: "#dev-support",
	}

	fmt.Println(help.SupportMessage())
}
Output:
For assistance, contact mycompany via Slack channel #dev-support

type Option

type Option func(*StandardErrorHandler)

Option is a functional option for configuring a StandardErrorHandler.

func WithExitFunc

func WithExitFunc(exit ExitFunc) Option

WithExitFunc allows injection of a custom exit handler (e.g. for testing).

func WithWriter

func WithWriter(w io.Writer) Option

WithWriter allows injection of a custom output writer.

type StandardErrorHandler

type StandardErrorHandler struct {
	Logger *slog.Logger
	Help   HelpConfig
	Exit   ExitFunc
	Writer io.Writer
	Usage  func() error
}

StandardErrorHandler is the default ErrorHandler implementation. It extracts hints, details, and stack traces from cockroachdb/errors and formats them for terminal or structured output.

func (*StandardErrorHandler) Check

func (h *StandardErrorHandler) Check(err error, prefix string, level string)

func (*StandardErrorHandler) Error

func (h *StandardErrorHandler) Error(err error, prefixes ...string)

func (*StandardErrorHandler) Fatal

func (h *StandardErrorHandler) Fatal(err error, prefixes ...string)

func (*StandardErrorHandler) SetUsage

func (h *StandardErrorHandler) SetUsage(usage func() error)

func (*StandardErrorHandler) Warn

func (h *StandardErrorHandler) Warn(err error, prefixes ...string)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL