|
| 1 | +// Package erf provides error management with stack trace. |
| 2 | +package erf |
| 3 | + |
| 4 | +import ( |
| 5 | + "bytes" |
| 6 | + "errors" |
| 7 | + "fmt" |
| 8 | + "unsafe" |
| 9 | +) |
| 10 | + |
| 11 | +// Erf is an error type that wraps the underlying error that stores and formats the stack trace. |
| 12 | +type Erf struct { |
| 13 | + err error |
| 14 | + format string |
| 15 | + args []interface{} |
| 16 | + tagIndexes map[string]int |
| 17 | + pc []uintptr |
| 18 | +} |
| 19 | + |
| 20 | +// Error is implementation of error. |
| 21 | +func (e *Erf) Error() string { |
| 22 | + return e.err.Error() |
| 23 | +} |
| 24 | + |
| 25 | +// Unwrap returns the underlying error. |
| 26 | +func (e *Erf) Unwrap() error { |
| 27 | + if err, ok := e.err.(WrappedError); ok { |
| 28 | + return err.Unwrap() |
| 29 | + } |
| 30 | + return nil |
| 31 | +} |
| 32 | + |
| 33 | +// Format is implementation of fmt.Formatter. |
| 34 | +func (e *Erf) Format(f fmt.State, verb rune) { |
| 35 | + buf := bytes.NewBuffer(nil) |
| 36 | + switch verb { |
| 37 | + case 's', 'v': |
| 38 | + buf.WriteString(e.err.Error()) |
| 39 | + if f.Flag('+') { |
| 40 | + format := "%+" |
| 41 | + for _, r := range []rune{'-', '#'} { |
| 42 | + if f.Flag(int(r)) { |
| 43 | + format += string(r) |
| 44 | + } |
| 45 | + } |
| 46 | + wid, prec := 1, 1 |
| 47 | + if f.Flag('-') { |
| 48 | + wid, prec = 2, 2 |
| 49 | + } |
| 50 | + if w, ok := f.Width(); ok { |
| 51 | + wid = w |
| 52 | + } |
| 53 | + if p, ok := f.Precision(); ok { |
| 54 | + prec = p |
| 55 | + } |
| 56 | + format += fmt.Sprintf("%d.%d", wid, prec) |
| 57 | + format += "s" |
| 58 | + buf.WriteRune('\n') |
| 59 | + buf.WriteString(fmt.Sprintf(format, e.StackTrace())) |
| 60 | + buf.WriteRune('\n') |
| 61 | + if !f.Flag('0') { |
| 62 | + for err := e.Unwrap(); err != nil; { |
| 63 | + if e2, ok := err.(*Erf); ok { |
| 64 | + buf.WriteRune('\n') |
| 65 | + buf.WriteString(e2.Error()) |
| 66 | + buf.WriteRune('\n') |
| 67 | + buf.WriteString(fmt.Sprintf(format, e2.StackTrace())) |
| 68 | + buf.WriteRune('\n') |
| 69 | + } |
| 70 | + if wErr, ok := err.(WrappedError); ok { |
| 71 | + err = wErr.Unwrap() |
| 72 | + } else { |
| 73 | + err = nil |
| 74 | + } |
| 75 | + } |
| 76 | + } |
| 77 | + } |
| 78 | + } |
| 79 | + if buf.Len() > 0 { |
| 80 | + _, _ = f.Write(buf.Bytes()) |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +// Fmt returns the format argument of the formatting functions (Newf, Errorf or Wrap) that created Erf. |
| 85 | +func (e *Erf) Fmt() string { |
| 86 | + return e.format |
| 87 | +} |
| 88 | + |
| 89 | +// Len returns the length of the arguments slice. |
| 90 | +func (e *Erf) Len() int { |
| 91 | + return len(e.args) |
| 92 | +} |
| 93 | + |
| 94 | +// Arg returns an argument value on the given index. It panics if index is out of range. |
| 95 | +func (e *Erf) Arg(index int) interface{} { |
| 96 | + if index < 0 || index >= e.Len() { |
| 97 | + panic("index is out of range") |
| 98 | + } |
| 99 | + return e.args[index] |
| 100 | +} |
| 101 | + |
| 102 | +// Args returns all argument values. It returns nil if Erf didn't create with formatting functions. |
| 103 | +func (e *Erf) Args() []interface{} { |
| 104 | + if e.args == nil { |
| 105 | + return nil |
| 106 | + } |
| 107 | + result := make([]interface{}, len(e.args)) |
| 108 | + copy(result, e.args) |
| 109 | + return result |
| 110 | +} |
| 111 | + |
| 112 | +// Attach attaches tags to arguments, if arguments are given. It panics if an error occurs. |
| 113 | +func (e *Erf) Attach(tags ...string) *Erf { |
| 114 | + if e.args == nil { |
| 115 | + panic("args are not using") |
| 116 | + } |
| 117 | + if e.tagIndexes != nil { |
| 118 | + panic("tags are already attached") |
| 119 | + } |
| 120 | + if len(tags) > len(e.args) { |
| 121 | + panic("tags are more than args") |
| 122 | + } |
| 123 | + tagIndexes := make(map[string]int, len(tags)) |
| 124 | + for index, tag := range tags { |
| 125 | + if _, ok := tagIndexes[tag]; ok { |
| 126 | + panic("tag is already defined") |
| 127 | + } |
| 128 | + tagIndexes[tag] = index |
| 129 | + } |
| 130 | + e.tagIndexes = tagIndexes |
| 131 | + return e |
| 132 | +} |
| 133 | + |
| 134 | +// Tag returns an argument value on the given tag. It panics if tag is not found. |
| 135 | +func (e *Erf) Tag(tag string) interface{} { |
| 136 | + index := -1 |
| 137 | + if idx, ok := e.tagIndexes[tag]; ok { |
| 138 | + index = idx |
| 139 | + } |
| 140 | + if index < 0 || index >= e.Len() { |
| 141 | + panic("tag is not found") |
| 142 | + } |
| 143 | + return e.args[index] |
| 144 | +} |
| 145 | + |
| 146 | +// PC returns program counters. |
| 147 | +func (e *Erf) PC() []uintptr { |
| 148 | + result := make([]uintptr, len(e.pc)) |
| 149 | + copy(result, e.pc) |
| 150 | + return result |
| 151 | +} |
| 152 | + |
| 153 | +// StackTrace returns a StackTrace of Erf. |
| 154 | +func (e *Erf) StackTrace() *StackTrace { |
| 155 | + return NewStackTrace(e.pc...) |
| 156 | +} |
| 157 | + |
| 158 | +func (e *Erf) initialize(skip int) { |
| 159 | + e.pc = getPC(int(4096/unsafe.Sizeof(uintptr(0))), skip) |
| 160 | +} |
| 161 | + |
| 162 | +// New creates a new Erf object with the given text. |
| 163 | +func New(text string) *Erf { |
| 164 | + e := &Erf{ |
| 165 | + err: errors.New(text), |
| 166 | + } |
| 167 | + e.initialize(4) |
| 168 | + return e |
| 169 | +} |
| 170 | + |
| 171 | +func newf(format string, args ...interface{}) *Erf { |
| 172 | + e := &Erf{ |
| 173 | + err: fmt.Errorf(format, args...), |
| 174 | + format: format, |
| 175 | + args: make([]interface{}, len(args)), |
| 176 | + } |
| 177 | + copy(e.args, args) |
| 178 | + return e |
| 179 | +} |
| 180 | + |
| 181 | +// Newf creates a new Erf object with the given format and args. |
| 182 | +func Newf(format string, args ...interface{}) *Erf { |
| 183 | + e := newf(format, args...) |
| 184 | + e.initialize(4) |
| 185 | + return e |
| 186 | +} |
| 187 | + |
| 188 | +// Errorf is similar with Newf except that it returns the error interface instead of the Erf pointer. |
| 189 | +func Errorf(format string, a ...interface{}) error { |
| 190 | + e := newf(format, a...) |
| 191 | + e.initialize(4) |
| 192 | + return e |
| 193 | +} |
| 194 | + |
| 195 | +// Wrap wraps the given error as the underlying error and returns a new Erf object as the error interface. |
| 196 | +func Wrap(err error) error { |
| 197 | + e := newf("%w", err) |
| 198 | + e.initialize(4) |
| 199 | + return e |
| 200 | +} |
0 commit comments