errutil
packageAPI reference for the errutil
package.
Imports
(12)TestMultiError_Append
Parameters
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))
}
}
TestMultiError_Error
Parameters
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)
}
}
TestMultiError_Unwrap
Parameters
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")
}
}
TestJoinErrors
Parameters
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")
}
}
Auto
Auto recovers from a panic, prints a rich stack trace, and exits.
func Auto()
{
if r := recover(); r != nil {
drawRichTrace(r)
os.Exit(1)
}
}
EnableAuto
EnableAuto enables automatic panic recovery (stub for future use).
func EnableAuto()
{}
drawRichTrace
Parameters
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)
}
}
snip
Parameters
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())
}
}
Print renders a rich trace (file:line + source context) for a wrapped error,
following the same format as Auto().
Parameters
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)
}
}
unwrapOne
Parameters
Returns
func unwrapOne(err error) error
{
u, ok := err.(interface{ Unwrap() error })
if !ok {
return nil
}
return u.Unwrap()
}
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
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)
})
}
startsWithAny
Parameters
Returns
func startsWithAny(s string, prefixes ...string) bool
{
for _, p := range prefixes {
if len(s) >= len(p) && s[:len(p)] == p {
return true
}
}
return false
}
containsAny
Parameters
Returns
func containsAny(s string, substrs ...string) bool
{
for _, ss := range substrs {
if contains(s, ss) {
return true
}
}
return false
}
contains
Parameters
Returns
func contains(s, substr string) bool
{
return len(s) >= len(substr) && strings.Contains(s, substr)
}
SetSkipPackages
SetSkipPackages appends packages to the skip list for stack trace filtering.
Parameters
func SetSkipPackages(pkgs ...string)
{
mu.Lock()
defer mu.Unlock()
skipPackages = append(skipPackages, pkgs...)
}
isSkip
Parameters
Returns
func isSkip(pkg string) bool
{
mu.RLock()
defer mu.RUnlock()
for _, s := range skipPackages {
if strings.HasPrefix(pkg, s) {
return true
}
}
return false
}
withStack
type withStack struct
Methods
Parameters
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())
}
}
Returns
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()
}
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
}
}
}
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
}
}
}
Parameters
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)
}
}
Fields
| Name | Type | Description |
|---|---|---|
| err | error | |
| stack | []uintptr | |
| showTrace | bool |
withCode
type withCode struct
Methods
Fields
| Name | Type | Description |
|---|---|---|
| err | error | |
| code | string |
withTraceOnly
type withTraceOnly struct
Methods
Returns
func (*withTraceOnly) Error() string
{
var sb strings.Builder
sb.WriteString(w.message)
sb.WriteString("\n")
sb.Write(w.panicStack())
return sb.String()
}
Returns
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 |
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
Returns
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]}
}
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
Returns
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}
}
WithCode
WithCode wraps an error with a string code, e.g. [CODE] message.
Parameters
Returns
func WithCode(code string, err error) error
{
if err == nil {
return nil
}
return &withCode{err: err, code: code}
}
StackTrace
StackTrace unwraps err and returns the captured stack frames, if any.
Parameters
Returns
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
}
MultiError
MultiError aggregates multiple errors into one.
type MultiError struct
Methods
Append adds non-nil errors to the collection.
Parameters
func (*MultiError) Append(errs ...error)
{
for _, err := range errs {
if err != nil {
e.Errors = append(e.Errors, err)
}
}
}
Error returns a concatenated error message.
Returns
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 returns the collected errors for errors.Is/As traversal.
Returns
func (*MultiError) Unwrap() []error
{
return e.Errors
}
HasErrors reports whether any errors were collected.
Returns
func (*MultiError) HasErrors() bool
{
return len(e.Errors) > 0
}
ErrorOrNil returns the MultiError if errors exist, or nil.
Returns
func (*MultiError) ErrorOrNil() error
{
if len(e.Errors) == 0 {
return nil
}
return e
}
Fields
| Name | Type | Description |
|---|---|---|
| Errors | []error |
JoinErrors
JoinErrors merges multiple errors into a single MultiError. Returns nil if none.
Parameters
Returns
func JoinErrors(errs ...error) error
{
e := &MultiError{}
e.Append(errs...)
return e.ErrorOrNil()
}
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
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()
}
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
Returns
func Trace(msg string) error
{
pc := make([]uintptr, 32)
n := runtime.Callers(2, pc)
return &withTraceOnly{message: msg, stack: pc[:n]}
}