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  }