errutil API

errutil

package

API reference for the errutil package.

F
function

TestMultiError_Append

Parameters

pkg/errutil/errutil_test.go:9-18
func TestMultiError_Append(t *testing.T)

{
	e := &MultiError{}
	e.Append(errors.New("err1"))
	e.Append(nil)
	e.Append(errors.New("err2"))

	if len(e.Errors) != 2 {
		t.Errorf("got %d errors, want 2", len(e.Errors))
	}
}
F
function

TestMultiError_Error

Parameters

pkg/errutil/errutil_test.go:20-36
func TestMultiError_Error(t *testing.T)

{
	e := &MultiError{}
	if e.Error() != "" {
		t.Errorf("empty multierror should return empty string, got %q", e.Error())
	}

	e.Append(errors.New("one"))
	if e.Error() != "one" {
		t.Errorf("single error: got %q, want %q", e.Error(), "one")
	}

	e.Append(errors.New("two"))
	want := "multiple errors occurred: one; two"
	if e.Error() != want {
		t.Errorf("multiple errors: got %q, want %q", e.Error(), want)
	}
}
F
function

TestMultiError_Unwrap

Parameters

pkg/errutil/errutil_test.go:38-47
func TestMultiError_Unwrap(t *testing.T)

{
	err1 := errors.New("err1")
	err2 := errors.New("err2")
	e := &MultiError{Errors: []error{err1, err2}}

	unwrapped := e.Unwrap()
	if len(unwrapped) != 2 || unwrapped[0] != err1 || unwrapped[1] != err2 {
		t.Error("Unwrap should return all errors")
	}
}
F
function

TestJoinErrors

Parameters

pkg/errutil/errutil_test.go:49-61
func TestJoinErrors(t *testing.T)

{
	err := JoinErrors(errors.New("a"), nil, errors.New("b"))
	if err == nil {
		t.Fatal("JoinErrors should return error")
	}
	if !strings.Contains(err.Error(), "a") || !strings.Contains(err.Error(), "b") {
		t.Errorf("JoinErrors error content: %v", err)
	}

	if JoinErrors(nil, nil) != nil {
		t.Error("JoinErrors with only nil should return nil")
	}
}
F
function

Auto

Auto recovers from a panic, prints a rich stack trace, and exits.

pkg/errutil/auto.go:21-26
func Auto()

{
	if r := recover(); r != nil {
		drawRichTrace(r)
		os.Exit(1)
	}
}
F
function

EnableAuto

EnableAuto enables automatic panic recovery (stub for future use).

pkg/errutil/auto.go:29-29
func EnableAuto()

{}
F
function

drawRichTrace

Parameters

r
any
pkg/errutil/auto.go:31-114
func drawRichTrace(r any)

{
	raw := debug.Stack()
	lines := strings.Split(string(raw), "\n")

	type entry struct {
		funcName string
		file     string
		line     int
	}

	var stack []entry

	for i := 0; i < len(lines); i++ {
		l := lines[i]
		t := strings.TrimSpace(l)

		if t == "" || strings.HasPrefix(t, "goroutine ") {
			continue
		}
		if strings.HasPrefix(t, "runtime.") || strings.Contains(t, "runtime/") {
			continue
		}
		if strings.Contains(t, "pkg/errutil/") || strings.HasPrefix(t, "created by ") {
			continue
		}
		if strings.Contains(t, "panic({") {
			continue
		}

		if !strings.Contains(t, ".go:") && !strings.HasPrefix(t, "created by ") {
			fn := strings.TrimSuffix(t, "(...)")
			fn = strings.TrimSuffix(fn, " {...}")

			e := entry{funcName: fn}

			if i+1 < len(lines) {
				nl := strings.TrimSpace(lines[i+1])
				if strings.Contains(nl, ".go:") {
					parts := strings.SplitN(nl, ".go:", 2)
					if len(parts) == 2 {
						e.file = strings.TrimSpace(parts[0]) + ".go"
						numStr := strings.TrimSpace(parts[1])
						if idx := strings.Index(numStr, " +"); idx != -1 {
							numStr = numStr[:idx]
						}
						n, err := strconv.Atoi(numStr)
						if err == nil {
							e.line = n
						}
					}
					i++
				}
			}

			if !strings.Contains(e.funcName, "drawRichTrace") && !strings.Contains(e.funcName, "Auto.func1") && !strings.HasPrefix(e.funcName, "Auto") && !strings.HasPrefix(e.funcName, "EnableAuto") && !strings.HasPrefix(e.funcName, "github.com/mirkobrombin/go-foundation/pkg/errutil.") {
				stack = append(stack, e)
			}
		}
	}

	yl := "\x1b[33m"
	cy := "\x1b[36m"
	gy := "\x1b[90m"
	rs := "\x1b[0m"

	fmt.Fprintf(os.Stderr, "\n  panic: %v\n\n", r)

	if len(stack) == 0 {
		fmt.Fprintln(os.Stderr, "  (no user frames)")
		return
	}

	for i := len(stack) - 1; i >= 0; i-- {
		e := stack[i]
		num := len(stack) - i

		fmt.Fprintf(os.Stderr, "  %s%d.%s %s%s%s\n", yl, num, rs, cy, e.funcName, rs)
		if e.file != "" {
			fmt.Fprintf(os.Stderr, "     %s%s:%d%s\n", gy, e.file, e.line, rs)
			snip(e.file, e.line, os.Stderr)
		}
		fmt.Fprintln(os.Stderr)
	}
}
F
function

snip

Parameters

path
string
target
int
w
pkg/errutil/auto.go:116-146
func snip(path string, target int, w *os.File)

{
	f, err := os.Open(path)
	if err != nil {
		fmt.Fprintf(w, "      %s(source not available)%s\n", gy, rs)
		return
	}
	defer f.Close()

	start := target - 2
	if start < 1 {
		start = 1
	}
	end := target + 2

	s := bufio.NewScanner(f)
	n := 0
	for s.Scan() {
		n++
		if n < start {
			continue
		}
		if n > end {
			break
		}
		mark := "   "
		if n == target {
			mark = " >>"
		}
		fmt.Fprintf(w, "      %s %5d  %s\n", mark, n, s.Text())
	}
}
F
function

Print

Print renders a rich trace (file:line + source context) for a wrapped error,
following the same format as Auto().

Parameters

err
error
pkg/errutil/auto.go:150-200
func Print(err error)

{
	if err == nil {
		return
	}

	fmt.Fprintf(os.Stderr, "\n  error: %s\n\n", err.Error())

	type frame struct {
		funcName string
		file     string
		line     int
	}
	var chain []frame
	seen := map[*withStack]bool{}

	for e := err; e != nil; e = unwrapOne(e) {
		if ws, ok := e.(*withStack); ok && !seen[ws] {
			seen[ws] = true
			frms := runtime.CallersFrames(ws.stack)
			for {
				f, more := frms.Next()
				fn := f.Func.Name()
				if fn == "" {
					if !more {
						break
					}
					continue
				}
				if isSkip(fn) {
					if !more {
						break
					}
					continue
				}
				chain = append(chain, frame{funcName: fn[strings.LastIndex(fn, "/")+1:], file: f.File, line: f.Line})
				if !more {
					break
				}
			}
		}
	}

	for i := len(chain) - 1; i >= 0; i-- {
		f := chain[i]
		num := len(chain) - i
		fmt.Fprintf(os.Stderr, "  %s%d.%s %s%s%s\n", yl, num, rs, cy, f.funcName, rs)
		fmt.Fprintf(os.Stderr, "     %s%s:%d%s\n", gy, f.file, f.line, rs)
		snip(f.file, f.line, os.Stderr)
		fmt.Fprintln(os.Stderr)
	}
}
F
function

unwrapOne

Parameters

err
error

Returns

error
pkg/errutil/auto.go:202-208
func unwrapOne(err error) error

{
	u, ok := err.(interface{ Unwrap() error })
	if !ok {
		return nil
	}
	return u.Unwrap()
}
F
function

AutoHTTP

AutoHTTP returns an HTTP middleware that recovers from panics and renders a
rich stack trace (in dev mode, as plain text; in any case the full trace is
logged to stderr).

Parameters

Returns

pkg/errutil/autohttp.go:15-50
func AutoHTTP(next http.Handler) http.Handler

{
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if rec := recover(); rec != nil {
				raw := debug.Stack()

				fmt.Fprintf(os.Stderr, "\n%s panic: %v %s\n", r.Method, r.URL.String(), rec)
				os.Stderr.Write(raw)
				fmt.Fprintln(os.Stderr)

				w.Header().Set("Content-Type", "text/plain; charset=utf-8")
				w.WriteHeader(http.StatusInternalServerError)

				fmt.Fprintf(w, "500 - Internal Server Error\n\n")
				fmt.Fprintf(w, "panic: %v\n\n", rec)
				fmt.Fprintf(w, "Request: %s %s\n\n", r.Method, r.URL.String())

				lines := bytes.Split(raw, []byte("\n"))
				for _, l := range lines {
					str := string(bytes.TrimSpace(l))
					if str == "" || len(str) == 0 {
						continue
					}
					if startsWithAny(str, "goroutine ", "runtime.", "panic({", "pkg/errutil/") {
						continue
					}
					if containsAny(str, "runtime/", "runtime(") {
						continue
					}
					fmt.Fprintf(w, "  %s\n", str)
				}
			}
		}()
		next.ServeHTTP(w, r)
	})
}
F
function

startsWithAny

Parameters

s
string
prefixes
...string

Returns

bool
pkg/errutil/autohttp.go:52-59
func startsWithAny(s string, prefixes ...string) bool

{
	for _, p := range prefixes {
		if len(s) >= len(p) && s[:len(p)] == p {
			return true
		}
	}
	return false
}
F
function

containsAny

Parameters

s
string
substrs
...string

Returns

bool
pkg/errutil/autohttp.go:61-68
func containsAny(s string, substrs ...string) bool

{
	for _, ss := range substrs {
		if contains(s, ss) {
			return true
		}
	}
	return false
}
F
function

contains

Parameters

s
string
substr
string

Returns

bool
pkg/errutil/autohttp.go:70-72
func contains(s, substr string) bool

{
	return len(s) >= len(substr) && strings.Contains(s, substr)
}
F
function

SetSkipPackages

SetSkipPackages appends packages to the skip list for stack trace filtering.

Parameters

pkgs
...string
pkg/errutil/errors.go:20-24
func SetSkipPackages(pkgs ...string)

{
	mu.Lock()
	defer mu.Unlock()
	skipPackages = append(skipPackages, pkgs...)
}
F
function

isSkip

Parameters

pkg
string

Returns

bool
pkg/errutil/errors.go:26-35
func isSkip(pkg string) bool

{
	mu.RLock()
	defer mu.RUnlock()
	for _, s := range skipPackages {
		if strings.HasPrefix(pkg, s) {
			return true
		}
	}
	return false
}
S
struct

withStack

pkg/errutil/errors.go:37-41
type withStack struct

Methods

Format
Method

Parameters

verb rune
func (*withStack) Format(s fmt.State, verb rune)
{
	switch verb {
	case 'v':
		if s.Flag('+') {
			fmt.Fprint(s, w.err.Error())
			w.writeVerbose(s)
			return
		}
		fallthrough
	case 's':
		fmt.Fprint(s, w.err.Error())
	case 'q':
		fmt.Fprintf(s, "%q", w.err.Error())
	}
}
Error
Method

Returns

string
func (*withStack) Error() string
{
	if w.showTrace {
		var sb strings.Builder
		sb.WriteString(w.err.Error())
		w.writeChain(&sb, map[*withStack]bool{})
		return sb.String()
	}
	return w.err.Error()
}
Clean
Method

Returns

string
func (*withStack) Clean() string
{
	return w.err.Error()
}
writeVerbose
Method

Parameters

func (*withStack) writeVerbose(wr fmt.State)
{
	frames := runtime.CallersFrames(w.stack)
	for {
		frame, more := frames.Next()
		if frame.Func != nil {
			fn := frame.Func.Name()
			if !isSkip(fn) {
				fmt.Fprintf(wr, "\n    at %s:%d (%s)", frame.File, frame.Line, fn)
			}
		}
		if !more {
			break
		}
	}
}
writeStack
Method

Parameters

func (*withStack) writeStack(sb *strings.Builder)
{
	frames := runtime.CallersFrames(w.stack)
	for {
		frame, more := frames.Next()
		if frame.Func != nil {
			fn := frame.Func.Name()
			if !isSkip(fn) {
				sb.WriteString(fmt.Sprintf("\n    at %s:%d (%s)", frame.File, frame.Line, fn))
			}
		}
		if !more {
			break
		}
	}
}
writeChain
Method

Parameters

seen map[*withStack]bool
func (*withStack) writeChain(sb *strings.Builder, seen map[*withStack]bool)
{
	if seen[w] {
		return
	}
	seen[w] = true
	w.writeStack(sb)
	if next, ok := w.err.(*withStack); ok {
		next.writeChain(sb, seen)
	}
}
Unwrap
Method

Returns

error
func (*withStack) Unwrap() error
{
	return w.err
}
Stack
Method

Returns

[]uintptr
func (*withStack) Stack() []uintptr
{
	return w.stack
}

Fields

Name Type Description
err error
stack []uintptr
showTrace bool
S
struct

withCode

pkg/errutil/errors.go:124-127
type withCode struct

Methods

Error
Method

Returns

string
func (*withCode) Error() string
{
	return fmt.Sprintf("[%s] %s", w.code, w.err.Error())
}
Unwrap
Method

Returns

error
func (*withCode) Unwrap() error
{
	return w.err
}
Code
Method

Returns

string
func (*withCode) Code() string
{
	return w.code
}

Fields

Name Type Description
err error
code string
S
struct

withTraceOnly

pkg/errutil/errors.go:141-144
type withTraceOnly struct

Methods

Error
Method

Returns

string
func (*withTraceOnly) Error() string
{
	var sb strings.Builder
	sb.WriteString(w.message)
	sb.WriteString("\n")
	sb.Write(w.panicStack())
	return sb.String()
}
panicStack
Method

Returns

[]byte
func (*withTraceOnly) panicStack() []byte
{
	frames := runtime.CallersFrames(w.stack)
	var sb strings.Builder
	for {
		frame, more := frames.Next()
		if frame.Func != nil {
			fn := frame.Func.Name()
			if !isSkip(fn) {
				sb.WriteString(fmt.Sprintf("\n    at %s:%d (%s)", frame.File, frame.Line, fn))
			}
		}
		if !more {
			break
		}
	}
	return []byte(sb.String())
}

Fields

Name Type Description
stack []uintptr
message string
F
function

Wrap

Wrap captures a stack trace at the call site and attaches it to err.
Error() returns only the wrapped message. Use %+v or WError for full trace.

Parameters

err
error

Returns

error
pkg/errutil/errors.go:174-184
func Wrap(err error) error

{
	if err == nil {
		return nil
	}
	if _, ok := err.(*withStack); ok {
		return err
	}
	pc := make([]uintptr, 32)
	n := runtime.Callers(2, pc)
	return &withStack{err: err, stack: pc[:n]}
}
F
function

WError

WError is an opinionated Wrap that includes the stack trace directly in
Error(). Use it when you always want the trace visible on every print.

Parameters

err
error

Returns

error
pkg/errutil/errors.go:188-195
func WError(err error) error

{
	if err == nil {
		return nil
	}
	pc := make([]uintptr, 32)
	n := runtime.Callers(2, pc)
	return &withStack{err: err, stack: pc[:n], showTrace: true}
}
F
function

WithCode

WithCode wraps an error with a string code, e.g. [CODE] message.

Parameters

code
string
err
error

Returns

error
pkg/errutil/errors.go:198-203
func WithCode(code string, err error) error

{
	if err == nil {
		return nil
	}
	return &withCode{err: err, code: code}
}
F
function

StackTrace

StackTrace unwraps err and returns the captured stack frames, if any.

Parameters

err
error

Returns

[]uintptr
pkg/errutil/errors.go:206-225
func StackTrace(err error) []uintptr

{
	type stackTracer interface {
		Stack() []uintptr
	}
	var st stackTracer
	for {
		if s, ok := err.(stackTracer); ok {
			st = s
		}
		if unwrapper, ok := err.(interface{ Unwrap() error }); ok {
			err = unwrapper.Unwrap()
		} else {
			break
		}
	}
	if st != nil {
		return st.Stack()
	}
	return nil
}
S
struct

MultiError

MultiError aggregates multiple errors into one.

pkg/errutil/errors.go:228-230
type MultiError struct

Methods

Append
Method

Append adds non-nil errors to the collection.

Parameters

errs ...error
func (*MultiError) Append(errs ...error)
{
	for _, err := range errs {
		if err != nil {
			e.Errors = append(e.Errors, err)
		}
	}
}
Error
Method

Error returns a concatenated error message.

Returns

string
func (*MultiError) Error() string
{
	if len(e.Errors) == 0 {
		return ""
	}
	if len(e.Errors) == 1 {
		return e.Errors[0].Error()
	}
	var sb strings.Builder
	sb.WriteString("multiple errors occurred: ")
	for i, err := range e.Errors {
		if i > 0 {
			sb.WriteString("; ")
		}
		sb.WriteString(err.Error())
	}
	return sb.String()
}
Unwrap
Method

Unwrap returns the collected errors for errors.Is/As traversal.

Returns

[]error
func (*MultiError) Unwrap() []error
{
	return e.Errors
}
HasErrors
Method

HasErrors reports whether any errors were collected.

Returns

bool
func (*MultiError) HasErrors() bool
{
	return len(e.Errors) > 0
}
ErrorOrNil
Method

ErrorOrNil returns the MultiError if errors exist, or nil.

Returns

error
func (*MultiError) ErrorOrNil() error
{
	if len(e.Errors) == 0 {
		return nil
	}
	return e
}

Fields

Name Type Description
Errors []error
F
function

JoinErrors

JoinErrors merges multiple errors into a single MultiError. Returns nil if none.

Parameters

errs
...error

Returns

error
pkg/errutil/errors.go:279-283
func JoinErrors(errs ...error) error

{
	e := &MultiError{}
	e.Append(errs...)
	return e.ErrorOrNil()
}
F
function

Recover

Recover catches a panic in fn, prints a clean stack trace to stderr with
file:line and function names, then exits with status 1. The panic always
terminates the process.

Parameters

fn
func()
pkg/errutil/errors.go:288-319
func Recover(fn func())

{
	defer func() {
		if r := recover(); r != nil {
			stack := debug.Stack()
			lines := strings.Split(string(stack), "\n")

			var out strings.Builder
			for _, l := range lines {
				trimmed := strings.TrimSpace(l)
				if trimmed == "" {
					continue
				}
				if strings.HasPrefix(trimmed, "goroutine ") && strings.Contains(trimmed, "[running]") {
					continue
				}
				if strings.Contains(l, "runtime.") || strings.Contains(l, "runtime/") {
					continue
				}
				if strings.Contains(l, "pkg/errutil/") {
					continue
				}
				out.WriteString(l)
				out.WriteString("\n")
			}

			prefix := fmt.Sprintf("panic: %v\n", r)
			fmt.Fprint(os.Stderr, prefix+out.String())
			os.Exit(1)
		}
	}()
	fn()
}
F
function

Trace

Trace captures a stack trace at this point without modifying or wrapping
an existing error. Useful in defers to annotate “where did this come from”.

Parameters

msg
string

Returns

error
pkg/errutil/errors.go:323-327
func Trace(msg string) error

{
	pc := make([]uintptr, 32)
	n := runtime.Callers(2, pc)
	return &withTraceOnly{message: msg, stack: pc[:n]}
}