| package errors |
| |
| import ( |
| "fmt" |
| "io" |
| "path" |
| "runtime" |
| "strings" |
| ) |
| |
| // Frame represents a program counter inside a stack frame. |
| 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) |
| fmt.Fprintf(s, "%s\n\t%s", 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() |
| io.WriteString(s, funcname(name)) |
| case 'v': |
| f.Format(s, 's') |
| io.WriteString(s, ":") |
| f.Format(s, 'd') |
| } |
| } |
| |
| // StackTrace is stack of Frames from innermost (newest) to outermost (oldest). |
| type StackTrace []Frame |
| |
| func (st StackTrace) Format(s fmt.State, verb rune) { |
| switch verb { |
| case 'v': |
| switch { |
| case s.Flag('+'): |
| for _, f := range st { |
| fmt.Fprintf(s, "\n%+v", f) |
| } |
| case s.Flag('#'): |
| fmt.Fprintf(s, "%#v", []Frame(st)) |
| default: |
| fmt.Fprintf(s, "%v", []Frame(st)) |
| } |
| case 's': |
| fmt.Fprintf(s, "%s", []Frame(st)) |
| } |
| } |
| |
| // stack represents a stack of program counters. |
| type stack []uintptr |
| |
| func (s *stack) Format(st fmt.State, verb rune) { |
| switch verb { |
| case 'v': |
| switch { |
| case st.Flag('+'): |
| for _, pc := range *s { |
| f := Frame(pc) |
| fmt.Fprintf(st, "\n%+v", f) |
| } |
| } |
| } |
| } |
| |
| func (s *stack) StackTrace() StackTrace { |
| 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 |
| } |
| |
| // funcname removes the path prefix component of a function's name reported by func.Name(). |
| func funcname(name string) string { |
| i := strings.LastIndex(name, "/") |
| name = name[i+1:] |
| i = strings.Index(name, ".") |
| return name[i+1:] |
| } |
| |
| 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 |
| } |