|
@@ -0,0 +1,174 @@
|
|
|
|
+package errors
|
|
|
|
+
|
|
|
|
+import (
|
|
|
|
+ "fmt"
|
|
|
|
+ "io"
|
|
|
|
+ "path"
|
|
|
|
+ "runtime"
|
|
|
|
+ "strconv"
|
|
|
|
+ "strings"
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+// tip: 计算机中关于程序计数器,不要理解成是程序的执行次数,而是指向某个位置的下标,或者说是一个地址。
|
|
|
|
+
|
|
|
|
+// Frame 调用栈程序计数器,用来表示栈指针。
|
|
|
|
+type Frame uintptr
|
|
|
|
+
|
|
|
|
+// pc 返回程序计数器。我们会是用栈指针来获取栈帧对应函数的文件名 行号等信息
|
|
|
|
+// runtime.Caller 可以返回函数栈某一层的程序计数器,0表示函数本身,1表示调用者
|
|
|
|
+// runtime.Callers 可以返回某个调用链的所有程序计数器,由于历史原因,1表示函数本身,和上面的0相同。所以在这里做-1操作
|
|
|
|
+func (f Frame) pc() uintptr {
|
|
|
|
+ return uintptr(f) - 1
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// file 获取当前栈帧对应函数所在的文件名
|
|
|
|
+func (f Frame) file() string {
|
|
|
|
+ // runtime.FuncForPC 函数可以把程序计数器地址对应的函数信息获取出来
|
|
|
|
+ fn := runtime.FuncForPC(f.pc())
|
|
|
|
+ if fn == nil {
|
|
|
|
+ return "unknown"
|
|
|
|
+ }
|
|
|
|
+ file, _ := fn.FileLine(f.pc())
|
|
|
|
+ return file
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// line 获取当前栈帧对应函数所在的行号
|
|
|
|
+func (f Frame) line() int {
|
|
|
|
+ fn := runtime.FuncForPC(f.pc())
|
|
|
|
+ if fn == nil {
|
|
|
|
+ return 0
|
|
|
|
+ }
|
|
|
|
+ _, line := fn.FileLine(f.pc())
|
|
|
|
+ return line
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// name 获取当前栈帧对应函数的函数名称
|
|
|
|
+func (f Frame) name() string {
|
|
|
|
+ fn := runtime.FuncForPC(f.pc())
|
|
|
|
+ if fn == nil {
|
|
|
|
+ return "unknown"
|
|
|
|
+ }
|
|
|
|
+ return fn.Name()
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Format 栈帧对应的函数进行格式化输出。
|
|
|
|
+// 实现 fmt.Formatter 接口用来支持fmt包格式化输出
|
|
|
|
+// %s 只打印文件名
|
|
|
|
+// %+s 打印文件完整路径和名称
|
|
|
|
+// %d 只打印行号
|
|
|
|
+// %n 只打印函数名称
|
|
|
|
+// %v %s:%d
|
|
|
|
+// %+v %+s:%d
|
|
|
|
+func (f Frame) Format(s fmt.State, verb rune) {
|
|
|
|
+ switch verb {
|
|
|
|
+ case 's':
|
|
|
|
+ switch {
|
|
|
|
+ case s.Flag('+'):
|
|
|
|
+ _, _ = io.WriteString(s, f.name())
|
|
|
|
+ _, _ = io.WriteString(s, "\n\t")
|
|
|
|
+ _, _ = io.WriteString(s, f.file())
|
|
|
|
+ default:
|
|
|
|
+ _, _ = io.WriteString(s, path.Base(f.file()))
|
|
|
|
+ }
|
|
|
|
+ case 'd':
|
|
|
|
+ _, _ = io.WriteString(s, strconv.Itoa(f.line()))
|
|
|
|
+ case 'n':
|
|
|
|
+ _, _ = io.WriteString(s, f.name())
|
|
|
|
+ case 'v':
|
|
|
|
+ f.Format(s, 's')
|
|
|
|
+ _, _ = io.WriteString(s, ":")
|
|
|
|
+ f.Format(s, 'd')
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// MarshalText 序列化为字符串输出
|
|
|
|
+// 实现 encoding.TextMarshaler 接口
|
|
|
|
+func (f Frame) MarshalText() ([]byte, error) {
|
|
|
|
+ name := f.name()
|
|
|
|
+ if name == "unknown" {
|
|
|
|
+ return []byte(name), nil
|
|
|
|
+ }
|
|
|
|
+ return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// StackTrace 堆栈结构
|
|
|
|
+type StackTrace []Frame
|
|
|
|
+
|
|
|
|
+// Format 整个调用栈的格式化输出
|
|
|
|
+// 实现 fmt.Formatter 接口用来支持fmt包格式化输出
|
|
|
|
+// %s 堆栈输出格式为数组字符串
|
|
|
|
+// %v 堆栈输出格式为数组字符串
|
|
|
|
+// %+v 输出堆栈中每个栈帧的为文件名 函数名 行号,使用换行分隔。
|
|
|
|
+func (st StackTrace) Format(s fmt.State, verb rune) {
|
|
|
|
+ switch verb {
|
|
|
|
+ case 'v':
|
|
|
|
+ switch {
|
|
|
|
+ case s.Flag('+'):
|
|
|
|
+ for _, f := range st {
|
|
|
|
+ _, _ = io.WriteString(s, "\n")
|
|
|
|
+ f.Format(s, verb)
|
|
|
|
+ }
|
|
|
|
+ case s.Flag('#'):
|
|
|
|
+ fmt.Fprintf(s, "%#v", []Frame(st))
|
|
|
|
+ default:
|
|
|
|
+ st.formatSlice(s, verb)
|
|
|
|
+ }
|
|
|
|
+ case 's':
|
|
|
|
+ st.formatSlice(s, verb)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// formatSlice 将堆栈转换为数组字符串格式输出
|
|
|
|
+func (st StackTrace) formatSlice(s fmt.State, verb rune) {
|
|
|
|
+ _, _ = io.WriteString(s, "[")
|
|
|
|
+ for i, f := range st {
|
|
|
|
+ if i > 0 {
|
|
|
|
+ _, _ = io.WriteString(s, " ")
|
|
|
|
+ }
|
|
|
|
+ f.Format(s, verb)
|
|
|
|
+ }
|
|
|
|
+ _, _ = io.WriteString(s, "]")
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// stack 程序计数器的堆栈
|
|
|
|
+type stack []uintptr
|
|
|
|
+
|
|
|
|
+// Format 直接格式化输出
|
|
|
|
+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)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// StackTrace 转换为封装好的堆栈信息 StackTrace
|
|
|
|
+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 获取函数名。去除路径和包前缀
|
|
|
|
+func funcname(name string) string {
|
|
|
|
+ i := strings.LastIndex(name, "/")
|
|
|
|
+ name = name[i+1:]
|
|
|
|
+ i = strings.Index(name, ".")
|
|
|
|
+ return name[i+1:]
|
|
|
|
+}
|