trpc.group/trpc-go/trpc-go@v1.0.3/errs/stack.go (about)

     1  //
     2  //
     3  // Tencent is pleased to support the open source community by making tRPC available.
     4  //
     5  // Copyright (C) 2023 THL A29 Limited, a Tencent company.
     6  // All rights reserved.
     7  //
     8  // If you have downloaded a copy of the tRPC source code from Tencent,
     9  // please note that tRPC source code is licensed under the  Apache 2.0 License,
    10  // A copy of the Apache 2.0 License is included in this file.
    11  //
    12  //
    13  
    14  // Package errs provides trpc error code type, which contains errcode errmsg.
    15  // These definitions are multi-language universal.
    16  package errs
    17  
    18  import (
    19  	"fmt"
    20  	"io"
    21  	"path"
    22  	"runtime"
    23  	"strconv"
    24  	"strings"
    25  )
    26  
    27  var (
    28  	traceable bool               // if traceable is true, the error has a stack trace .
    29  	content   string             // if content is not empty, only print stack information contains it.
    30  	stackSkip = defaultStackSkip // number of stack frames skipped.
    31  )
    32  
    33  const (
    34  	defaultStackSkip = 3
    35  )
    36  
    37  // SetTraceable controls whether the error has a stack trace.
    38  func SetTraceable(x bool) {
    39  	traceable = x
    40  }
    41  
    42  // SetTraceableWithContent controls whether the error has a stack trace.
    43  // When printing the stack information, filter according to the content.
    44  // Avoid outputting a lot of useless information. The stack information
    45  // of other plugins can be filtered out by configuring content as the service name.
    46  func SetTraceableWithContent(c string) {
    47  	traceable = true
    48  	content = c
    49  }
    50  
    51  // SetStackSkip supports setting the number of skipped stack frames.
    52  // When encapsulating the New method, you can set stackSkip to 4 (determined
    53  // according to the number of encapsulation layers)
    54  // This function is used to set before the project starts and does not guarantee concurrency safety.
    55  func SetStackSkip(skip int) {
    56  	stackSkip = skip
    57  }
    58  
    59  func isOutput(str string) bool {
    60  	return strings.Contains(str, content)
    61  }
    62  
    63  // frame represents a program counter inside a stack frame.
    64  // For historical reasons if frame is interpreted as a uintptr
    65  // its value represents the program counter + 1.
    66  type frame uintptr
    67  
    68  // pc returns the program counter for this frame;
    69  // multiple frames may have the same PC value.
    70  func (f frame) pc() uintptr { return uintptr(f) - 1 }
    71  
    72  // file returns the full path to the file that contains the
    73  // function for this frame's pc.
    74  func (f frame) file() string {
    75  	fn := runtime.FuncForPC(f.pc())
    76  	if fn == nil {
    77  		return "unknown"
    78  	}
    79  	file, _ := fn.FileLine(f.pc())
    80  	return file
    81  }
    82  
    83  // line returns the line number of source code of the
    84  // function for this frame's pc.
    85  func (f frame) line() int {
    86  	fn := runtime.FuncForPC(f.pc())
    87  	if fn == nil {
    88  		return 0
    89  	}
    90  	_, line := fn.FileLine(f.pc())
    91  	return line
    92  }
    93  
    94  // name returns the name of this function, if known.
    95  func (f frame) name() string {
    96  	fn := runtime.FuncForPC(f.pc())
    97  	if fn == nil {
    98  		return "unknown"
    99  	}
   100  	return fn.Name()
   101  }
   102  
   103  // Format formats the frame according to the fmt.Formatter interface.
   104  //
   105  //	%s    source file
   106  //	%d    source line
   107  //	%n    function name
   108  //	%v    equivalent to %s:%d
   109  //
   110  // Format accepts flags that alter the printing of some verbs, as follows:
   111  //
   112  //	%+s   function name and path of source file relative to the compile time
   113  //	      GOPATH separated by \n\t (<funcName>\n\t<path>)
   114  //	%+v   equivalent to %+s:%d
   115  func (f frame) Format(s fmt.State, verb rune) {
   116  	switch verb {
   117  	case 's':
   118  		switch {
   119  		case s.Flag('+'):
   120  			io.WriteString(s, f.name())
   121  			io.WriteString(s, "\n\t")
   122  			io.WriteString(s, f.file())
   123  		default:
   124  			io.WriteString(s, path.Base(f.file()))
   125  		}
   126  	case 'd':
   127  		io.WriteString(s, strconv.Itoa(f.line()))
   128  	case 'n':
   129  		io.WriteString(s, funcName(f.name()))
   130  	case 'v':
   131  		f.Format(s, 's')
   132  		io.WriteString(s, ":")
   133  		f.Format(s, 'd')
   134  	}
   135  }
   136  
   137  // stackTrace is stack of Frames from innermost (newest) to outermost (oldest).
   138  type stackTrace []frame
   139  
   140  // Format formats the stack of Frames according to the fmt.Formatter interface.
   141  //
   142  //	%s	lists source files for each frame in the stack
   143  //	%v	lists the source file and line number for each frame in the stack
   144  //
   145  // Format accepts flags that alter the printing of some verbs, as follows:
   146  //
   147  //	%+v   Prints filename, function, and line number for each frame in the stack.
   148  func (st stackTrace) Format(s fmt.State, verb rune) {
   149  	switch verb {
   150  	case 'v':
   151  		switch {
   152  		case s.Flag('+'):
   153  			for _, f := range st {
   154  				// filter, only print stack information contains the content.
   155  				if !isOutput(fmt.Sprintf("%+v", f)) {
   156  					continue
   157  				}
   158  				io.WriteString(s, "\n")
   159  				f.Format(s, verb)
   160  			}
   161  		case s.Flag('#'):
   162  			fmt.Fprintf(s, "%#v", []frame(st))
   163  		default:
   164  			st.formatSlice(s, verb)
   165  		}
   166  	case 's':
   167  		st.formatSlice(s, verb)
   168  	}
   169  }
   170  
   171  // formatSlice will format this stackTrace into the given buffer as a slice of
   172  // frame, only valid when called with '%s' or '%v'.
   173  func (st stackTrace) formatSlice(s fmt.State, verb rune) {
   174  	io.WriteString(s, "[")
   175  	for i, f := range st {
   176  		if i > 0 {
   177  			io.WriteString(s, " ")
   178  		}
   179  		f.Format(s, verb)
   180  	}
   181  	io.WriteString(s, "]")
   182  }
   183  
   184  func callers() stackTrace {
   185  	const depth = 32
   186  	var pcs [depth]uintptr
   187  	n := runtime.Callers(stackSkip, pcs[:])
   188  	stack := pcs[0:n]
   189  	// convert to errors.stackTrace
   190  	st := make([]frame, len(stack))
   191  	for i := 0; i < len(st); i++ {
   192  		st[i] = frame((stack)[i])
   193  	}
   194  	return st
   195  }
   196  
   197  // funcName removes the path prefix component of a function's name reported by func.Name().
   198  func funcName(name string) string {
   199  	i := strings.LastIndex(name, "/")
   200  	name = name[i+1:]
   201  	i = strings.Index(name, ".")
   202  	return name[i+1:]
   203  }