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:] }