-
Notifications
You must be signed in to change notification settings - Fork 715
Introduce Stacktrace and Frame #37
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, that is true, I'm leaning on the Can you think of a way we could write a test case for this ? We might have to extract the body of
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add this test to }, {
Frame(0),
"%+s",
"unknown",
}, {It will fail. Fix it for TDD points.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| 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 | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
%+v is also supported because
case 'v'recursively callsf.Format(s, 's').There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't want to add it til I had written a test case for it (I try to TDD where I can). As you say, it'll probably work because of the way I wrote
%vThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added test and docs.