github.com/mitranim/gg@v0.1.17/trace.go (about) 1 package gg 2 3 import ( 4 rt "runtime" 5 "strings" 6 ) 7 8 // These variables control how stack traces are printed. 9 var ( 10 TraceTable = true 11 TraceSkipLang = true 12 TraceShortName = true 13 TraceBaseDir = `` // Set to `Cwd()` for better traces. 14 ) 15 16 // Free cast of `~[]~uintptr` to `Trace`. 17 func ToTrace[Slice ~[]Val, Val ~uintptr](src Slice) Trace { 18 return CastUnsafe[Trace](src) 19 } 20 21 /* 22 Shortcut for capturing a trace of the the current call stack, skipping N frames 23 where 1 corresponds to the caller's frame. 24 */ 25 func CaptureTrace(skip int) Trace { return make(Trace, 64).Capture(skip + 1) } 26 27 /* 28 Alias of `[]uintptr` with various methods for capturing and printing 29 stack traces. 30 */ 31 type Trace []Caller 32 33 /* 34 Uses `runtime.Callers` to capture the current call stack into the given `Trace`, 35 which must have enough capacity. The returned slice is truncated. 36 */ 37 func (self Trace) Capture(skip int) Trace { 38 return self[:rt.Callers(skip+2, self.Prim())] 39 } 40 41 /* 42 Returns a multi-line text representation of the trace, with no leading 43 indentation. See `.AppendIndent`. 44 */ 45 func (self Trace) String() string { return AppenderString(self) } 46 47 /* 48 Appends a multi-line text representation of the trace, with no leading 49 indentation. See `.AppendIndent`. 50 */ 51 func (self Trace) AppendTo(buf []byte) []byte { return self.AppendIndent(buf, 0) } 52 53 /* 54 Returns a multi-line text representation of the trace with the given leading 55 indentation. See `.AppendIndent`. 56 */ 57 func (self Trace) StringIndent(lvl int) string { 58 return ToString(self.AppendIndent(nil, lvl)) 59 } 60 61 /* 62 Appends a multi-line text representation of the trace, with the given leading 63 indentation. Used internally by other trace printing methods. Affected by the 64 various "Trace*" variables. If `TraceTable` is true, the trace is formatted as 65 a table, where each frame takes only one line, and names are aligned. 66 Otherwise, the trace is formatted similarly to the default representation used 67 by the Go runtime. 68 */ 69 func (self Trace) AppendIndent(buf []byte, lvl int) []byte { 70 if TraceTable { 71 return self.AppendIndentTable(buf, lvl) 72 } 73 return self.AppendIndentMulti(buf, lvl) 74 } 75 76 /* 77 Appends a table-style representation of the trace. Used internally by 78 `.AppendIndent` if `TraceTable` is true. 79 */ 80 func (self Trace) AppendIndentTable(buf []byte, lvl int) []byte { 81 return self.Frames().AppendIndentTable(buf, lvl) 82 } 83 84 /* 85 Appends a representation of the trace similar to the default used by the Go 86 runtime. Used internally by `.AppendIndent` if `TraceTable` is false. 87 */ 88 func (self Trace) AppendIndentMulti(buf []byte, lvl int) []byte { 89 for _, val := range self { 90 buf = val.AppendNewlineIndent(buf, lvl) 91 } 92 return buf 93 } 94 95 /* 96 Returns a table-style representation of the trace with the given leading 97 indentation. 98 */ 99 func (self Trace) TableIndent(lvl int) string { 100 return ToString(self.AppendIndentTable(nil, lvl)) 101 } 102 103 /* 104 Returns a table-style representation of the trace with no leading indentation. 105 */ 106 func (self Trace) Table() string { return self.TableIndent(0) } 107 108 // True if there are any non-zero frames. 109 func (self Trace) IsNotEmpty() bool { return Some(self, IsNotZero[Caller]) } 110 111 // Converts to `Frames`, which is used for formatting. 112 func (self Trace) Frames() Frames { return Map(self, Caller.Frame) } 113 114 /* 115 Free cast to the underlying type. Useful for `runtime.Callers` and for 116 implementing `StackTraced` in error types. 117 */ 118 func (self Trace) Prim() []uintptr { return CastUnsafe[[]uintptr](self) } 119 120 // Represents an entry in a call stack. Used for formatting. 121 type Caller uintptr 122 123 // Short for "program counter". 124 func (self Caller) Pc() uintptr { 125 if IsZero(self) { 126 return 0 127 } 128 // For historic reasons. 129 return uintptr(self) - 1 130 } 131 132 // Uses `runtime.FuncForPC` to return the function corresponding to this frame. 133 func (self Caller) Func() *rt.Func { 134 if IsZero(self) { 135 return nil 136 } 137 return rt.FuncForPC(self.Pc()) 138 } 139 140 // Converts to `Frame`, which is used for formatting. 141 func (self Caller) Frame() (out Frame) { 142 out.Init(self) 143 return 144 } 145 146 /* 147 Returns a single-line representation of the frame that includes function name, 148 file path, and row. 149 */ 150 func (self Caller) String() string { return AppenderString(self) } 151 152 func (self Caller) AppendTo(buf []byte) []byte { 153 return self.Frame().AppendTo(buf) 154 } 155 156 func (self Caller) AppendIndent(buf []byte, lvl int) []byte { 157 return self.Frame().AppendIndent(buf, lvl) 158 } 159 160 func (self Caller) AppendNewlineIndent(buf []byte, lvl int) []byte { 161 return self.Frame().AppendNewlineIndent(buf, lvl) 162 } 163 164 // Used for stack trace formatting. 165 type Frames []Frame 166 167 func (self Frames) NameWidth() int { 168 var out int 169 for _, val := range self { 170 if !val.Skip() { 171 out = MaxPrim2(out, len(val.NameShort())) 172 } 173 } 174 return out 175 } 176 177 func (self Frames) AppendIndentTable(buf []byte, lvl int) []byte { 178 wid := self.NameWidth() 179 for _, val := range self { 180 buf = val.AppendRowIndent(buf, lvl, wid) 181 } 182 return buf 183 } 184 185 // Represents a stack frame. Generated by `Caller`. Used for formatting. 186 type Frame struct { 187 Caller Caller 188 Func *rt.Func 189 Name string 190 File string 191 Line int 192 } 193 194 // True if the frame has a known associated function. 195 func (self Frame) IsValid() bool { return self.Func != nil } 196 197 func (self *Frame) Init(val Caller) { 198 self.Caller = val 199 200 fun := val.Func() 201 self.Func = fun 202 203 if fun != nil { 204 self.Name = FuncNameBase(fun) 205 self.File, self.Line = fun.FileLine(val.Pc()) 206 } 207 } 208 209 /* 210 Returns a single-line representation of the frame that includes function name, 211 file path, and row. 212 */ 213 func (self Frame) String() string { return AppenderString(self) } 214 215 /* 216 Appends a single-line representation of the frame that includes function name, 217 file path, and row. 218 */ 219 func (self Frame) AppendTo(inout []byte) []byte { 220 buf := Buf(inout) 221 if self.Skip() { 222 return buf 223 } 224 225 buf.AppendString(self.NameShort()) 226 buf.AppendSpace() 227 buf.AppendString(self.Path()) 228 buf.AppendString(`:`) 229 buf.AppendInt(self.Line) 230 return buf 231 } 232 233 func (self Frame) AppendIndent(inout []byte, lvl int) []byte { 234 buf := Buf(inout) 235 if self.Skip() { 236 return buf 237 } 238 239 buf.AppendString(self.NameShort()) 240 buf.AppendNewline() 241 buf.AppendIndents(lvl) 242 buf.AppendString(self.Path()) 243 buf.AppendString(`:`) 244 buf.AppendInt(self.Line) 245 return buf 246 } 247 248 func (self Frame) AppendNewlineIndent(inout []byte, lvl int) []byte { 249 buf := Buf(inout) 250 if self.Skip() { 251 return buf 252 } 253 254 buf.AppendNewline() 255 buf.AppendIndents(lvl) 256 return self.AppendIndent(buf, lvl+1) 257 } 258 259 func (self Frame) AppendRowIndent(inout []byte, lvl, wid int) []byte { 260 buf := Buf(inout) 261 if self.Skip() { 262 return buf 263 } 264 265 name := self.NameShort() 266 267 buf.AppendNewline() 268 buf.AppendIndents(lvl) 269 buf.AppendString(name) 270 buf.AppendSpace() 271 buf.AppendSpaces(wid - len(name)) 272 buf.AppendString(self.Path()) 273 buf.AppendString(`:`) 274 buf.AppendInt(self.Line) 275 return buf 276 } 277 278 /* 279 True if the frame should not be displayed, either because it's invalid, or 280 because `TraceSkipLang` is set and the frame represents a "language" frame 281 which is mostly not useful for debugging app code. 282 */ 283 func (self *Frame) Skip() bool { 284 return !self.IsValid() || (TraceSkipLang && self.IsLang()) 285 } 286 287 /* 288 True if the frame represents a "language" frame which is mostly not useful for 289 debugging app code. 290 */ 291 func (self *Frame) IsLang() bool { 292 pkg := self.Pkg() 293 return pkg == `runtime` || pkg == `testing` 294 } 295 296 // Returns the package name of the given frame. 297 func (self *Frame) Pkg() string { 298 name := self.Name 299 ind := strings.IndexByte(name, '.') 300 if ind >= 0 { 301 return name[:ind] 302 } 303 return name 304 } 305 306 func (self *Frame) NameShort() string { 307 if TraceShortName { 308 return FuncNameShort(self.Name) 309 } 310 return self.Name 311 } 312 313 func (self *Frame) Path() string { 314 if TraceBaseDir != `` { 315 return relOpt(TraceBaseDir, self.File) 316 } 317 return self.File 318 }