github.com/sunvim/utils@v0.1.0/errors/stack.go (about)

     1  package errors
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"path"
     7  	"runtime"
     8  	"strings"
     9  )
    10  
    11  // Frame represents a program counter inside a stack frame.
    12  type Frame uintptr
    13  
    14  // pc returns the program counter for this frame;
    15  // multiple frames may have the same PC value.
    16  func (f Frame) pc() uintptr { return uintptr(f) - 1 }
    17  
    18  // file returns the full path to the file that contains the
    19  // function for this Frame's pc.
    20  func (f Frame) file() string {
    21  	fn := runtime.FuncForPC(f.pc())
    22  	if fn == nil {
    23  		return "unknown"
    24  	}
    25  	file, _ := fn.FileLine(f.pc())
    26  	return file
    27  }
    28  
    29  // line returns the line number of source code of the
    30  // function for this Frame's pc.
    31  func (f Frame) line() int {
    32  	fn := runtime.FuncForPC(f.pc())
    33  	if fn == nil {
    34  		return 0
    35  	}
    36  	_, line := fn.FileLine(f.pc())
    37  	return line
    38  }
    39  
    40  // Format formats the frame according to the fmt.Formatter interface.
    41  //
    42  //    %s    source file
    43  //    %d    source line
    44  //    %n    function name
    45  //    %v    equivalent to %s:%d
    46  //
    47  // Format accepts flags that alter the printing of some verbs, as follows:
    48  //
    49  //    %+s   function name and path of source file relative to the compile time
    50  //          GOPATH separated by \n\t (<funcname>\n\t<path>)
    51  //    %+v   equivalent to %+s:%d
    52  func (f Frame) Format(s fmt.State, verb rune) {
    53  	switch verb {
    54  	case 's':
    55  		switch {
    56  		case s.Flag('+'):
    57  			pc := f.pc()
    58  			fn := runtime.FuncForPC(pc)
    59  			if fn == nil {
    60  				io.WriteString(s, "unknown")
    61  			} else {
    62  				file, _ := fn.FileLine(pc)
    63  				fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
    64  			}
    65  		default:
    66  			io.WriteString(s, path.Base(f.file()))
    67  		}
    68  	case 'd':
    69  		fmt.Fprintf(s, "%d", f.line())
    70  	case 'n':
    71  		name := runtime.FuncForPC(f.pc()).Name()
    72  		io.WriteString(s, funcname(name))
    73  	case 'v':
    74  		f.Format(s, 's')
    75  		io.WriteString(s, ":")
    76  		f.Format(s, 'd')
    77  	}
    78  }
    79  
    80  // StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
    81  type StackTrace []Frame
    82  
    83  // Format formats the stack of Frames according to the fmt.Formatter interface.
    84  //
    85  //    %s	lists source files for each Frame in the stack
    86  //    %v	lists the source file and line number for each Frame in the stack
    87  //
    88  // Format accepts flags that alter the printing of some verbs, as follows:
    89  //
    90  //    %+v   Prints filename, function, and line number for each Frame in the stack.
    91  func (st StackTrace) Format(s fmt.State, verb rune) {
    92  	switch verb {
    93  	case 'v':
    94  		switch {
    95  		case s.Flag('+'):
    96  			for _, f := range st {
    97  				fmt.Fprintf(s, "\n%+v", f)
    98  			}
    99  		case s.Flag('#'):
   100  			fmt.Fprintf(s, "%#v", []Frame(st))
   101  		default:
   102  			fmt.Fprintf(s, "%v", []Frame(st))
   103  		}
   104  	case 's':
   105  		fmt.Fprintf(s, "%s", []Frame(st))
   106  	}
   107  }
   108  
   109  // stack represents a stack of program counters.
   110  type stack []uintptr
   111  
   112  func (s *stack) Format(st fmt.State, verb rune) {
   113  	switch verb {
   114  	case 'v':
   115  		switch {
   116  		case st.Flag('+'):
   117  			for _, pc := range *s {
   118  				f := Frame(pc)
   119  				fmt.Fprintf(st, "\n%+v", f)
   120  			}
   121  		}
   122  	}
   123  }
   124  
   125  func (s *stack) StackTrace() StackTrace {
   126  	f := make([]Frame, len(*s))
   127  	for i := 0; i < len(f); i++ {
   128  		f[i] = Frame((*s)[i])
   129  	}
   130  	return f
   131  }
   132  
   133  func callers() *stack {
   134  	const depth = 32
   135  	var pcs [depth]uintptr
   136  	n := runtime.Callers(3, pcs[:])
   137  	var st stack = pcs[0:n]
   138  	return &st
   139  }
   140  
   141  // funcname removes the path prefix component of a function's name reported by func.Name().
   142  func funcname(name string) string {
   143  	i := strings.LastIndex(name, "/")
   144  	name = name[i+1:]
   145  	i = strings.Index(name, ".")
   146  	return name[i+1:]
   147  }