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 }