├── readme.md ├── .travis.yml ├── license ├── stackerr_test.go ├── patents └── stackerr.go /readme.md: -------------------------------------------------------------------------------- 1 | stackerr [![Build Status](https://secure.travis-ci.org/facebookgo/stackerr.png)](http://travis-ci.org/facebookgo/stackerr) 2 | ======== 3 | 4 | Documentation: https://godoc.org/github.com/facebookgo/stackerr 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.2 5 | - 1.3 6 | 7 | matrix: 8 | fast_finish: true 9 | 10 | before_install: 11 | - go get -v code.google.com/p/go.tools/cmd/vet 12 | - go get -v github.com/golang/lint/golint 13 | - go get -v code.google.com/p/go.tools/cmd/cover 14 | 15 | install: 16 | - go install -race -v std 17 | - go get -race -t -v ./... 18 | - go install -race -v ./... 19 | 20 | script: 21 | - go vet ./... 22 | - $HOME/gopath/bin/golint . 23 | - go test -cpu=2 -race -v ./... 24 | - go test -cpu=2 -covermode=atomic ./... 25 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For stackerr software 4 | 5 | Copyright (c) 2015, Facebook, Inc. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Facebook nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /stackerr_test.go: -------------------------------------------------------------------------------- 1 | package stackerr_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "regexp" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/facebookgo/stackerr" 11 | ) 12 | 13 | func TestNew(t *testing.T) { 14 | const errStr = "foo bar baz" 15 | e := stackerr.New(errStr) 16 | matches := []string{ 17 | errStr, 18 | "stackerr_test.go:15 +TestNew$", 19 | } 20 | match(t, e.Error(), matches) 21 | } 22 | 23 | func TestNewf(t *testing.T) { 24 | const fmtStr = "%s 42" 25 | const errStr = "foo bar baz" 26 | e := stackerr.Newf(fmtStr, errStr) 27 | matches := []string{ 28 | fmt.Sprintf(fmtStr, errStr), 29 | "stackerr_test.go:26 +TestNewf$", 30 | } 31 | match(t, e.Error(), matches) 32 | } 33 | 34 | func TestWrap(t *testing.T) { 35 | const errStr = "foo bar baz" 36 | e := stackerr.Wrap(errors.New(errStr)) 37 | matches := []string{ 38 | errStr, 39 | "stackerr_test.go:36 +TestWrap$", 40 | } 41 | match(t, e.Error(), matches) 42 | } 43 | 44 | func TestNilWrap(t *testing.T) { 45 | if stackerr.WrapSkip(nil, 1) != nil { 46 | t.Fatal("did not get nil error") 47 | } 48 | } 49 | 50 | func TestDoubleWrap(t *testing.T) { 51 | e := stackerr.New("") 52 | if stackerr.WrapSkip(e, 1) != e { 53 | t.Fatal("double wrap failure") 54 | } 55 | } 56 | 57 | func TestLog(t *testing.T) { 58 | t.Log(stackerr.New("hello")) 59 | } 60 | 61 | func TestUnderlying(t *testing.T) { 62 | e1 := errors.New("") 63 | e2 := stackerr.Wrap(e1) 64 | errs := stackerr.Underlying(e2) 65 | if len(errs) != 2 || errs[0] != e2 || errs[1] != e1 { 66 | t.Fatal("failed Underlying") 67 | } 68 | } 69 | 70 | func match(t testing.TB, s string, matches []string) { 71 | lines := strings.Split(s, "\n") 72 | for i, m := range matches { 73 | if !regexp.MustCompile(m).MatchString(lines[i]) { 74 | t.Fatalf( 75 | "did not find expected match \"%s\" on line %d in:\n%s", 76 | m, 77 | i, 78 | s, 79 | ) 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /patents: -------------------------------------------------------------------------------- 1 | Additional Grant of Patent Rights Version 2 2 | 3 | "Software" means the stackerr software distributed by Facebook, Inc. 4 | 5 | Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software 6 | ("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable 7 | (subject to the termination provision below) license under any Necessary 8 | Claims, to make, have made, use, sell, offer to sell, import, and otherwise 9 | transfer the Software. For avoidance of doubt, no license is granted under 10 | Facebook’s rights in any patent claims that are infringed by (i) modifications 11 | to the Software made by you or any third party or (ii) the Software in 12 | combination with any software or other technology. 13 | 14 | The license granted hereunder will terminate, automatically and without notice, 15 | if you (or any of your subsidiaries, corporate affiliates or agents) initiate 16 | directly or indirectly, or take a direct financial interest in, any Patent 17 | Assertion: (i) against Facebook or any of its subsidiaries or corporate 18 | affiliates, (ii) against any party if such Patent Assertion arises in whole or 19 | in part from any software, technology, product or service of Facebook or any of 20 | its subsidiaries or corporate affiliates, or (iii) against any party relating 21 | to the Software. Notwithstanding the foregoing, if Facebook or any of its 22 | subsidiaries or corporate affiliates files a lawsuit alleging patent 23 | infringement against you in the first instance, and you respond by filing a 24 | patent infringement counterclaim in that lawsuit against that party that is 25 | unrelated to the Software, the license granted hereunder will not terminate 26 | under section (i) of this paragraph due to such counterclaim. 27 | 28 | A "Necessary Claim" is a claim of a patent owned by Facebook that is 29 | necessarily infringed by the Software standing alone. 30 | 31 | A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, 32 | or contributory infringement or inducement to infringe any patent, including a 33 | cross-claim or counterclaim. 34 | -------------------------------------------------------------------------------- /stackerr.go: -------------------------------------------------------------------------------- 1 | // Package stackerr provides a way to augment errors with one or more stack 2 | // traces to allow for easier debugging. 3 | package stackerr 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | 9 | "github.com/facebookgo/stack" 10 | ) 11 | 12 | // Error provides the wrapper that adds multiple Stacks to an error. Each Stack 13 | // represents a location in code thru which this error was wrapped. 14 | type Error struct { 15 | multiStack *stack.Multi 16 | underlying error 17 | } 18 | 19 | // Error provides a multi line error string that includes the stack trace. 20 | func (e *Error) Error() string { 21 | return fmt.Sprintf("%s\n%s", e.underlying, e.multiStack) 22 | } 23 | 24 | // MultiStack identifies the locations this error was wrapped at. 25 | func (e *Error) MultiStack() *stack.Multi { 26 | return e.multiStack 27 | } 28 | 29 | // Underlying returns the error that is being wrapped. 30 | func (e *Error) Underlying() error { 31 | return e.underlying 32 | } 33 | 34 | type hasMultiStack interface { 35 | MultiStack() *stack.Multi 36 | } 37 | 38 | // WrapSkip the error and add the current Stack. The argument skip is the 39 | // number of stack frames to ascend, with 0 identifying the caller of Wrap. If 40 | // the error to be wrapped has a MultiStack, the current stack will be added to 41 | // it. If the error to be wrapped is nil, a nil error is returned. 42 | func WrapSkip(err error, skip int) error { 43 | // nil errors are returned back as nil. 44 | if err == nil { 45 | return nil 46 | } 47 | 48 | // we're adding another Stack to an already wrapped error. 49 | if se, ok := err.(hasMultiStack); ok { 50 | se.MultiStack().AddCallers(skip + 1) 51 | return err 52 | } 53 | 54 | // we're create a freshly wrapped error. 55 | return &Error{ 56 | multiStack: stack.CallersMulti(skip + 1), 57 | underlying: err, 58 | } 59 | } 60 | 61 | // Wrap provides a convenience function that calls WrapSkip with skip=0. That 62 | // is, the Stack starts with the caller of Wrap. 63 | func Wrap(err error) error { 64 | return WrapSkip(err, 1) 65 | } 66 | 67 | // New returns a new error that includes the Stack. 68 | func New(s string) error { 69 | return WrapSkip(errors.New(s), 1) 70 | } 71 | 72 | // Newf formats and returns a new error that includes the Stack. 73 | func Newf(format string, args ...interface{}) error { 74 | return WrapSkip(fmt.Errorf(format, args...), 1) 75 | } 76 | 77 | type hasUnderlying interface { 78 | Underlying() error 79 | } 80 | 81 | // Underlying returns all the underlying errors by iteratively checking if the 82 | // error has an Underlying error. If e is nil, the returned slice will be nil. 83 | func Underlying(e error) []error { 84 | if e == nil { 85 | return nil 86 | } 87 | 88 | var errs []error 89 | for { 90 | if e == nil { 91 | return errs 92 | } 93 | errs = append(errs, e) 94 | 95 | if eh, ok := e.(hasUnderlying); ok { 96 | e = eh.Underlying() 97 | } else { 98 | e = nil 99 | } 100 | } 101 | } 102 | 103 | // Matcher defines the interface to check if an error matches an expectation. 104 | type Matcher interface { 105 | Match(e error) bool 106 | } 107 | 108 | type equals struct { 109 | error error 110 | } 111 | 112 | func (e *equals) Match(other error) bool { 113 | return e.error == other 114 | } 115 | 116 | // Equals returns a Matcher to check if an error equals the given error. 117 | func Equals(e error) Matcher { 118 | return &equals{e} 119 | } 120 | 121 | // HasUnderlying returns true if any of the underlying errors satisfy the given 122 | // Matcher. 123 | func HasUnderlying(e error, m Matcher) bool { 124 | for _, o := range Underlying(e) { 125 | if m.Match(o) { 126 | return true 127 | } 128 | } 129 | return false 130 | } 131 | 132 | // MatcherFunc allows using a function as a Matcher. For example to use 133 | // os.IsNotExist as a matcher. 134 | type MatcherFunc func(err error) bool 135 | 136 | // Match calls the underlying function. 137 | func (f MatcherFunc) Match(err error) bool { 138 | return f(err) 139 | } 140 | --------------------------------------------------------------------------------