You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
178 lines
4.3 KiB
178 lines
4.3 KiB
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 |
|
}
|
|
|