├── .travis.yml ├── go.mod ├── error_1_13.go ├── LICENSE.MIT ├── join_unwrap_1_20.go ├── error_1_13_test.go ├── join_unwrap_backward.go ├── README.md ├── error_backward.go ├── parse_panic.go ├── stackframe.go ├── parse_panic_test.go ├── error.go └── error_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.8.x" 5 | - "1.11.x" 6 | - "1.16.x" 7 | - "1.21.x" 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-errors/errors 2 | 3 | go 1.14 4 | 5 | // Was not API-compatible with earlier or later releases. 6 | retract v1.3.0 7 | -------------------------------------------------------------------------------- /error_1_13.go: -------------------------------------------------------------------------------- 1 | //go:build go1.13 2 | // +build go1.13 3 | 4 | package errors 5 | 6 | import ( 7 | baseErrors "errors" 8 | ) 9 | 10 | // As finds the first error in err's tree that matches target, and if one is found, sets 11 | // target to that error value and returns true. Otherwise, it returns false. 12 | // 13 | // For more information see stdlib errors.As. 14 | func As(err error, target interface{}) bool { 15 | return baseErrors.As(err, target) 16 | } 17 | 18 | // Is detects whether the error is equal to a given error. Errors 19 | // are considered equal by this function if they are matched by errors.Is 20 | // or if their contained errors are matched through errors.Is. 21 | func Is(e error, original error) bool { 22 | if baseErrors.Is(e, original) { 23 | return true 24 | } 25 | 26 | if e, ok := e.(*Error); ok { 27 | return Is(e.Err, original) 28 | } 29 | 30 | if original, ok := original.(*Error); ok { 31 | return Is(e, original.Err) 32 | } 33 | 34 | return false 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE.MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Conrad Irwin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /join_unwrap_1_20.go: -------------------------------------------------------------------------------- 1 | //go:build go1.20 2 | // +build go1.20 3 | 4 | package errors 5 | 6 | import baseErrors "errors" 7 | 8 | // Join returns an error that wraps the given errors. 9 | // Any nil error values are discarded. 10 | // Join returns nil if every value in errs is nil. 11 | // The error formats as the concatenation of the strings obtained 12 | // by calling the Error method of each element of errs, with a newline 13 | // between each string. 14 | // 15 | // A non-nil error returned by Join implements the Unwrap() []error method. 16 | // 17 | // For more information see stdlib errors.Join. 18 | func Join(errs ...error) error { 19 | return baseErrors.Join(errs...) 20 | } 21 | 22 | // Unwrap returns the result of calling the Unwrap method on err, if err's 23 | // type contains an Unwrap method returning error. 24 | // Otherwise, Unwrap returns nil. 25 | // 26 | // Unwrap only calls a method of the form "Unwrap() error". 27 | // In particular Unwrap does not unwrap errors returned by [Join]. 28 | // 29 | // For more information see stdlib errors.Unwrap. 30 | func Unwrap(err error) error { 31 | return baseErrors.Unwrap(err) 32 | } 33 | -------------------------------------------------------------------------------- /error_1_13_test.go: -------------------------------------------------------------------------------- 1 | // +build go1.13 2 | 3 | package errors 4 | 5 | import ( 6 | "io" 7 | "testing" 8 | ) 9 | 10 | // This test should work only for go 1.13 and latter 11 | func TestIs113(t *testing.T) { 12 | custErr := errorWithCustomIs{ 13 | Key: "TestForFun", 14 | Err: io.EOF, 15 | } 16 | 17 | shouldMatch := errorWithCustomIs{ 18 | Key: "TestForFun", 19 | } 20 | 21 | shouldNotMatch := errorWithCustomIs{Key: "notOk"} 22 | 23 | if !Is(custErr, shouldMatch) { 24 | t.Errorf("custErr is not a TestForFun customError") 25 | } 26 | 27 | if Is(custErr, shouldNotMatch) { 28 | t.Errorf("custErr is a notOk customError") 29 | } 30 | 31 | if !Is(custErr, New(shouldMatch)) { 32 | t.Errorf("custErr is not a New(TestForFun customError)") 33 | } 34 | 35 | if Is(custErr, New(shouldNotMatch)) { 36 | t.Errorf("custErr is a New(notOk customError)") 37 | } 38 | 39 | if !Is(New(custErr), shouldMatch) { 40 | t.Errorf("New(custErr) is not a TestForFun customError") 41 | } 42 | 43 | if Is(New(custErr), shouldNotMatch) { 44 | t.Errorf("New(custErr) is a notOk customError") 45 | } 46 | 47 | if !Is(New(custErr), New(shouldMatch)) { 48 | t.Errorf("New(custErr) is not a New(TestForFun customError)") 49 | } 50 | 51 | if Is(New(custErr), New(shouldNotMatch)) { 52 | t.Errorf("New(custErr) is a New(notOk customError)") 53 | } 54 | } 55 | 56 | type errorWithCustomIs struct { 57 | Key string 58 | Err error 59 | } 60 | 61 | func (ewci errorWithCustomIs) Error() string { 62 | return "[" + ewci.Key + "]: " + ewci.Err.Error() 63 | } 64 | 65 | func (ewci errorWithCustomIs) Is(target error) bool { 66 | matched, ok := target.(errorWithCustomIs) 67 | return ok && matched.Key == ewci.Key 68 | } 69 | -------------------------------------------------------------------------------- /join_unwrap_backward.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.20 2 | // +build !go1.20 3 | 4 | package errors 5 | 6 | // Disclaimer: functions Join and Unwrap are copied from the stdlib errors 7 | // package v1.21.0. 8 | 9 | // Join returns an error that wraps the given errors. 10 | // Any nil error values are discarded. 11 | // Join returns nil if every value in errs is nil. 12 | // The error formats as the concatenation of the strings obtained 13 | // by calling the Error method of each element of errs, with a newline 14 | // between each string. 15 | // 16 | // A non-nil error returned by Join implements the Unwrap() []error method. 17 | func Join(errs ...error) error { 18 | n := 0 19 | for _, err := range errs { 20 | if err != nil { 21 | n++ 22 | } 23 | } 24 | if n == 0 { 25 | return nil 26 | } 27 | e := &joinError{ 28 | errs: make([]error, 0, n), 29 | } 30 | for _, err := range errs { 31 | if err != nil { 32 | e.errs = append(e.errs, err) 33 | } 34 | } 35 | return e 36 | } 37 | 38 | type joinError struct { 39 | errs []error 40 | } 41 | 42 | func (e *joinError) Error() string { 43 | var b []byte 44 | for i, err := range e.errs { 45 | if i > 0 { 46 | b = append(b, '\n') 47 | } 48 | b = append(b, err.Error()...) 49 | } 50 | return string(b) 51 | } 52 | 53 | func (e *joinError) Unwrap() []error { 54 | return e.errs 55 | } 56 | 57 | // Unwrap returns the result of calling the Unwrap method on err, if err's 58 | // type contains an Unwrap method returning error. 59 | // Otherwise, Unwrap returns nil. 60 | // 61 | // Unwrap only calls a method of the form "Unwrap() error". 62 | // In particular Unwrap does not unwrap errors returned by [Join]. 63 | func Unwrap(err error) error { 64 | u, ok := err.(interface { 65 | Unwrap() error 66 | }) 67 | if !ok { 68 | return nil 69 | } 70 | return u.Unwrap() 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-errors/errors 2 | ================ 3 | 4 | [![Build Status](https://travis-ci.org/go-errors/errors.svg?branch=master)](https://travis-ci.org/go-errors/errors) 5 | 6 | Package errors adds stacktrace support to errors in go. 7 | 8 | This is particularly useful when you want to understand the state of execution 9 | when an error was returned unexpectedly. 10 | 11 | It provides the type \*Error which implements the standard golang error 12 | interface, so you can use this library interchangeably with code that is 13 | expecting a normal error return. 14 | 15 | Usage 16 | ----- 17 | 18 | Full documentation is available on 19 | [godoc](https://godoc.org/github.com/go-errors/errors), but here's a simple 20 | example: 21 | 22 | ```go 23 | package crashy 24 | 25 | import "github.com/go-errors/errors" 26 | 27 | var Crashed = errors.Errorf("oh dear") 28 | 29 | func Crash() error { 30 | return errors.New(Crashed) 31 | } 32 | ``` 33 | 34 | This can be called as follows: 35 | 36 | ```go 37 | package main 38 | 39 | import ( 40 | "crashy" 41 | "fmt" 42 | "github.com/go-errors/errors" 43 | ) 44 | 45 | func main() { 46 | err := crashy.Crash() 47 | if err != nil { 48 | if errors.Is(err, crashy.Crashed) { 49 | fmt.Println(err.(*errors.Error).ErrorStack()) 50 | } else { 51 | panic(err) 52 | } 53 | } 54 | } 55 | ``` 56 | 57 | Meta-fu 58 | ------- 59 | 60 | This package was original written to allow reporting to 61 | [Bugsnag](https://bugsnag.com/) from 62 | [bugsnag-go](https://github.com/bugsnag/bugsnag-go), but after I found similar 63 | packages by Facebook and Dropbox, it was moved to one canonical location so 64 | everyone can benefit. 65 | 66 | This package is licensed under the MIT license, see LICENSE.MIT for details. 67 | 68 | 69 | ## Changelog 70 | * v1.1.0 updated to use go1.13's standard-library errors.Is method instead of == in errors.Is 71 | * v1.2.0 added `errors.As` from the standard library. 72 | * v1.3.0 *BREAKING* updated error methods to return `error` instead of `*Error`. 73 | > Code that needs access to the underlying `*Error` can use the new errors.AsError(e) 74 | > ``` 75 | > // before 76 | > errors.New(err).ErrorStack() 77 | > // after 78 | >. errors.AsError(errors.Wrap(err)).ErrorStack() 79 | > ``` 80 | * v1.4.0 *BREAKING* v1.4.0 reverted all changes from v1.3.0 and is identical to v1.2.0 81 | * v1.4.1 no code change, but now without an unnecessary cover.out file. 82 | * v1.4.2 performance improvement to ErrorStack() to avoid unnecessary work https://github.com/go-errors/errors/pull/40 83 | * v1.5.0 add errors.Join() and errors.Unwrap() copying the stdlib https://github.com/go-errors/errors/pull/40 84 | * v1.5.1 fix build on go1.13..go1.19 (broken by adding Join and Unwrap with wrong build constraints) 85 | -------------------------------------------------------------------------------- /error_backward.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.13 2 | // +build !go1.13 3 | 4 | package errors 5 | 6 | import ( 7 | "reflect" 8 | ) 9 | 10 | type unwrapper interface { 11 | Unwrap() error 12 | } 13 | 14 | // As assigns error or any wrapped error to the value target points 15 | // to. If there is no value of the target type of target As returns 16 | // false. 17 | func As(err error, target interface{}) bool { 18 | targetType := reflect.TypeOf(target) 19 | 20 | for { 21 | errType := reflect.TypeOf(err) 22 | 23 | if errType == nil { 24 | return false 25 | } 26 | 27 | if reflect.PtrTo(errType) == targetType { 28 | reflect.ValueOf(target).Elem().Set(reflect.ValueOf(err)) 29 | return true 30 | } 31 | 32 | wrapped, ok := err.(unwrapper) 33 | if ok { 34 | err = wrapped.Unwrap() 35 | } else { 36 | return false 37 | } 38 | } 39 | } 40 | 41 | // Is detects whether the error is equal to a given error. Errors 42 | // are considered equal by this function if they are the same object, 43 | // or if they both contain the same error inside an errors.Error. 44 | func Is(e error, original error) bool { 45 | if e == original { 46 | return true 47 | } 48 | 49 | if e, ok := e.(*Error); ok { 50 | return Is(e.Err, original) 51 | } 52 | 53 | if original, ok := original.(*Error); ok { 54 | return Is(e, original.Err) 55 | } 56 | 57 | return false 58 | } 59 | 60 | // Disclaimer: functions Join and Unwrap are copied from the stdlib errors 61 | // package v1.21.0. 62 | 63 | // Join returns an error that wraps the given errors. 64 | // Any nil error values are discarded. 65 | // Join returns nil if every value in errs is nil. 66 | // The error formats as the concatenation of the strings obtained 67 | // by calling the Error method of each element of errs, with a newline 68 | // between each string. 69 | // 70 | // A non-nil error returned by Join implements the Unwrap() []error method. 71 | func Join(errs ...error) error { 72 | n := 0 73 | for _, err := range errs { 74 | if err != nil { 75 | n++ 76 | } 77 | } 78 | if n == 0 { 79 | return nil 80 | } 81 | e := &joinError{ 82 | errs: make([]error, 0, n), 83 | } 84 | for _, err := range errs { 85 | if err != nil { 86 | e.errs = append(e.errs, err) 87 | } 88 | } 89 | return e 90 | } 91 | 92 | type joinError struct { 93 | errs []error 94 | } 95 | 96 | func (e *joinError) Error() string { 97 | var b []byte 98 | for i, err := range e.errs { 99 | if i > 0 { 100 | b = append(b, '\n') 101 | } 102 | b = append(b, err.Error()...) 103 | } 104 | return string(b) 105 | } 106 | 107 | func (e *joinError) Unwrap() []error { 108 | return e.errs 109 | } 110 | 111 | // Unwrap returns the result of calling the Unwrap method on err, if err's 112 | // type contains an Unwrap method returning error. 113 | // Otherwise, Unwrap returns nil. 114 | // 115 | // Unwrap only calls a method of the form "Unwrap() error". 116 | // In particular Unwrap does not unwrap errors returned by [Join]. 117 | func Unwrap(err error) error { 118 | u, ok := err.(interface { 119 | Unwrap() error 120 | }) 121 | if !ok { 122 | return nil 123 | } 124 | return u.Unwrap() 125 | } 126 | -------------------------------------------------------------------------------- /parse_panic.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | type uncaughtPanic struct{ message string } 9 | 10 | func (p uncaughtPanic) Error() string { 11 | return p.message 12 | } 13 | 14 | // ParsePanic allows you to get an error object from the output of a go program 15 | // that panicked. This is particularly useful with https://github.com/mitchellh/panicwrap. 16 | func ParsePanic(text string) (*Error, error) { 17 | lines := strings.Split(text, "\n") 18 | 19 | state := "start" 20 | 21 | var message string 22 | var stack []StackFrame 23 | 24 | for i := 0; i < len(lines); i++ { 25 | line := lines[i] 26 | 27 | if state == "start" { 28 | if strings.HasPrefix(line, "panic: ") { 29 | message = strings.TrimPrefix(line, "panic: ") 30 | state = "seek" 31 | } else { 32 | return nil, Errorf("bugsnag.panicParser: Invalid line (no prefix): %s", line) 33 | } 34 | 35 | } else if state == "seek" { 36 | if strings.HasPrefix(line, "goroutine ") && strings.HasSuffix(line, "[running]:") { 37 | state = "parsing" 38 | } 39 | 40 | } else if state == "parsing" { 41 | if line == "" { 42 | state = "done" 43 | break 44 | } 45 | createdBy := false 46 | if strings.HasPrefix(line, "created by ") { 47 | line = strings.TrimPrefix(line, "created by ") 48 | createdBy = true 49 | } 50 | 51 | i++ 52 | 53 | if i >= len(lines) { 54 | return nil, Errorf("bugsnag.panicParser: Invalid line (unpaired): %s", line) 55 | } 56 | 57 | frame, err := parsePanicFrame(line, lines[i], createdBy) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | stack = append(stack, *frame) 63 | if createdBy { 64 | state = "done" 65 | break 66 | } 67 | } 68 | } 69 | 70 | if state == "done" || state == "parsing" { 71 | return &Error{Err: uncaughtPanic{message}, frames: stack}, nil 72 | } 73 | return nil, Errorf("could not parse panic: %v", text) 74 | } 75 | 76 | // The lines we're passing look like this: 77 | // 78 | // main.(*foo).destruct(0xc208067e98) 79 | // /0/go/src/github.com/bugsnag/bugsnag-go/pan/main.go:22 +0x151 80 | func parsePanicFrame(name string, line string, createdBy bool) (*StackFrame, error) { 81 | idx := strings.LastIndex(name, "(") 82 | if idx == -1 && !createdBy { 83 | return nil, Errorf("bugsnag.panicParser: Invalid line (no call): %s", name) 84 | } 85 | if idx != -1 { 86 | name = name[:idx] 87 | } 88 | pkg := "" 89 | 90 | if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 { 91 | pkg += name[:lastslash] + "/" 92 | name = name[lastslash+1:] 93 | } 94 | if period := strings.Index(name, "."); period >= 0 { 95 | pkg += name[:period] 96 | name = name[period+1:] 97 | } 98 | 99 | name = strings.Replace(name, "·", ".", -1) 100 | 101 | if !strings.HasPrefix(line, "\t") { 102 | return nil, Errorf("bugsnag.panicParser: Invalid line (no tab): %s", line) 103 | } 104 | 105 | idx = strings.LastIndex(line, ":") 106 | if idx == -1 { 107 | return nil, Errorf("bugsnag.panicParser: Invalid line (no line number): %s", line) 108 | } 109 | file := line[1:idx] 110 | 111 | number := line[idx+1:] 112 | if idx = strings.Index(number, " +"); idx > -1 { 113 | number = number[:idx] 114 | } 115 | 116 | lno, err := strconv.ParseInt(number, 10, 32) 117 | if err != nil { 118 | return nil, Errorf("bugsnag.panicParser: Invalid line (bad line number): %s", line) 119 | } 120 | 121 | return &StackFrame{ 122 | File: file, 123 | LineNumber: int(lno), 124 | Package: pkg, 125 | Name: name, 126 | }, nil 127 | } 128 | -------------------------------------------------------------------------------- /stackframe.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "os" 8 | "runtime" 9 | "strings" 10 | ) 11 | 12 | // A StackFrame contains all necessary information about to generate a line 13 | // in a callstack. 14 | type StackFrame struct { 15 | // The path to the file containing this ProgramCounter 16 | File string 17 | // The LineNumber in that file 18 | LineNumber int 19 | // The Name of the function that contains this ProgramCounter 20 | Name string 21 | // The Package that contains this function 22 | Package string 23 | // The underlying ProgramCounter 24 | ProgramCounter uintptr 25 | } 26 | 27 | // NewStackFrame popoulates a stack frame object from the program counter. 28 | func NewStackFrame(pc uintptr) (frame StackFrame) { 29 | 30 | frame = StackFrame{ProgramCounter: pc} 31 | if frame.Func() == nil { 32 | return 33 | } 34 | frame.Package, frame.Name = packageAndName(frame.Func()) 35 | 36 | // pc -1 because the program counters we use are usually return addresses, 37 | // and we want to show the line that corresponds to the function call 38 | frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1) 39 | return 40 | 41 | } 42 | 43 | // Func returns the function that contained this frame. 44 | func (frame *StackFrame) Func() *runtime.Func { 45 | if frame.ProgramCounter == 0 { 46 | return nil 47 | } 48 | return runtime.FuncForPC(frame.ProgramCounter) 49 | } 50 | 51 | // String returns the stackframe formatted in the same way as go does 52 | // in runtime/debug.Stack() 53 | func (frame *StackFrame) String() string { 54 | str := fmt.Sprintf("%s:%d (0x%x)\n", frame.File, frame.LineNumber, frame.ProgramCounter) 55 | 56 | source, err := frame.sourceLine() 57 | if err != nil { 58 | return str 59 | } 60 | 61 | return str + fmt.Sprintf("\t%s: %s\n", frame.Name, source) 62 | } 63 | 64 | // SourceLine gets the line of code (from File and Line) of the original source if possible. 65 | func (frame *StackFrame) SourceLine() (string, error) { 66 | source, err := frame.sourceLine() 67 | if err != nil { 68 | return source, New(err) 69 | } 70 | return source, err 71 | } 72 | 73 | func (frame *StackFrame) sourceLine() (string, error) { 74 | if frame.LineNumber <= 0 { 75 | return "???", nil 76 | } 77 | 78 | file, err := os.Open(frame.File) 79 | if err != nil { 80 | return "", err 81 | } 82 | defer file.Close() 83 | 84 | scanner := bufio.NewScanner(file) 85 | currentLine := 1 86 | for scanner.Scan() { 87 | if currentLine == frame.LineNumber { 88 | return string(bytes.Trim(scanner.Bytes(), " \t")), nil 89 | } 90 | currentLine++ 91 | } 92 | if err := scanner.Err(); err != nil { 93 | return "", err 94 | } 95 | 96 | return "???", nil 97 | } 98 | 99 | func packageAndName(fn *runtime.Func) (string, string) { 100 | name := fn.Name() 101 | pkg := "" 102 | 103 | // The name includes the path name to the package, which is unnecessary 104 | // since the file name is already included. Plus, it has center dots. 105 | // That is, we see 106 | // runtime/debug.*T·ptrmethod 107 | // and want 108 | // *T.ptrmethod 109 | // Since the package path might contains dots (e.g. code.google.com/...), 110 | // we first remove the path prefix if there is one. 111 | if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 { 112 | pkg += name[:lastslash] + "/" 113 | name = name[lastslash+1:] 114 | } 115 | if period := strings.Index(name, "."); period >= 0 { 116 | pkg += name[:period] 117 | name = name[period+1:] 118 | } 119 | 120 | name = strings.Replace(name, "·", ".", -1) 121 | return pkg, name 122 | } 123 | -------------------------------------------------------------------------------- /parse_panic_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | var createdBy = `panic: hello! 9 | 10 | goroutine 54 [running]: 11 | runtime.panic(0x35ce40, 0xc208039db0) 12 | /0/c/go/src/pkg/runtime/panic.c:279 +0xf5 13 | github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001() 14 | /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74 15 | net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0) 16 | /0/c/go/src/pkg/net/http/server.go:1698 +0x91 17 | created by github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.App.Index 18 | /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:14 +0x3e 19 | 20 | goroutine 16 [IO wait]: 21 | net.runtime_pollWait(0x911c30, 0x72, 0x0) 22 | /0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66 23 | net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0) 24 | /0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46 25 | net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0) 26 | /0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42 27 | net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23) 28 | /0/c/go/src/pkg/net/fd_unix.go:409 +0x343 29 | net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0) 30 | /0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d 31 | net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0) 32 | /0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b 33 | github.com/revel/revel.Run(0xe6d9) 34 | /0/go/src/github.com/revel/revel/server.go:113 +0x926 35 | main.main() 36 | /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a 37 | ` 38 | 39 | var normalSplit = `panic: hello! 40 | 41 | goroutine 54 [running]: 42 | runtime.panic(0x35ce40, 0xc208039db0) 43 | /0/c/go/src/pkg/runtime/panic.c:279 +0xf5 44 | github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001() 45 | /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74 46 | net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0) 47 | /0/c/go/src/pkg/net/http/server.go:1698 +0x91 48 | 49 | goroutine 16 [IO wait]: 50 | net.runtime_pollWait(0x911c30, 0x72, 0x0) 51 | /0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66 52 | net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0) 53 | /0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46 54 | net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0) 55 | /0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42 56 | net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23) 57 | /0/c/go/src/pkg/net/fd_unix.go:409 +0x343 58 | net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0) 59 | /0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d 60 | net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0) 61 | /0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b 62 | github.com/revel/revel.Run(0xe6d9) 63 | /0/go/src/github.com/revel/revel/server.go:113 +0x926 64 | main.main() 65 | /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a 66 | ` 67 | 68 | var lastGoroutine = `panic: hello! 69 | 70 | goroutine 16 [IO wait]: 71 | net.runtime_pollWait(0x911c30, 0x72, 0x0) 72 | /0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66 73 | net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0) 74 | /0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46 75 | net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0) 76 | /0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42 77 | net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23) 78 | /0/c/go/src/pkg/net/fd_unix.go:409 +0x343 79 | net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0) 80 | /0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d 81 | net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0) 82 | /0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b 83 | github.com/revel/revel.Run(0xe6d9) 84 | /0/go/src/github.com/revel/revel/server.go:113 +0x926 85 | main.main() 86 | /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a 87 | 88 | goroutine 54 [running]: 89 | runtime.panic(0x35ce40, 0xc208039db0) 90 | /0/c/go/src/pkg/runtime/panic.c:279 +0xf5 91 | github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001() 92 | /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74 93 | net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0) 94 | /0/c/go/src/pkg/net/http/server.go:1698 +0x91 95 | ` 96 | 97 | var result = []StackFrame{ 98 | StackFrame{File: "/0/c/go/src/pkg/runtime/panic.c", LineNumber: 279, Name: "panic", Package: "runtime"}, 99 | StackFrame{File: "/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go", LineNumber: 13, Name: "func.001", Package: "github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers"}, 100 | StackFrame{File: "/0/c/go/src/pkg/net/http/server.go", LineNumber: 1698, Name: "(*Server).Serve", Package: "net/http"}, 101 | } 102 | 103 | var resultCreatedBy = append(result, 104 | StackFrame{File: "/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go", LineNumber: 14, Name: "App.Index", Package: "github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers", ProgramCounter: 0x0}) 105 | 106 | func TestParsePanic(t *testing.T) { 107 | 108 | todo := map[string]string{ 109 | "createdBy": createdBy, 110 | "normalSplit": normalSplit, 111 | "lastGoroutine": lastGoroutine, 112 | } 113 | 114 | for key, val := range todo { 115 | Err, err := ParsePanic(val) 116 | 117 | if err != nil { 118 | t.Fatal(err) 119 | } 120 | 121 | if Err.TypeName() != "panic" { 122 | t.Errorf("Wrong type: %s", Err.TypeName()) 123 | } 124 | 125 | if Err.Error() != "hello!" { 126 | t.Errorf("Wrong message: %s", Err.TypeName()) 127 | } 128 | 129 | if Err.StackFrames()[0].Func() != nil { 130 | t.Errorf("Somehow managed to find a func...") 131 | } 132 | 133 | result := result 134 | if key == "createdBy" { 135 | result = resultCreatedBy 136 | } 137 | 138 | if !reflect.DeepEqual(Err.StackFrames(), result) { 139 | t.Errorf("Wrong stack for %s: %#v", key, Err.StackFrames()) 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | // Package errors provides errors that have stack-traces. 2 | // 3 | // This is particularly useful when you want to understand the 4 | // state of execution when an error was returned unexpectedly. 5 | // 6 | // It provides the type *Error which implements the standard 7 | // golang error interface, so you can use this library interchangably 8 | // with code that is expecting a normal error return. 9 | // 10 | // For example: 11 | // 12 | // package crashy 13 | // 14 | // import "github.com/go-errors/errors" 15 | // 16 | // var Crashed = errors.Errorf("oh dear") 17 | // 18 | // func Crash() error { 19 | // return errors.New(Crashed) 20 | // } 21 | // 22 | // This can be called as follows: 23 | // 24 | // package main 25 | // 26 | // import ( 27 | // "crashy" 28 | // "fmt" 29 | // "github.com/go-errors/errors" 30 | // ) 31 | // 32 | // func main() { 33 | // err := crashy.Crash() 34 | // if err != nil { 35 | // if errors.Is(err, crashy.Crashed) { 36 | // fmt.Println(err.(*errors.Error).ErrorStack()) 37 | // } else { 38 | // panic(err) 39 | // } 40 | // } 41 | // } 42 | // 43 | // This package was original written to allow reporting to Bugsnag, 44 | // but after I found similar packages by Facebook and Dropbox, it 45 | // was moved to one canonical location so everyone can benefit. 46 | package errors 47 | 48 | import ( 49 | "bytes" 50 | "fmt" 51 | "reflect" 52 | "runtime" 53 | ) 54 | 55 | // The maximum number of stackframes on any error. 56 | var MaxStackDepth = 50 57 | 58 | // Error is an error with an attached stacktrace. It can be used 59 | // wherever the builtin error interface is expected. 60 | type Error struct { 61 | Err error 62 | stack []uintptr 63 | frames []StackFrame 64 | prefix string 65 | } 66 | 67 | // New makes an Error from the given value. If that value is already an 68 | // error then it will be used directly, if not, it will be passed to 69 | // fmt.Errorf("%v"). The stacktrace will point to the line of code that 70 | // called New. 71 | func New(e interface{}) *Error { 72 | var err error 73 | 74 | switch e := e.(type) { 75 | case error: 76 | err = e 77 | default: 78 | err = fmt.Errorf("%v", e) 79 | } 80 | 81 | stack := make([]uintptr, MaxStackDepth) 82 | length := runtime.Callers(2, stack[:]) 83 | return &Error{ 84 | Err: err, 85 | stack: stack[:length], 86 | } 87 | } 88 | 89 | // Wrap makes an Error from the given value. If that value is already an *Error 90 | // it will not be wrapped and instead will be returned without modification. If 91 | // that value is already an error then it will be used directly and wrapped. 92 | // Otherwise, the value will be passed to fmt.Errorf("%v") and then wrapped. To 93 | // explicitly wrap an *Error with a new stacktrace use Errorf. The skip 94 | // parameter indicates how far up the stack to start the stacktrace. 0 is from 95 | // the current call, 1 from its caller, etc. 96 | func Wrap(e interface{}, skip int) *Error { 97 | if e == nil { 98 | return nil 99 | } 100 | 101 | var err error 102 | 103 | switch e := e.(type) { 104 | case *Error: 105 | return e 106 | case error: 107 | err = e 108 | default: 109 | err = fmt.Errorf("%v", e) 110 | } 111 | 112 | stack := make([]uintptr, MaxStackDepth) 113 | length := runtime.Callers(2+skip, stack[:]) 114 | return &Error{ 115 | Err: err, 116 | stack: stack[:length], 117 | } 118 | } 119 | 120 | // WrapPrefix makes an Error from the given value. If that value is already an 121 | // *Error it will not be wrapped and instead will be returned without 122 | // modification. If that value is already an error then it will be used 123 | // directly and wrapped. Otherwise, the value will be passed to 124 | // fmt.Errorf("%v") and then wrapped. To explicitly wrap an *Error with a new 125 | // stacktrace use Errorf. The prefix parameter is used to add a prefix to the 126 | // error message when calling Error(). The skip parameter indicates how far up 127 | // the stack to start the stacktrace. 0 is from the current call, 1 from its 128 | // caller, etc. 129 | func WrapPrefix(e interface{}, prefix string, skip int) *Error { 130 | if e == nil { 131 | return nil 132 | } 133 | 134 | err := Wrap(e, 1+skip) 135 | 136 | if err.prefix != "" { 137 | prefix = fmt.Sprintf("%s: %s", prefix, err.prefix) 138 | } 139 | 140 | return &Error{ 141 | Err: err.Err, 142 | stack: err.stack, 143 | prefix: prefix, 144 | } 145 | 146 | } 147 | 148 | // Errorf creates a new error with the given message. You can use it 149 | // as a drop-in replacement for fmt.Errorf() to provide descriptive 150 | // errors in return values. 151 | func Errorf(format string, a ...interface{}) *Error { 152 | return Wrap(fmt.Errorf(format, a...), 1) 153 | } 154 | 155 | // Error returns the underlying error's message. 156 | func (err *Error) Error() string { 157 | 158 | msg := err.Err.Error() 159 | if err.prefix != "" { 160 | msg = fmt.Sprintf("%s: %s", err.prefix, msg) 161 | } 162 | 163 | return msg 164 | } 165 | 166 | // Stack returns the callstack formatted the same way that go does 167 | // in runtime/debug.Stack() 168 | func (err *Error) Stack() []byte { 169 | buf := bytes.Buffer{} 170 | 171 | for _, frame := range err.StackFrames() { 172 | buf.WriteString(frame.String()) 173 | } 174 | 175 | return buf.Bytes() 176 | } 177 | 178 | // Callers satisfies the bugsnag ErrorWithCallerS() interface 179 | // so that the stack can be read out. 180 | func (err *Error) Callers() []uintptr { 181 | return err.stack 182 | } 183 | 184 | // ErrorStack returns a string that contains both the 185 | // error message and the callstack. 186 | func (err *Error) ErrorStack() string { 187 | return err.TypeName() + " " + err.Error() + "\n" + string(err.Stack()) 188 | } 189 | 190 | // StackFrames returns an array of frames containing information about the 191 | // stack. 192 | func (err *Error) StackFrames() []StackFrame { 193 | if err.frames == nil { 194 | err.frames = make([]StackFrame, len(err.stack)) 195 | 196 | for i, pc := range err.stack { 197 | err.frames[i] = NewStackFrame(pc) 198 | } 199 | } 200 | 201 | return err.frames 202 | } 203 | 204 | // TypeName returns the type this error. e.g. *errors.stringError. 205 | func (err *Error) TypeName() string { 206 | if _, ok := err.Err.(uncaughtPanic); ok { 207 | return "panic" 208 | } 209 | return reflect.TypeOf(err.Err).String() 210 | } 211 | 212 | // Return the wrapped error (implements api for As function). 213 | func (err *Error) Unwrap() error { 214 | return err.Err 215 | } 216 | -------------------------------------------------------------------------------- /error_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "reflect" 8 | "runtime" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | func BenchmarkStackFormat(b *testing.B) { 14 | b.ReportAllocs() 15 | 16 | for i := 0; i < b.N; i++ { 17 | func() { 18 | defer func() { 19 | err := recover() 20 | if err != 'a' { 21 | b.Fatal(err) 22 | } 23 | 24 | e := Errorf("hi") 25 | _ = string(e.Stack()) 26 | }() 27 | 28 | a() 29 | }() 30 | } 31 | } 32 | 33 | func TestAs(t *testing.T) { 34 | var errStrIn errorString = "TestForFun" 35 | 36 | var errStrOut errorString 37 | if As(errStrIn, &errStrOut) { 38 | if errStrOut != "TestForFun" { 39 | t.Errorf("direct errStr value is not returned") 40 | } 41 | } else { 42 | t.Errorf("direct errStr is not returned") 43 | } 44 | 45 | errStrOut = "" 46 | err := Wrap(errStrIn, 0) 47 | if As(err, &errStrOut) { 48 | if errStrOut != "TestForFun" { 49 | t.Errorf("wrapped errStr value is not returned") 50 | } 51 | } else { 52 | t.Errorf("wrapped errStr is not returned") 53 | } 54 | } 55 | 56 | func TestStackFormat(t *testing.T) { 57 | 58 | defer func() { 59 | err := recover() 60 | if err != 'a' { 61 | t.Fatal(err) 62 | } 63 | 64 | e, expected := Errorf("hi"), callers() 65 | 66 | bs := [][]uintptr{e.stack, expected} 67 | 68 | if err := compareStacks(bs[0], bs[1]); err != nil { 69 | t.Errorf("Stack didn't match") 70 | t.Errorf(err.Error()) 71 | } 72 | 73 | stack := string(e.Stack()) 74 | 75 | if !strings.Contains(stack, "a: b(5)") { 76 | t.Errorf("Stack trace does not contain source line: 'a: b(5)'") 77 | t.Errorf(stack) 78 | } 79 | if !strings.Contains(stack, "error_test.go:") { 80 | t.Errorf("Stack trace does not contain file name: 'error_test.go:'") 81 | t.Errorf(stack) 82 | } 83 | }() 84 | 85 | a() 86 | } 87 | 88 | func TestSkipWorks(t *testing.T) { 89 | 90 | defer func() { 91 | err := recover() 92 | if err != 'a' { 93 | t.Fatal(err) 94 | } 95 | 96 | bs := [][]uintptr{Wrap("hi", 2).stack, callersSkip(2)} 97 | 98 | if err := compareStacks(bs[0], bs[1]); err != nil { 99 | t.Errorf("Stack didn't match") 100 | t.Errorf(err.Error()) 101 | } 102 | }() 103 | 104 | a() 105 | } 106 | 107 | func TestNew(t *testing.T) { 108 | 109 | err := New("foo") 110 | 111 | if err.Error() != "foo" { 112 | t.Errorf("Wrong message") 113 | } 114 | 115 | err = New(fmt.Errorf("foo")) 116 | 117 | if err.Error() != "foo" { 118 | t.Errorf("Wrong message") 119 | } 120 | 121 | bs := [][]uintptr{New("foo").stack, callers()} 122 | 123 | if err := compareStacks(bs[0], bs[1]); err != nil { 124 | t.Errorf("Stack didn't match") 125 | t.Errorf(err.Error()) 126 | } 127 | 128 | if err.ErrorStack() != err.TypeName()+" "+err.Error()+"\n"+string(err.Stack()) { 129 | t.Errorf("ErrorStack is in the wrong format") 130 | } 131 | } 132 | 133 | // This test should work for any go version 134 | func TestIs(t *testing.T) { 135 | if Is(nil, io.EOF) { 136 | t.Errorf("nil is an error") 137 | } 138 | 139 | if !Is(io.EOF, io.EOF) { 140 | t.Errorf("io.EOF is not io.EOF") 141 | } 142 | 143 | if !Is(io.EOF, New(io.EOF)) { 144 | t.Errorf("io.EOF is not New(io.EOF)") 145 | } 146 | 147 | if !Is(New(io.EOF), New(io.EOF)) { 148 | t.Errorf("New(io.EOF) is not New(io.EOF)") 149 | } 150 | 151 | if Is(io.EOF, fmt.Errorf("io.EOF")) { 152 | t.Errorf("io.EOF is fmt.Errorf") 153 | } 154 | } 155 | 156 | func TestWrapError(t *testing.T) { 157 | 158 | e := func() error { 159 | return Wrap("hi", 1) 160 | }() 161 | 162 | if e.Error() != "hi" { 163 | t.Errorf("Constructor with a string failed") 164 | } 165 | 166 | if Wrap(fmt.Errorf("yo"), 0).Error() != "yo" { 167 | t.Errorf("Constructor with an error failed") 168 | } 169 | 170 | if Wrap(e, 0) != e { 171 | t.Errorf("Constructor with an Error failed") 172 | } 173 | 174 | if Wrap(nil, 0) != nil { 175 | t.Errorf("Constructor with nil failed") 176 | } 177 | } 178 | 179 | func TestWrapPrefixError(t *testing.T) { 180 | 181 | e := func() error { 182 | return WrapPrefix("hi", "prefix", 1) 183 | }() 184 | 185 | if e.Error() != "prefix: hi" { 186 | t.Errorf("Constructor with a string failed") 187 | } 188 | 189 | if WrapPrefix(fmt.Errorf("yo"), "prefix", 0).Error() != "prefix: yo" { 190 | t.Errorf("Constructor with an error failed") 191 | } 192 | 193 | prefixed := WrapPrefix(e, "prefix", 0) 194 | original := e.(*Error) 195 | 196 | if prefixed.Err != original.Err || !reflect.DeepEqual(prefixed.stack, original.stack) || !reflect.DeepEqual(prefixed.frames, original.frames) || prefixed.Error() != "prefix: prefix: hi" { 197 | t.Errorf("Constructor with an Error failed") 198 | } 199 | 200 | if original.Error() == prefixed.Error() { 201 | t.Errorf("WrapPrefix changed the original error") 202 | } 203 | 204 | if WrapPrefix(nil, "prefix", 0) != nil { 205 | t.Errorf("Constructor with nil failed") 206 | } 207 | 208 | if !strings.HasSuffix(original.StackFrames()[0].File, "error_test.go") || strings.HasSuffix(original.StackFrames()[1].File, "error_test.go") { 209 | t.Errorf("Skip failed") 210 | } 211 | } 212 | 213 | func ExampleErrorf(x int) (int, error) { 214 | if x%2 == 1 { 215 | return 0, Errorf("can only halve even numbers, got %d", x) 216 | } 217 | return x / 2, nil 218 | } 219 | 220 | func ExampleWrapError() (error, error) { 221 | // Wrap io.EOF with the current stack-trace and return it 222 | return nil, Wrap(io.EOF, 0) 223 | } 224 | 225 | func ExampleWrapError_skip() { 226 | defer func() { 227 | if err := recover(); err != nil { 228 | // skip 1 frame (the deferred function) and then return the wrapped err 229 | err = Wrap(err, 1) 230 | } 231 | }() 232 | } 233 | 234 | func ExampleIs(reader io.Reader, buff []byte) { 235 | _, err := reader.Read(buff) 236 | if Is(err, io.EOF) { 237 | return 238 | } 239 | } 240 | 241 | func ExampleNew(UnexpectedEOF error) error { 242 | // calling New attaches the current stacktrace to the existing UnexpectedEOF error 243 | return New(UnexpectedEOF) 244 | } 245 | 246 | func ExampleWrap() error { 247 | 248 | if err := recover(); err != nil { 249 | return Wrap(err, 1) 250 | } 251 | 252 | return a() 253 | } 254 | 255 | func ExampleError_Error(err error) { 256 | fmt.Println(err.Error()) 257 | } 258 | 259 | func ExampleError_ErrorStack(err error) { 260 | fmt.Println(err.(*Error).ErrorStack()) 261 | } 262 | 263 | func ExampleError_Stack(err *Error) { 264 | fmt.Println(err.Stack()) 265 | } 266 | 267 | func ExampleError_TypeName(err *Error) { 268 | fmt.Println(err.TypeName(), err.Error()) 269 | } 270 | 271 | func ExampleError_StackFrames(err *Error) { 272 | for _, frame := range err.StackFrames() { 273 | fmt.Println(frame.File, frame.LineNumber, frame.Package, frame.Name) 274 | } 275 | } 276 | 277 | func a() error { 278 | b(5) 279 | return nil 280 | } 281 | 282 | func b(i int) { 283 | c() 284 | } 285 | 286 | func c() { 287 | panic('a') 288 | } 289 | 290 | // compareStacks will compare a stack created using the errors package (actual) 291 | // with a reference stack created with the callers function (expected). The 292 | // first entry is not compared since the actual and expected stacks cannot 293 | // be created at the exact same program counter position so the first entry 294 | // will always differ somewhat. Returns nil if the stacks are equal enough and 295 | // an error containing a detailed error message otherwise. 296 | func compareStacks(actual, expected []uintptr) error { 297 | if len(actual) != len(expected) { 298 | return stackCompareError("Stacks does not have equal length", actual, expected) 299 | } 300 | for i, pc := range actual { 301 | if i != 0 && pc != expected[i] { 302 | return stackCompareError(fmt.Sprintf("Stacks does not match entry %d (and maybe others)", i), actual, expected) 303 | } 304 | } 305 | return nil 306 | } 307 | 308 | func stackCompareError(msg string, actual, expected []uintptr) error { 309 | return fmt.Errorf("%s\nActual stack trace:\n%s\nExpected stack trace:\n%s", msg, readableStackTrace(actual), readableStackTrace(expected)) 310 | } 311 | 312 | func callers() []uintptr { 313 | return callersSkip(1) 314 | } 315 | 316 | func callersSkip(skip int) []uintptr { 317 | callers := make([]uintptr, MaxStackDepth) 318 | length := runtime.Callers(skip+2, callers[:]) 319 | return callers[:length] 320 | } 321 | 322 | func readableStackTrace(callers []uintptr) string { 323 | var result bytes.Buffer 324 | frames := callersToFrames(callers) 325 | for _, frame := range frames { 326 | result.WriteString(fmt.Sprintf("%s:%d (%#x)\n\t%s\n", frame.File, frame.Line, frame.PC, frame.Function)) 327 | } 328 | return result.String() 329 | } 330 | 331 | func callersToFrames(callers []uintptr) []runtime.Frame { 332 | frames := make([]runtime.Frame, 0, len(callers)) 333 | framesPtr := runtime.CallersFrames(callers) 334 | for { 335 | frame, more := framesPtr.Next() 336 | frames = append(frames, frame) 337 | if !more { 338 | return frames 339 | } 340 | } 341 | } 342 | 343 | type errorString string 344 | 345 | func (e errorString) Error() string { 346 | return string(e) 347 | } 348 | --------------------------------------------------------------------------------