vitess.io/vitess@v0.16.2/go/tb/error.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package tb exposes some handy traceback functionality buried in the runtime. 18 // 19 // It can also be used to provide context to errors reducing the temptation to 20 // panic carelessly, just to get stack information. 21 // 22 // The theory is that most errors that are created with the fmt.Errorf 23 // style are likely to be rare, but require more context to debug 24 // properly. The additional cost of computing a stack trace is 25 // therefore negligible. 26 package tb 27 28 import ( 29 "bytes" 30 "fmt" 31 "os" 32 "runtime" 33 ) 34 35 var ( 36 dunno = []byte("???") 37 centerDot = []byte("·") 38 dot = []byte(".") 39 ) 40 41 // StackError represents an error along with a stack trace. 42 type StackError interface { 43 Error() string 44 StackTrace() string 45 } 46 47 type stackError struct { 48 err error 49 stackTrace string 50 } 51 52 func (e stackError) Error() string { 53 return fmt.Sprintf("%v\n%v", e.err, e.stackTrace) 54 } 55 56 func (e stackError) StackTrace() string { 57 return e.stackTrace 58 } 59 60 func Errorf(msg string, args ...any) error { 61 stack := "" 62 // See if any arg is already embedding a stack - no need to 63 // recompute something expensive and make the message unreadable. 64 for _, arg := range args { 65 if stackErr, ok := arg.(stackError); ok { 66 stack = stackErr.stackTrace 67 break 68 } 69 } 70 71 if stack == "" { 72 // magic 5 trims off just enough stack data to be clear 73 stack = string(Stack(5)) 74 } 75 76 return stackError{fmt.Errorf(msg, args...), stack} 77 } 78 79 // Stack is taken from runtime/debug.go 80 // calldepth is the number of (bottommost) frames to skip. 81 func Stack(calldepth int) []byte { 82 return stack(calldepth) 83 } 84 85 func stack(calldepth int) []byte { 86 buf := new(bytes.Buffer) // the returned data 87 // As we loop, we open files and read them. These variables record the currently 88 // loaded file. 89 var lines [][]byte 90 var lastFile string 91 for i := calldepth; ; i++ { // Caller we care about is the user, 2 frames up 92 pc, file, line, ok := runtime.Caller(i) 93 if !ok { 94 break 95 } 96 // Print this much at least. If we can't find the source, it won't show. 97 fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) 98 if file != lastFile { 99 data, err := os.ReadFile(file) 100 if err != nil { 101 continue 102 } 103 lines = bytes.Split(data, []byte{'\n'}) 104 lastFile = file 105 } 106 line-- // in stack trace, lines are 1-indexed but our array is 0-indexed 107 fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line)) 108 } 109 return buf.Bytes() 110 } 111 112 // source returns a space-trimmed slice of the n'th line. 113 func source(lines [][]byte, n int) []byte { 114 if n < 0 || n >= len(lines) { 115 return dunno 116 } 117 return bytes.Trim(lines[n], " \t") 118 } 119 120 // function returns, if possible, the name of the function containing the PC. 121 func function(pc uintptr) []byte { 122 fn := runtime.FuncForPC(pc) 123 if fn == nil { 124 return dunno 125 } 126 name := []byte(fn.Name()) 127 // The name includes the path name to the package, which is unnecessary 128 // since the file name is already included. Plus, it has center dots. 129 // That is, we see 130 // runtime/debug.*T·ptrmethod 131 // and want 132 // *T.ptrmethod 133 if period := bytes.Index(name, dot); period >= 0 { 134 name = name[period+1:] 135 } 136 name = bytes.Replace(name, centerDot, dot, -1) 137 return name 138 }