mirror of
https://github.com/retailcrm/mg-transport-core.git
synced 2024-11-29 00:25:31 +03:00
251 lines
6.3 KiB
Go
251 lines
6.3 KiB
Go
// Package stacktrace contains code borrowed from the github.com/pkg/errors
|
|
package stacktrace
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const unknown = "unknown"
|
|
|
|
var (
|
|
dunno = []byte("???")
|
|
centerDot = []byte("·")
|
|
dot = []byte(".")
|
|
slash = []byte("/")
|
|
)
|
|
|
|
// Frame represents a program counter inside a stack frame.
|
|
// For historical reasons if Frame is interpreted as an uintptr
|
|
// its value represents the program counter + 1.
|
|
type Frame uintptr
|
|
|
|
// pc returns the program counter for this frame;
|
|
// multiple frames may have the same PC value.
|
|
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
|
|
|
|
// file returns the full path to the file that contains the
|
|
// function for this Frame's pc.
|
|
func (f Frame) file() string {
|
|
fn := runtime.FuncForPC(f.pc())
|
|
if fn == nil {
|
|
return unknown
|
|
}
|
|
file, _ := fn.FileLine(f.pc())
|
|
return file
|
|
}
|
|
|
|
// line returns the line number of source code of the
|
|
// function for this Frame's pc.
|
|
func (f Frame) line() int {
|
|
fn := runtime.FuncForPC(f.pc())
|
|
if fn == nil {
|
|
return 0
|
|
}
|
|
_, line := fn.FileLine(f.pc())
|
|
return line
|
|
}
|
|
|
|
// name returns the name of this function, if known.
|
|
func (f Frame) name() string {
|
|
fn := runtime.FuncForPC(f.pc())
|
|
if fn == nil {
|
|
return unknown
|
|
}
|
|
return fn.Name()
|
|
}
|
|
|
|
// Format formats the frame according to the fmt.Formatter interface.
|
|
//
|
|
// %s source file
|
|
// %d source line
|
|
// %n function name
|
|
// %v equivalent to %s:%d
|
|
//
|
|
// Format accepts flags that alter the printing of some verbs, as follows:
|
|
//
|
|
// %+s function name and path of source file relative to the compile time
|
|
// GOPATH separated by \n\t (<funcname>\n\t<path>)
|
|
// %+v equivalent to %+s:%d
|
|
func (f Frame) Format(s fmt.State, verb rune) {
|
|
switch verb {
|
|
case 's':
|
|
switch {
|
|
case s.Flag('+'):
|
|
_, _ = io.WriteString(s, f.name())
|
|
_, _ = io.WriteString(s, "\n\t")
|
|
_, _ = io.WriteString(s, f.file())
|
|
default:
|
|
_, _ = io.WriteString(s, path.Base(f.file()))
|
|
}
|
|
case 'd':
|
|
_, _ = io.WriteString(s, strconv.Itoa(f.line()))
|
|
case 'n':
|
|
_, _ = io.WriteString(s, funcname(f.name()))
|
|
case 'v':
|
|
f.Format(s, 's')
|
|
_, _ = io.WriteString(s, ":")
|
|
f.Format(s, 'd')
|
|
}
|
|
}
|
|
|
|
// MarshalText formats a stacktrace Frame as a text string. The output is the
|
|
// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs.
|
|
func (f Frame) MarshalText() ([]byte, error) {
|
|
name := f.name()
|
|
if name == unknown {
|
|
return []byte(name), nil
|
|
}
|
|
return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil
|
|
}
|
|
|
|
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
|
type StackTrace []Frame
|
|
|
|
// Format formats the stack of Frames according to the fmt.Formatter interface.
|
|
//
|
|
// %s lists source files for each Frame in the stack
|
|
// %v lists the source file and line number for each Frame in the stack
|
|
//
|
|
// Format accepts flags that alter the printing of some verbs, as follows:
|
|
//
|
|
// %+v Prints filename, function, and line number for each Frame in the stack.
|
|
func (st StackTrace) Format(s fmt.State, verb rune) {
|
|
switch verb {
|
|
case 'v':
|
|
switch {
|
|
case s.Flag('+'):
|
|
for _, f := range st {
|
|
_, _ = io.WriteString(s, "\n")
|
|
f.Format(s, verb)
|
|
}
|
|
case s.Flag('#'):
|
|
_, _ = fmt.Fprintf(s, "%#v", []Frame(st))
|
|
default:
|
|
st.formatSlice(s, verb)
|
|
}
|
|
case 's':
|
|
st.formatSlice(s, verb)
|
|
}
|
|
}
|
|
|
|
// formatSlice will format this StackTrace into the given buffer as a slice of
|
|
// Frame, only valid when called with '%s' or '%v'.
|
|
func (st StackTrace) formatSlice(s fmt.State, verb rune) {
|
|
_, _ = io.WriteString(s, "[")
|
|
for i, f := range st {
|
|
if i > 0 {
|
|
_, _ = io.WriteString(s, " ")
|
|
}
|
|
f.Format(s, verb)
|
|
}
|
|
_, _ = io.WriteString(s, "]")
|
|
}
|
|
|
|
// stack represents a stack of program counters.
|
|
type stack []uintptr
|
|
|
|
type StackTraced interface {
|
|
StackTrace() StackTrace
|
|
}
|
|
|
|
func (s *stack) Format(st fmt.State, verb rune) {
|
|
if verb == 'v' && st.Flag('+') {
|
|
for _, pc := range *s {
|
|
f := Frame(pc)
|
|
_, _ = fmt.Fprintf(st, "\n%+v", f)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *stack) StackTrace() StackTrace {
|
|
f := make([]Frame, len(*s))
|
|
for i := 0; i < len(f); i++ {
|
|
f[i] = Frame((*s)[i])
|
|
}
|
|
return f
|
|
}
|
|
|
|
// FormattedStack returns a nicely formatted stack frame, skipping skip frames.
|
|
func FormattedStack(skip int, prefix string) []byte {
|
|
buf := new(bytes.Buffer) // the returned data
|
|
// As we loop, we open files and read them. These variables record the currently
|
|
// loaded file.
|
|
var lines [][]byte
|
|
var lastFile string
|
|
for i := skip; ; i++ { // Skip the expected number of frames
|
|
pc, file, line, ok := runtime.Caller(i)
|
|
if !ok {
|
|
break
|
|
}
|
|
// Print this much at least. If we can't find the source, it won't show.
|
|
_, _ = fmt.Fprintf(buf, "%s%s:%d (0x%x)\n", prefix, file, line, pc)
|
|
if file != lastFile {
|
|
data, err := os.ReadFile(file)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
lines = bytes.Split(data, []byte{'\n'})
|
|
lastFile = file
|
|
}
|
|
_, _ = fmt.Fprintf(buf, "%s\t%s: %s\n", prefix, function(pc), source(lines, line))
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func callers(skip int) *stack {
|
|
const depth = 32
|
|
var pcs [depth]uintptr
|
|
n := runtime.Callers(skip, pcs[:])
|
|
var st stack = pcs[0:n]
|
|
return &st
|
|
}
|
|
|
|
// function returns, if possible, the name of the function containing the PC.
|
|
func function(pc uintptr) []byte {
|
|
fn := runtime.FuncForPC(pc)
|
|
if fn == nil {
|
|
return dunno
|
|
}
|
|
name := []byte(fn.Name())
|
|
// The name includes the path name to the package, which is unnecessary
|
|
// since the file name is already included. Plus, it has center dots.
|
|
// That is, we see
|
|
// runtime/debug.*T·ptrmethod
|
|
// and want
|
|
// *T.ptrmethod
|
|
// Also the package path might contains dot (e.g. code.google.com/...),
|
|
// so first eliminate the path prefix
|
|
if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 {
|
|
name = name[lastSlash+1:]
|
|
}
|
|
if period := bytes.Index(name, dot); period >= 0 {
|
|
name = name[period+1:]
|
|
}
|
|
name = bytes.ReplaceAll(name, centerDot, dot)
|
|
return name
|
|
}
|
|
|
|
// source returns a space-trimmed slice of the n'th line.
|
|
func source(lines [][]byte, n int) []byte {
|
|
n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
|
|
if n < 0 || n >= len(lines) {
|
|
return dunno
|
|
}
|
|
return bytes.TrimSpace(lines[n])
|
|
}
|
|
|
|
// funcname removes the path prefix component of a function's name reported by func.Name().
|
|
func funcname(name string) string {
|
|
i := strings.LastIndex(name, "/")
|
|
name = name[i+1:]
|
|
i = strings.Index(name, ".")
|
|
return name[i+1:]
|
|
}
|