forked from pkg/errors
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce Stacktrace and Frame (pkg#37)
* Introduce Stacktrace and Frame This PR is a continuation of a series aimed at exposing the stack trace information embedded in each error value. The secondary effect is to deprecated the `Fprintf` helper. Taking cues from from @ChrisHines' `stack` package this PR introduces a new interface `Stacktrace() []Frame` and a `Frame` type, similar in function to the `runtime.Frame` type (although lacking its iterator type). Each `Frame` implemnts `fmt.Formatter` allowing it to print itself. The older `Location` interface is still supported but also deprecated.
- Loading branch information
1 parent
f45f2b7
commit 3cdd332
Showing
6 changed files
with
367 additions
and
81 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package errors | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"path" | ||
"runtime" | ||
"strings" | ||
) | ||
|
||
// Frame repesents an activation record. | ||
type Frame uintptr | ||
|
||
// pc returns the program counter for this frame; | ||
// multiple frames may have the same PC value. | ||
func (f Frame) pc() uintptr { return uintptr(f) - 1 } | ||
|
||
// file returns the full path to the file that contains the | ||
// function for this Frame's pc. | ||
func (f Frame) file() string { | ||
fn := runtime.FuncForPC(f.pc()) | ||
if fn == nil { | ||
return "unknown" | ||
} | ||
file, _ := fn.FileLine(f.pc()) | ||
return file | ||
} | ||
|
||
// line returns the line number of source code of the | ||
// function for this Frame's pc. | ||
func (f Frame) line() int { | ||
fn := runtime.FuncForPC(f.pc()) | ||
if fn == nil { | ||
return 0 | ||
} | ||
_, line := fn.FileLine(f.pc()) | ||
return line | ||
} | ||
|
||
// Format formats the frame according to the fmt.Formatter interface. | ||
// | ||
// %s source file | ||
// %d source line | ||
// %n function name | ||
// %v equivalent to %s:%d | ||
// | ||
// Format accepts flags that alter the printing of some verbs, as follows: | ||
// | ||
// %+s path of source file relative to the compile time GOPATH | ||
// %+v equivalent to %+s:%d | ||
func (f Frame) Format(s fmt.State, verb rune) { | ||
switch verb { | ||
case 's': | ||
switch { | ||
case s.Flag('+'): | ||
pc := f.pc() | ||
fn := runtime.FuncForPC(pc) | ||
if fn == nil { | ||
io.WriteString(s, "unknown") | ||
} else { | ||
file, _ := fn.FileLine(pc) | ||
io.WriteString(s, trimGOPATH(fn.Name(), file)) | ||
} | ||
default: | ||
io.WriteString(s, path.Base(f.file())) | ||
} | ||
case 'd': | ||
fmt.Fprintf(s, "%d", f.line()) | ||
case 'n': | ||
name := runtime.FuncForPC(f.pc()).Name() | ||
i := strings.LastIndex(name, "/") | ||
name = name[i+1:] | ||
i = strings.Index(name, ".") | ||
io.WriteString(s, name[i+1:]) | ||
case 'v': | ||
f.Format(s, 's') | ||
io.WriteString(s, ":") | ||
f.Format(s, 'd') | ||
} | ||
} | ||
|
||
// stack represents a stack of program counters. | ||
type stack []uintptr | ||
|
||
// Deprecated: use Stacktrace() | ||
func (s *stack) Stack() []uintptr { return *s } | ||
|
||
// Deprecated: use Stacktrace()[0] | ||
func (s *stack) Location() (string, int) { | ||
frame := s.Stacktrace()[0] | ||
return fmt.Sprintf("%+s", frame), frame.line() | ||
} | ||
|
||
func (s *stack) Stacktrace() []Frame { | ||
f := make([]Frame, len(*s)) | ||
for i := 0; i < len(f); i++ { | ||
f[i] = Frame((*s)[i]) | ||
} | ||
return f | ||
} | ||
|
||
func callers() *stack { | ||
const depth = 32 | ||
var pcs [depth]uintptr | ||
n := runtime.Callers(3, pcs[:]) | ||
var st stack = pcs[0:n] | ||
return &st | ||
} | ||
|
||
func trimGOPATH(name, file string) string { | ||
// Here we want to get the source file path relative to the compile time | ||
// GOPATH. As of Go 1.6.x there is no direct way to know the compiled | ||
// GOPATH at runtime, but we can infer the number of path segments in the | ||
// GOPATH. We note that fn.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: | ||
// | ||
// pkg/sub/file.go | ||
// | ||
// From this we can easily see that fn.Name() has one less path separator | ||
// than our desired output. 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 = "/" | ||
goal := strings.Count(name, sep) + 2 | ||
i := len(file) | ||
for n := 0; n < goal; n++ { | ||
i = strings.LastIndex(file[:i], sep) | ||
if i == -1 { | ||
// not enough separators found, set i so that the slice expression | ||
// below leaves file unmodified | ||
i = -len(sep) | ||
break | ||
} | ||
} | ||
// get back to 0 or trim the leading separator | ||
file = file[i+len(sep):] | ||
return file | ||
} |
Oops, something went wrong.