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