├── .travis.yml ├── bench_test.go ├── license ├── patents ├── stack_test.go ├── readme.md └── stack.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.3 5 | 6 | matrix: 7 | fast_finish: true 8 | 9 | before_install: 10 | - if ! go get code.google.com/p/go.tools/cmd/vet; then go get golang.org/x/tools/cmd/vet; fi 11 | - go get -v github.com/golang/lint/golint 12 | - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi 13 | 14 | install: 15 | - go install -race -v std 16 | - go get -race -t -v ./... 17 | - go install -race -v ./... 18 | 19 | script: 20 | - go vet ./... 21 | - $HOME/gopath/bin/golint . 22 | - go test -cpu=2 -race -v ./... 23 | - go test -cpu=2 -covermode=atomic ./... 24 | -------------------------------------------------------------------------------- /bench_test.go: -------------------------------------------------------------------------------- 1 | package stack_test 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sync" 7 | "testing" 8 | 9 | "github.com/facebookgo/stack" 10 | ) 11 | 12 | func BenchmarkCallersMulti(b *testing.B) { 13 | for i := 0; i < b.N; i++ { 14 | stack.CallersMulti(0) 15 | } 16 | } 17 | 18 | func BenchmarkCallers(b *testing.B) { 19 | for i := 0; i < b.N; i++ { 20 | stack.Callers(0) 21 | } 22 | } 23 | 24 | func BenchmarkCaller(b *testing.B) { 25 | for i := 0; i < b.N; i++ { 26 | stack.Caller(0) 27 | } 28 | } 29 | 30 | func BenchmarkRuntimeCallersReusePCS(b *testing.B) { 31 | pcs := make([]uintptr, 32) 32 | for i := 0; i < b.N; i++ { 33 | runtime.Callers(0, pcs) 34 | } 35 | } 36 | 37 | func BenchmarkRuntimeCallersMakePCS(b *testing.B) { 38 | for i := 0; i < b.N; i++ { 39 | pcs := make([]uintptr, 32) 40 | runtime.Callers(0, pcs) 41 | } 42 | } 43 | 44 | func BenchmarkRuntimeCallersFixedPCS(b *testing.B) { 45 | for i := 0; i < b.N; i++ { 46 | var pcs [32]uintptr 47 | runtime.Callers(0, pcs[:]) 48 | } 49 | } 50 | 51 | func BenchmarkRuntimeCallersSyncPool(b *testing.B) { 52 | pool := sync.Pool{New: func() interface{} { return make([]uintptr, 32) }} 53 | for i := 0; i < b.N; i++ { 54 | pcs := pool.Get().([]uintptr) 55 | runtime.Callers(0, pcs[:]) 56 | pcs = pcs[0:] 57 | pool.Put(pcs) 58 | } 59 | } 60 | 61 | func BenchmarkSprintf(b *testing.B) { 62 | for i := 0; i < b.N; i++ { 63 | fmt.Sprintf("hello") 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For stack 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 | -------------------------------------------------------------------------------- /patents: -------------------------------------------------------------------------------- 1 | Additional Grant of Patent Rights Version 2 2 | 3 | "Software" means the stack 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 | -------------------------------------------------------------------------------- /stack_test.go: -------------------------------------------------------------------------------- 1 | package stack_test 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/facebookgo/stack" 9 | ) 10 | 11 | func indirect1() stack.Stack { 12 | return stack.Callers(0) 13 | } 14 | 15 | func indirect2() stack.Stack { 16 | return indirect1() 17 | } 18 | 19 | func indirect3() stack.Stack { 20 | return indirect2() 21 | } 22 | 23 | func TestCallers(t *testing.T) { 24 | s := indirect3() 25 | matches := []string{ 26 | "stack_test.go:12 +indirect1$", 27 | "stack_test.go:16 +indirect2$", 28 | "stack_test.go:20 +indirect3$", 29 | "stack_test.go:24 +TestCallers$", 30 | } 31 | match(t, s.String(), matches) 32 | } 33 | 34 | func TestCallersMulti(t *testing.T) { 35 | m := stack.CallersMulti(0) 36 | const expected = "stack_test.go:35 TestCallersMulti" 37 | first := m.Stacks()[0][0].String() 38 | if !strings.HasSuffix(first, expected) { 39 | t.Fatalf(`expected suffix "%s" got "%s"`, expected, first) 40 | } 41 | } 42 | 43 | func TestCallersMultiWithTwo(t *testing.T) { 44 | m := stack.CallersMulti(0) 45 | m.AddCallers(0) 46 | matches := []string{ 47 | "stack_test.go:44 +TestCallersMultiWithTwo$", 48 | "", 49 | "", 50 | `\(Stack 2\)$`, 51 | "stack_test.go:45 +TestCallersMultiWithTwo$", 52 | } 53 | match(t, m.String(), matches) 54 | } 55 | 56 | type typ struct{} 57 | 58 | func (m typ) indirect1() stack.Stack { 59 | return stack.Callers(0) 60 | } 61 | 62 | func (m typ) indirect2() stack.Stack { 63 | return m.indirect1() 64 | } 65 | 66 | func (m typ) indirect3() stack.Stack { 67 | return m.indirect2() 68 | } 69 | 70 | func TestCallersWithStruct(t *testing.T) { 71 | var m typ 72 | s := m.indirect3() 73 | matches := []string{ 74 | "stack_test.go:59 +typ.indirect1$", 75 | "stack_test.go:63 +typ.indirect2$", 76 | "stack_test.go:67 +typ.indirect3$", 77 | "stack_test.go:72 +TestCallersWithStruct$", 78 | } 79 | match(t, s.String(), matches) 80 | } 81 | 82 | func TestCaller(t *testing.T) { 83 | f := stack.Caller(0) 84 | const expected = "stack_test.go:83 TestCaller" 85 | if !strings.HasSuffix(f.String(), expected) { 86 | t.Fatalf(`expected suffix "%s" got "%s"`, expected, f) 87 | } 88 | } 89 | 90 | func match(t testing.TB, s string, matches []string) { 91 | lines := strings.Split(s, "\n") 92 | for i, m := range matches { 93 | if !regexp.MustCompile(m).MatchString(lines[i]) { 94 | t.Fatalf( 95 | "did not find expected match \"%s\" on line %d in:\n%s", 96 | m, 97 | i, 98 | s, 99 | ) 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # stack 2 | -- 3 | import "github.com/facebookgo/stack" 4 | 5 | Package stack provides utilities to capture and pass around stack traces. 6 | 7 | This is useful for building errors that know where they originated from, to 8 | track where a certain log event occured and so on. 9 | 10 | The package provides stack.Multi which represents a sequence of stack traces. 11 | Since in Go we return errors they don't necessarily end up with a single useful 12 | stack trace. For example an error may be going thru a channel across goroutines, 13 | in which case we may want to capture a stack trace in both (or many) goroutines. 14 | stack.Multi in turn is made up of stack.Stack, which is a set of stack.Frames. 15 | Each stack.Frame contains the File/Line/Name (function name). All these types 16 | implement a pretty human readable String() function. 17 | 18 | The GOPATH is stripped from the File location. Look at the StripGOPATH function 19 | on instructions for how to embed to GOPATH into the binary for when deploying to 20 | production and the GOPATH environment variable may not be set. The package name 21 | is stripped from the Name of the function since it included in the File 22 | location. 23 | 24 | ## Usage 25 | 26 | #### func StripGOPATH 27 | 28 | ```go 29 | func StripGOPATH(f string) string 30 | ``` 31 | StripGOPATH strips the GOPATH prefix from the file path f. In development, this 32 | will be done using the GOPATH environment variable. For production builds, where 33 | the GOPATH environment will not be set, the GOPATH can be included in the binary 34 | by passing ldflags, for example: 35 | 36 | GO_LDFLAGS="$GO_LDFLAGS -X github.com/facebookgo/stack.gopath $GOPATH" 37 | go install "-ldflags=$GO_LDFLAGS" my/pkg 38 | 39 | #### func StripPackage 40 | 41 | ```go 42 | func StripPackage(n string) string 43 | ``` 44 | StripPackage strips the package name from the given Func.Name. 45 | 46 | #### type Frame 47 | 48 | ```go 49 | type Frame struct { 50 | File string 51 | Line int 52 | Name string 53 | } 54 | ``` 55 | 56 | Frame identifies a file, line & function name in the stack. 57 | 58 | #### func Caller 59 | 60 | ```go 61 | func Caller(skip int) Frame 62 | ``` 63 | Caller returns a single Frame for the caller. The argument skip is the number of 64 | stack frames to ascend, with 0 identifying the caller of Callers. 65 | 66 | #### func (Frame) String 67 | 68 | ```go 69 | func (f Frame) String() string 70 | ``` 71 | String provides the standard file:line representation. 72 | 73 | #### type Multi 74 | 75 | ```go 76 | type Multi struct { 77 | } 78 | ``` 79 | 80 | Multi represents a number of Stacks. This is useful to allow tracking a value as 81 | it travels thru code. 82 | 83 | #### func CallersMulti 84 | 85 | ```go 86 | func CallersMulti(skip int) *Multi 87 | ``` 88 | CallersMulti returns a Multi which includes one Stack for the current callers. 89 | The argument skip is the number of stack frames to ascend, with 0 identifying 90 | the caller of CallersMulti. 91 | 92 | #### func (*Multi) Add 93 | 94 | ```go 95 | func (m *Multi) Add(s Stack) 96 | ``` 97 | Add the given Stack to this Multi. 98 | 99 | #### func (*Multi) AddCallers 100 | 101 | ```go 102 | func (m *Multi) AddCallers(skip int) 103 | ``` 104 | AddCallers adds the Callers Stack to this Multi. The argument skip is the number 105 | of stack frames to ascend, with 0 identifying the caller of Callers. 106 | 107 | #### func (*Multi) Stacks 108 | 109 | ```go 110 | func (m *Multi) Stacks() []Stack 111 | ``` 112 | Stacks returns the tracked Stacks. 113 | 114 | #### func (*Multi) String 115 | 116 | ```go 117 | func (m *Multi) String() string 118 | ``` 119 | String provides a human readable multi-line stack trace. 120 | 121 | #### type Stack 122 | 123 | ```go 124 | type Stack []Frame 125 | ``` 126 | 127 | Stack represents an ordered set of Frames. 128 | 129 | #### func Callers 130 | 131 | ```go 132 | func Callers(skip int) Stack 133 | ``` 134 | Callers returns a Stack of Frames for the callers. The argument skip is the 135 | number of stack frames to ascend, with 0 identifying the caller of Callers. 136 | 137 | #### func (Stack) String 138 | 139 | ```go 140 | func (s Stack) String() string 141 | ``` 142 | String provides the standard multi-line stack trace. 143 | -------------------------------------------------------------------------------- /stack.go: -------------------------------------------------------------------------------- 1 | // Package stack provides utilities to capture and pass around stack traces. 2 | // 3 | // This is useful for building errors that know where they originated from, to 4 | // track where a certain log event occured and so on. 5 | // 6 | // The package provides stack.Multi which represents a sequence of stack 7 | // traces. Since in Go we return errors they don't necessarily end up with a 8 | // single useful stack trace. For example an error may be going thru a channel 9 | // across goroutines, in which case we may want to capture a stack trace in 10 | // both (or many) goroutines. stack.Multi in turn is made up of stack.Stack, 11 | // which is a set of stack.Frames. Each stack.Frame contains the File/Line/Name 12 | // (function name). All these types implement a pretty human readable String() 13 | // function. 14 | // 15 | // The GOPATH is stripped from the File location. Look at the StripGOPATH 16 | // function on instructions for how to embed to GOPATH into the binary for when 17 | // deploying to production and the GOPATH environment variable may not be set. 18 | // The package name is stripped from the Name of the function since it included 19 | // in the File location. 20 | package stack 21 | 22 | import ( 23 | "bytes" 24 | "fmt" 25 | "os" 26 | "path/filepath" 27 | "runtime" 28 | "strings" 29 | ) 30 | 31 | const maxStackSize = 32 32 | 33 | // Frame identifies a file, line & function name in the stack. 34 | type Frame struct { 35 | File string 36 | Line int 37 | Name string 38 | } 39 | 40 | // String provides the standard file:line representation. 41 | func (f Frame) String() string { 42 | return fmt.Sprintf("%s:%d %s", f.File, f.Line, f.Name) 43 | } 44 | 45 | // Stack represents an ordered set of Frames. 46 | type Stack []Frame 47 | 48 | // String provides the standard multi-line stack trace. 49 | func (s Stack) String() string { 50 | var b bytes.Buffer 51 | writeStack(&b, s) 52 | return b.String() 53 | } 54 | 55 | // Multi represents a number of Stacks. This is useful to allow tracking a 56 | // value as it travels thru code. 57 | type Multi struct { 58 | stacks []Stack 59 | } 60 | 61 | // Stacks returns the tracked Stacks. 62 | func (m *Multi) Stacks() []Stack { 63 | return m.stacks 64 | } 65 | 66 | // Add the given Stack to this Multi. 67 | func (m *Multi) Add(s Stack) { 68 | m.stacks = append(m.stacks, s) 69 | } 70 | 71 | // AddCallers adds the Callers Stack to this Multi. The argument skip is 72 | // the number of stack frames to ascend, with 0 identifying the caller of 73 | // Callers. 74 | func (m *Multi) AddCallers(skip int) { 75 | m.Add(Callers(skip + 1)) 76 | } 77 | 78 | // String provides a human readable multi-line stack trace. 79 | func (m *Multi) String() string { 80 | var b bytes.Buffer 81 | for i, s := range m.stacks { 82 | if i != 0 { 83 | fmt.Fprintf(&b, "\n(Stack %d)\n", i+1) 84 | } 85 | writeStack(&b, s) 86 | } 87 | return b.String() 88 | } 89 | 90 | // Copy makes a copy of the stack which is safe to modify. 91 | func (m *Multi) Copy() *Multi { 92 | m2 := &Multi{ 93 | stacks: make([]Stack, len(m.stacks)), 94 | } 95 | copy(m2.stacks, m.stacks) 96 | return m2 97 | } 98 | 99 | // Caller returns a single Frame for the caller. The argument skip is the 100 | // number of stack frames to ascend, with 0 identifying the caller of Callers. 101 | func Caller(skip int) Frame { 102 | pc, file, line, _ := runtime.Caller(skip + 1) 103 | fun := runtime.FuncForPC(pc) 104 | return Frame{ 105 | File: StripGOPATH(file), 106 | Line: line, 107 | Name: StripPackage(fun.Name()), 108 | } 109 | } 110 | 111 | // Callers returns a Stack of Frames for the callers. The argument skip is the 112 | // number of stack frames to ascend, with 0 identifying the caller of Callers. 113 | func Callers(skip int) Stack { 114 | pcs := make([]uintptr, maxStackSize) 115 | num := runtime.Callers(skip+2, pcs) 116 | stack := make(Stack, num) 117 | for i, pc := range pcs[:num] { 118 | fun := runtime.FuncForPC(pc) 119 | file, line := fun.FileLine(pc - 1) 120 | stack[i].File = StripGOPATH(file) 121 | stack[i].Line = line 122 | stack[i].Name = StripPackage(fun.Name()) 123 | } 124 | return stack 125 | } 126 | 127 | // CallersMulti returns a Multi which includes one Stack for the 128 | // current callers. The argument skip is the number of stack frames to ascend, 129 | // with 0 identifying the caller of CallersMulti. 130 | func CallersMulti(skip int) *Multi { 131 | m := new(Multi) 132 | m.AddCallers(skip + 1) 133 | return m 134 | } 135 | 136 | func writeStack(b *bytes.Buffer, s Stack) { 137 | var width int 138 | for _, f := range s { 139 | if l := len(f.File) + numDigits(f.Line) + 1; l > width { 140 | width = l 141 | } 142 | } 143 | last := len(s) - 1 144 | for i, f := range s { 145 | b.WriteString(f.File) 146 | b.WriteRune(rune(':')) 147 | n, _ := fmt.Fprintf(b, "%d", f.Line) 148 | for i := width - len(f.File) - n; i != 0; i-- { 149 | b.WriteRune(rune(' ')) 150 | } 151 | b.WriteString(f.Name) 152 | if i != last { 153 | b.WriteRune(rune('\n')) 154 | } 155 | } 156 | } 157 | 158 | func numDigits(i int) int { 159 | var n int 160 | for { 161 | n++ 162 | i = i / 10 163 | if i == 0 { 164 | return n 165 | } 166 | } 167 | } 168 | 169 | var ( 170 | // This can be set by a build script. It will be the colon separated equivalent 171 | // of the environment variable. 172 | gopath string 173 | 174 | // This is the processed version based on either the above variable set by the 175 | // build or from the GOPATH environment variable. 176 | gopaths []string 177 | ) 178 | 179 | func init() { 180 | // prefer the variable set at build time, otherwise fallback to the 181 | // environment variable. 182 | if gopath == "" { 183 | gopath = os.Getenv("GOPATH") 184 | } 185 | SetGOPATH(gopath) 186 | } 187 | 188 | // StripGOPATH strips the GOPATH prefix from the file path f. 189 | // In development, this will be done using the GOPATH environment variable. 190 | // For production builds, where the GOPATH environment will not be set, the 191 | // GOPATH can be included in the binary by passing ldflags, for example: 192 | // 193 | // GO_LDFLAGS="$GO_LDFLAGS -X github.com/facebookgo/stack.gopath $GOPATH" 194 | // go install "-ldflags=$GO_LDFLAGS" my/pkg 195 | func StripGOPATH(f string) string { 196 | for _, p := range gopaths { 197 | if strings.HasPrefix(f, p) { 198 | return f[len(p):] 199 | } 200 | } 201 | return f 202 | } 203 | 204 | // SetGOPATH configures the GOPATH to enable relative paths in stack traces. 205 | func SetGOPATH(gp string) { 206 | gopath = gp 207 | gopaths = nil 208 | 209 | for _, p := range strings.Split(gopath, ":") { 210 | if p != "" { 211 | gopaths = append(gopaths, filepath.Join(p, "src")+"/") 212 | } 213 | } 214 | 215 | // Also strip GOROOT for maximum cleanliness 216 | gopaths = append(gopaths, filepath.Join(runtime.GOROOT(), "src", "pkg")+"/") 217 | } 218 | 219 | // StripPackage strips the package name from the given Func.Name. 220 | func StripPackage(n string) string { 221 | slashI := strings.LastIndex(n, "/") 222 | if slashI == -1 { 223 | slashI = 0 // for built-in packages 224 | } 225 | dotI := strings.Index(n[slashI:], ".") 226 | if dotI == -1 { 227 | return n 228 | } 229 | return n[slashI+dotI+1:] 230 | } 231 | --------------------------------------------------------------------------------