pax_global_header00006660000000000000000000000064127155174130014520gustar00rootroot0000000000000052 comment=100eb0c0a9c5b306ca2fb4f165df21d80ada4b82 stack-1.5.2/000077500000000000000000000000001271551741300126325ustar00rootroot00000000000000stack-1.5.2/.travis.yml000066400000000000000000000003261271551741300147440ustar00rootroot00000000000000language: go sudo: false go: - 1.2 - 1.3 - 1.4 - 1.5 - 1.6 - tip before_install: - go get github.com/mattn/goveralls - go get golang.org/x/tools/cmd/cover script: - goveralls -service=travis-ci stack-1.5.2/LICENSE.md000066400000000000000000000010471271551741300142400ustar00rootroot00000000000000Copyright 2014 Chris Hines Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. stack-1.5.2/README.md000066400000000000000000000030271271551741300141130ustar00rootroot00000000000000[![GoDoc](https://godoc.org/github.com/go-stack/stack?status.svg)](https://godoc.org/github.com/go-stack/stack) [![Go Report Card](https://goreportcard.com/badge/go-stack/stack)](https://goreportcard.com/report/go-stack/stack) [![TravisCI](https://travis-ci.org/go-stack/stack.svg?branch=master)](https://travis-ci.org/go-stack/stack) [![Coverage Status](https://coveralls.io/repos/github/go-stack/stack/badge.svg?branch=master)](https://coveralls.io/github/go-stack/stack?branch=master) # stack Package stack implements utilities to capture, manipulate, and format call stacks. It provides a simpler API than package runtime. The implementation takes care of the minutia and special cases of interpreting the program counter (pc) values returned by runtime.Callers. ## Versioning Package stack publishes releases via [semver](http://semver.org/) compatible Git tags prefixed with a single 'v'. The master branch always contains the latest release. The develop branch contains unreleased commits. ## Formatting Package stack's types implement fmt.Formatter, which provides a simple and flexible way to declaratively configure formatting when used with logging or error tracking packages. ```go func DoTheThing() { c := stack.Caller(0) log.Print(c) // "source.go:10" log.Printf("%+v", c) // "pkg/path/source.go:10" log.Printf("%n", c) // "DoTheThing" s := stack.Trace().TrimRuntime() log.Print(s) // "[source.go:15 caller.go:42 main.go:14]" } ``` See the docs for all of the supported formatting options. stack-1.5.2/format_test.go000066400000000000000000000005241271551741300155110ustar00rootroot00000000000000// +build go1.2 package stack_test import ( "fmt" "github.com/go-stack/stack" ) func Example_callFormat() { logCaller("%+s") logCaller("%v %[1]n()") // Output: // github.com/go-stack/stack/format_test.go // format_test.go:13 Example_callFormat() } func logCaller(format string) { fmt.Printf(format+"\n", stack.Caller(1)) } stack-1.5.2/stack.go000066400000000000000000000211001271551741300142600ustar00rootroot00000000000000// Package stack implements utilities to capture, manipulate, and format call // stacks. It provides a simpler API than package runtime. // // The implementation takes care of the minutia and special cases of // interpreting the program counter (pc) values returned by runtime.Callers. // // Package stack's types implement fmt.Formatter, which provides a simple and // flexible way to declaratively configure formatting when used with logging // or error tracking packages. package stack import ( "bytes" "errors" "fmt" "io" "runtime" "strconv" "strings" ) // Call records a single function invocation from a goroutine stack. type Call struct { fn *runtime.Func pc uintptr } // Caller returns a Call from the stack of the current goroutine. The argument // skip is the number of stack frames to ascend, with 0 identifying the // calling function. func Caller(skip int) Call { var pcs [2]uintptr n := runtime.Callers(skip+1, pcs[:]) var c Call if n < 2 { return c } c.pc = pcs[1] if runtime.FuncForPC(pcs[0]) != sigpanic { c.pc-- } c.fn = runtime.FuncForPC(c.pc) return c } // String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", c). func (c Call) String() string { return fmt.Sprint(c) } // MarshalText implements encoding.TextMarshaler. It formats the Call the same // as fmt.Sprintf("%v", c). func (c Call) MarshalText() ([]byte, error) { if c.fn == nil { return nil, ErrNoFunc } buf := bytes.Buffer{} fmt.Fprint(&buf, c) return buf.Bytes(), nil } // ErrNoFunc means that the Call has a nil *runtime.Func. The most likely // cause is a Call with the zero value. var ErrNoFunc = errors.New("no call stack information") // Format implements fmt.Formatter with support for the following verbs. // // %s source file // %d line number // %n function name // %v equivalent to %s:%d // // It accepts the '+' and '#' flags for most of the verbs as follows. // // %+s path of source file relative to the compile time GOPATH // %#s full path of source file // %+n import path qualified function name // %+v equivalent to %+s:%d // %#v equivalent to %#s:%d func (c Call) Format(s fmt.State, verb rune) { if c.fn == nil { fmt.Fprintf(s, "%%!%c(NOFUNC)", verb) return } switch verb { case 's', 'v': file, line := c.fn.FileLine(c.pc) switch { case s.Flag('#'): // done case s.Flag('+'): file = file[pkgIndex(file, c.fn.Name()):] default: const sep = "/" if i := strings.LastIndex(file, sep); i != -1 { file = file[i+len(sep):] } } io.WriteString(s, file) if verb == 'v' { buf := [7]byte{':'} s.Write(strconv.AppendInt(buf[:1], int64(line), 10)) } case 'd': _, line := c.fn.FileLine(c.pc) buf := [6]byte{} s.Write(strconv.AppendInt(buf[:0], int64(line), 10)) case 'n': name := c.fn.Name() if !s.Flag('+') { const pathSep = "/" if i := strings.LastIndex(name, pathSep); i != -1 { name = name[i+len(pathSep):] } const pkgSep = "." if i := strings.Index(name, pkgSep); i != -1 { name = name[i+len(pkgSep):] } } io.WriteString(s, name) } } // PC returns the program counter for this call frame; multiple frames may // have the same PC value. func (c Call) PC() uintptr { return c.pc } // name returns the import path qualified name of the function containing the // call. func (c Call) name() string { if c.fn == nil { return "???" } return c.fn.Name() } func (c Call) file() string { if c.fn == nil { return "???" } file, _ := c.fn.FileLine(c.pc) return file } func (c Call) line() int { if c.fn == nil { return 0 } _, line := c.fn.FileLine(c.pc) return line } // CallStack records a sequence of function invocations from a goroutine // stack. type CallStack []Call // String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", cs). func (cs CallStack) String() string { return fmt.Sprint(cs) } var ( openBracketBytes = []byte("[") closeBracketBytes = []byte("]") spaceBytes = []byte(" ") ) // MarshalText implements encoding.TextMarshaler. It formats the CallStack the // same as fmt.Sprintf("%v", cs). func (cs CallStack) MarshalText() ([]byte, error) { buf := bytes.Buffer{} buf.Write(openBracketBytes) for i, pc := range cs { if pc.fn == nil { return nil, ErrNoFunc } if i > 0 { buf.Write(spaceBytes) } fmt.Fprint(&buf, pc) } buf.Write(closeBracketBytes) return buf.Bytes(), nil } // Format implements fmt.Formatter by printing the CallStack as square brackets // ([, ]) surrounding a space separated list of Calls each formatted with the // supplied verb and options. func (cs CallStack) Format(s fmt.State, verb rune) { s.Write(openBracketBytes) for i, pc := range cs { if i > 0 { s.Write(spaceBytes) } pc.Format(s, verb) } s.Write(closeBracketBytes) } // findSigpanic intentionally executes faulting code to generate a stack trace // containing an entry for runtime.sigpanic. func findSigpanic() *runtime.Func { var fn *runtime.Func var p *int func() int { defer func() { if p := recover(); p != nil { var pcs [512]uintptr n := runtime.Callers(2, pcs[:]) for _, pc := range pcs[:n] { f := runtime.FuncForPC(pc) if f.Name() == "runtime.sigpanic" { fn = f break } } } }() // intentional nil pointer dereference to trigger sigpanic return *p }() return fn } var sigpanic = findSigpanic() // Trace returns a CallStack for the current goroutine with element 0 // identifying the calling function. func Trace() CallStack { var pcs [512]uintptr n := runtime.Callers(2, pcs[:]) cs := make([]Call, n) for i, pc := range pcs[:n] { pcFix := pc if i > 0 && cs[i-1].fn != sigpanic { pcFix-- } cs[i] = Call{ fn: runtime.FuncForPC(pcFix), pc: pcFix, } } return cs } // TrimBelow returns a slice of the CallStack with all entries below c // removed. func (cs CallStack) TrimBelow(c Call) CallStack { for len(cs) > 0 && cs[0].pc != c.pc { cs = cs[1:] } return cs } // TrimAbove returns a slice of the CallStack with all entries above c // removed. func (cs CallStack) TrimAbove(c Call) CallStack { for len(cs) > 0 && cs[len(cs)-1].pc != c.pc { cs = cs[:len(cs)-1] } return cs } // pkgIndex returns the index that results in file[index:] being the path of // file relative to the compile time GOPATH, and file[:index] being the // $GOPATH/src/ portion of file. funcName must be the name of a function in // file as returned by runtime.Func.Name. func pkgIndex(file, funcName string) int { // As of Go 1.6.2 there is no direct way to know the compile time GOPATH // at runtime, but we can infer the number of path segments in the GOPATH. // We note that runtime.Func.Name() returns the function name qualified by // the import path, which does not include the GOPATH. Thus we can trim // segments from the beginning of the file path until the number of path // separators remaining is one more than the number of path separators in // the function name. For example, given: // // GOPATH /home/user // file /home/user/src/pkg/sub/file.go // fn.Name() pkg/sub.Type.Method // // We want to produce: // // file[:idx] == /home/user/src/ // file[idx:] == pkg/sub/file.go // // From this we can easily see that fn.Name() has one less path separator // than our desired result for file[idx:]. We count separators from the // end of the file path until it finds two more than in the function name // and then move one character forward to preserve the initial path // segment without a leading separator. const sep = "/" i := len(file) for n := strings.Count(funcName, sep) + 2; n > 0; n-- { i = strings.LastIndex(file[:i], sep) if i == -1 { i = -len(sep) break } } // get back to 0 or trim the leading separator return i + len(sep) } var runtimePath string func init() { var pcs [1]uintptr runtime.Callers(0, pcs[:]) fn := runtime.FuncForPC(pcs[0]) file, _ := fn.FileLine(pcs[0]) idx := pkgIndex(file, fn.Name()) runtimePath = file[:idx] if runtime.GOOS == "windows" { runtimePath = strings.ToLower(runtimePath) } } func inGoroot(c Call) bool { file := c.file() if len(file) == 0 || file[0] == '?' { return true } if runtime.GOOS == "windows" { file = strings.ToLower(file) } return strings.HasPrefix(file, runtimePath) || strings.HasSuffix(file, "/_testmain.go") } // TrimRuntime returns a slice of the CallStack with the topmost entries from // the go runtime removed. It considers any calls originating from unknown // files, files under GOROOT, or _testmain.go as part of the runtime. func (cs CallStack) TrimRuntime() CallStack { for len(cs) > 0 && inGoroot(cs[len(cs)-1]) { cs = cs[:len(cs)-1] } return cs } stack-1.5.2/stack_test.go000066400000000000000000000205471271551741300153350ustar00rootroot00000000000000package stack_test import ( "fmt" "io/ioutil" "path" "path/filepath" "reflect" "runtime" "strings" "testing" "github.com/go-stack/stack" ) const importPath = "github.com/go-stack/stack" type testType struct{} func (tt testType) testMethod() (c stack.Call, pc uintptr, file string, line int, ok bool) { c = stack.Caller(0) pc, file, line, ok = runtime.Caller(0) line-- return } func TestCallFormat(t *testing.T) { t.Parallel() c := stack.Caller(0) pc, file, line, ok := runtime.Caller(0) line-- if !ok { t.Fatal("runtime.Caller(0) failed") } relFile := path.Join(importPath, filepath.Base(file)) c2, pc2, file2, line2, ok2 := testType{}.testMethod() if !ok2 { t.Fatal("runtime.Caller(0) failed") } relFile2 := path.Join(importPath, filepath.Base(file2)) data := []struct { c stack.Call desc string fmt string out string }{ {stack.Call{}, "error", "%s", "%!s(NOFUNC)"}, {c, "func", "%s", path.Base(file)}, {c, "func", "%+s", relFile}, {c, "func", "%#s", file}, {c, "func", "%d", fmt.Sprint(line)}, {c, "func", "%n", "TestCallFormat"}, {c, "func", "%+n", runtime.FuncForPC(pc - 1).Name()}, {c, "func", "%v", fmt.Sprint(path.Base(file), ":", line)}, {c, "func", "%+v", fmt.Sprint(relFile, ":", line)}, {c, "func", "%#v", fmt.Sprint(file, ":", line)}, {c2, "meth", "%s", path.Base(file2)}, {c2, "meth", "%+s", relFile2}, {c2, "meth", "%#s", file2}, {c2, "meth", "%d", fmt.Sprint(line2)}, {c2, "meth", "%n", "testType.testMethod"}, {c2, "meth", "%+n", runtime.FuncForPC(pc2).Name()}, {c2, "meth", "%v", fmt.Sprint(path.Base(file2), ":", line2)}, {c2, "meth", "%+v", fmt.Sprint(relFile2, ":", line2)}, {c2, "meth", "%#v", fmt.Sprint(file2, ":", line2)}, } for _, d := range data { got := fmt.Sprintf(d.fmt, d.c) if got != d.out { t.Errorf("fmt.Sprintf(%q, Call(%s)) = %s, want %s", d.fmt, d.desc, got, d.out) } } } func TestCallString(t *testing.T) { t.Parallel() c := stack.Caller(0) _, file, line, ok := runtime.Caller(0) line-- if !ok { t.Fatal("runtime.Caller(0) failed") } c2, _, file2, line2, ok2 := testType{}.testMethod() if !ok2 { t.Fatal("runtime.Caller(0) failed") } data := []struct { c stack.Call desc string out string }{ {stack.Call{}, "error", "%!v(NOFUNC)"}, {c, "func", fmt.Sprint(path.Base(file), ":", line)}, {c2, "meth", fmt.Sprint(path.Base(file2), ":", line2)}, } for _, d := range data { got := d.c.String() if got != d.out { t.Errorf("got %s, want %s", got, d.out) } } } func TestCallMarshalText(t *testing.T) { t.Parallel() c := stack.Caller(0) _, file, line, ok := runtime.Caller(0) line-- if !ok { t.Fatal("runtime.Caller(0) failed") } c2, _, file2, line2, ok2 := testType{}.testMethod() if !ok2 { t.Fatal("runtime.Caller(0) failed") } data := []struct { c stack.Call desc string out []byte err error }{ {stack.Call{}, "error", nil, stack.ErrNoFunc}, {c, "func", []byte(fmt.Sprint(path.Base(file), ":", line)), nil}, {c2, "meth", []byte(fmt.Sprint(path.Base(file2), ":", line2)), nil}, } for _, d := range data { text, err := d.c.MarshalText() if got, want := err, d.err; got != want { t.Errorf("%s: got err %v, want err %v", d.desc, got, want) } if got, want := text, d.out; !reflect.DeepEqual(got, want) { t.Errorf("%s: got %s, want %s", d.desc, got, want) } } } func TestCallStackString(t *testing.T) { cs, line0 := getTrace(t) _, file, line1, ok := runtime.Caller(0) line1-- if !ok { t.Fatal("runtime.Caller(0) failed") } file = path.Base(file) if got, want := cs.String(), fmt.Sprintf("[%s:%d %s:%d]", file, line0, file, line1); got != want { t.Errorf("\n got %v\nwant %v", got, want) } } func TestCallStackMarshalText(t *testing.T) { cs, line0 := getTrace(t) _, file, line1, ok := runtime.Caller(0) line1-- if !ok { t.Fatal("runtime.Caller(0) failed") } file = path.Base(file) text, _ := cs.MarshalText() if got, want := text, []byte(fmt.Sprintf("[%s:%d %s:%d]", file, line0, file, line1)); !reflect.DeepEqual(got, want) { t.Errorf("\n got %v\nwant %v", got, want) } } func getTrace(t *testing.T) (stack.CallStack, int) { cs := stack.Trace().TrimRuntime() _, _, line, ok := runtime.Caller(0) line-- if !ok { t.Fatal("runtime.Caller(0) failed") } return cs, line } func TestTrimAbove(t *testing.T) { trace := trimAbove() if got, want := len(trace), 2; got != want { t.Errorf("got len(trace) == %v, want %v, trace: %n", got, want, trace) } if got, want := fmt.Sprintf("%n", trace[1]), "TestTrimAbove"; got != want { t.Errorf("got %q, want %q", got, want) } } func trimAbove() stack.CallStack { call := stack.Caller(1) trace := stack.Trace() return trace.TrimAbove(call) } func TestTrimBelow(t *testing.T) { trace := trimBelow() if got, want := fmt.Sprintf("%n", trace[0]), "TestTrimBelow"; got != want { t.Errorf("got %q, want %q", got, want) } } func trimBelow() stack.CallStack { call := stack.Caller(1) trace := stack.Trace() return trace.TrimBelow(call) } func TestTrimRuntime(t *testing.T) { trace := stack.Trace().TrimRuntime() if got, want := len(trace), 1; got != want { t.Errorf("got len(trace) == %v, want %v, goroot: %q, trace: %#v", got, want, runtime.GOROOT(), trace) } } func BenchmarkCallVFmt(b *testing.B) { c := stack.Caller(0) b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprint(ioutil.Discard, c) } } func BenchmarkCallPlusVFmt(b *testing.B) { c := stack.Caller(0) b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprintf(ioutil.Discard, "%+v", c) } } func BenchmarkCallSharpVFmt(b *testing.B) { c := stack.Caller(0) b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprintf(ioutil.Discard, "%#v", c) } } func BenchmarkCallSFmt(b *testing.B) { c := stack.Caller(0) b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprintf(ioutil.Discard, "%s", c) } } func BenchmarkCallPlusSFmt(b *testing.B) { c := stack.Caller(0) b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprintf(ioutil.Discard, "%+s", c) } } func BenchmarkCallSharpSFmt(b *testing.B) { c := stack.Caller(0) b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprintf(ioutil.Discard, "%#s", c) } } func BenchmarkCallDFmt(b *testing.B) { c := stack.Caller(0) b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprintf(ioutil.Discard, "%d", c) } } func BenchmarkCallNFmt(b *testing.B) { c := stack.Caller(0) b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprintf(ioutil.Discard, "%n", c) } } func BenchmarkCallPlusNFmt(b *testing.B) { c := stack.Caller(0) b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprintf(ioutil.Discard, "%+n", c) } } func BenchmarkCaller(b *testing.B) { for i := 0; i < b.N; i++ { stack.Caller(0) } } func BenchmarkTrace(b *testing.B) { for i := 0; i < b.N; i++ { stack.Trace() } } func deepStack(depth int, b *testing.B) stack.CallStack { if depth > 0 { return deepStack(depth-1, b) } b.StartTimer() s := stack.Trace() return s } func BenchmarkTrace10(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() deepStack(10, b) } } func BenchmarkTrace50(b *testing.B) { b.StopTimer() for i := 0; i < b.N; i++ { deepStack(50, b) } } func BenchmarkTrace100(b *testing.B) { b.StopTimer() for i := 0; i < b.N; i++ { deepStack(100, b) } } //////////////// // Benchmark functions followed by formatting //////////////// func BenchmarkCallerAndVFmt(b *testing.B) { for i := 0; i < b.N; i++ { fmt.Fprint(ioutil.Discard, stack.Caller(0)) } } func BenchmarkTraceAndVFmt(b *testing.B) { for i := 0; i < b.N; i++ { fmt.Fprint(ioutil.Discard, stack.Trace()) } } func BenchmarkTrace10AndVFmt(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() fmt.Fprint(ioutil.Discard, deepStack(10, b)) } } //////////////// // Baseline against package runtime. //////////////// func BenchmarkRuntimeCaller(b *testing.B) { for i := 0; i < b.N; i++ { runtime.Caller(0) } } func BenchmarkRuntimeCallerAndFmt(b *testing.B) { for i := 0; i < b.N; i++ { _, file, line, _ := runtime.Caller(0) const sep = "/" if i := strings.LastIndex(file, sep); i != -1 { file = file[i+len(sep):] } fmt.Fprint(ioutil.Discard, file, ":", line) } } func BenchmarkFuncForPC(b *testing.B) { pc, _, _, _ := runtime.Caller(0) pc-- b.ResetTimer() for i := 0; i < b.N; i++ { runtime.FuncForPC(pc) } } func BenchmarkFuncFileLine(b *testing.B) { pc, _, _, _ := runtime.Caller(0) pc-- fn := runtime.FuncForPC(pc) b.ResetTimer() for i := 0; i < b.N; i++ { fn.FileLine(pc) } } stack-1.5.2/stackinternal_test.go000066400000000000000000000021371271551741300170650ustar00rootroot00000000000000package stack import ( "runtime" "testing" ) func TestFindSigpanic(t *testing.T) { t.Parallel() sp := findSigpanic() if got, want := sp.Name(), "runtime.sigpanic"; got != want { t.Errorf("got == %v, want == %v", got, want) } } func TestCaller(t *testing.T) { t.Parallel() c := Caller(0) _, file, line, ok := runtime.Caller(0) line-- if !ok { t.Fatal("runtime.Caller(0) failed") } if got, want := c.file(), file; got != want { t.Errorf("got file == %v, want file == %v", got, want) } if got, want := c.line(), line; got != want { t.Errorf("got line == %v, want line == %v", got, want) } } type fholder struct { f func() CallStack } func (fh *fholder) labyrinth() CallStack { for { return fh.f() } panic("this line only needed for go 1.0") } func TestTrace(t *testing.T) { t.Parallel() fh := fholder{ f: func() CallStack { cs := Trace() return cs }, } cs := fh.labyrinth() lines := []int{51, 41, 56} for i, line := range lines { if got, want := cs[i].line(), line; got != want { t.Errorf("got line[%d] == %v, want line[%d] == %v", i, got, i, want) } } }