fermentord/vendor/github.com/mgutz/logxi/v1/callstack.go

262 lines
6.9 KiB
Go
Raw Normal View History

package log
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/mgutz/ansi"
)
type sourceLine struct {
lineno int
line string
}
type frameInfo struct {
filename string
lineno int
method string
context []*sourceLine
contextLines int
}
func (ci *frameInfo) readSource(contextLines int) error {
if ci.lineno == 0 || disableCallstack {
return nil
}
start := maxInt(1, ci.lineno-contextLines)
end := ci.lineno + contextLines
f, err := os.Open(ci.filename)
if err != nil {
// if we can't read a file, it means user is running this in production
disableCallstack = true
return err
}
defer f.Close()
lineno := 1
scanner := bufio.NewScanner(f)
for scanner.Scan() {
if start <= lineno && lineno <= end {
line := scanner.Text()
line = expandTabs(line, 4)
ci.context = append(ci.context, &sourceLine{lineno: lineno, line: line})
}
lineno++
}
if err := scanner.Err(); err != nil {
InternalLog.Warn("scanner error", "file", ci.filename, "err", err)
}
return nil
}
func (ci *frameInfo) String(color string, sourceColor string) string {
buf := pool.Get()
defer pool.Put(buf)
if disableCallstack {
buf.WriteString(color)
buf.WriteString(Separator)
buf.WriteString(indent)
buf.WriteString(ci.filename)
buf.WriteRune(':')
buf.WriteString(strconv.Itoa(ci.lineno))
return buf.String()
}
// skip anything in the logxi package
if isLogxiCode(ci.filename) {
return ""
}
// make path relative to current working directory or home
tildeFilename, err := filepath.Rel(wd, ci.filename)
if err != nil {
InternalLog.Warn("Could not make path relative", "path", ci.filename)
return ""
}
// ../../../ is too complex. Make path relative to home
if strings.HasPrefix(tildeFilename, strings.Repeat(".."+string(os.PathSeparator), 3)) {
tildeFilename = strings.Replace(tildeFilename, home, "~", 1)
}
buf.WriteString(color)
buf.WriteString(Separator)
buf.WriteString(indent)
buf.WriteString("in ")
buf.WriteString(ci.method)
buf.WriteString("(")
buf.WriteString(tildeFilename)
buf.WriteRune(':')
buf.WriteString(strconv.Itoa(ci.lineno))
buf.WriteString(")")
if ci.contextLines == -1 {
return buf.String()
}
buf.WriteString("\n")
// the width of the printed line number
var linenoWidth int
// trim spaces at start of source code based on common spaces
var skipSpaces = 1000
// calculate width of lineno and number of leading spaces that can be
// removed
for _, li := range ci.context {
linenoWidth = maxInt(linenoWidth, len(fmt.Sprintf("%d", li.lineno)))
index := indexOfNonSpace(li.line)
if index > -1 && index < skipSpaces {
skipSpaces = index
}
}
for _, li := range ci.context {
var format string
format = fmt.Sprintf("%%s%%%dd: %%s\n", linenoWidth)
if li.lineno == ci.lineno {
buf.WriteString(color)
if ci.contextLines > 2 {
format = fmt.Sprintf("%%s=> %%%dd: %%s\n", linenoWidth)
}
} else {
buf.WriteString(sourceColor)
if ci.contextLines > 2 {
// account for "=> "
format = fmt.Sprintf("%%s%%%dd: %%s\n", linenoWidth+3)
}
}
// trim spaces at start
idx := minInt(len(li.line), skipSpaces)
buf.WriteString(fmt.Sprintf(format, Separator+indent+indent, li.lineno, li.line[idx:]))
}
// get rid of last \n
buf.Truncate(buf.Len() - 1)
if !disableColors {
buf.WriteString(ansi.Reset)
}
return buf.String()
}
// parseDebugStack parases a stack created by debug.Stack()
//
// This is what the string looks like
// /Users/mgutz/go/src/github.com/mgutz/logxi/v1/jsonFormatter.go:45 (0x5fa70)
// (*JSONFormatter).writeError: jf.writeString(buf, err.Error()+"\n"+string(debug.Stack()))
// /Users/mgutz/go/src/github.com/mgutz/logxi/v1/jsonFormatter.go:82 (0x5fdc3)
// (*JSONFormatter).appendValue: jf.writeError(buf, err)
// /Users/mgutz/go/src/github.com/mgutz/logxi/v1/jsonFormatter.go:109 (0x605ca)
// (*JSONFormatter).set: jf.appendValue(buf, val)
// ...
// /Users/mgutz/goroot/src/runtime/asm_amd64.s:2232 (0x38bf1)
// goexit:
func parseDebugStack(stack string, skip int, ignoreRuntime bool) []*frameInfo {
frames := []*frameInfo{}
// BUG temporarily disable since there is a bug with embedded newlines
if true {
return frames
}
lines := strings.Split(stack, "\n")
for i := skip * 2; i < len(lines); i += 2 {
ci := &frameInfo{}
sourceLine := lines[i]
if sourceLine == "" {
break
}
if ignoreRuntime && strings.Contains(sourceLine, filepath.Join("src", "runtime")) {
break
}
colon := strings.Index(sourceLine, ":")
slash := strings.Index(sourceLine, "/")
if colon < slash {
// must be on Windows where paths look like c:/foo/bar.go:lineno
colon = strings.Index(sourceLine[slash:], ":") + slash
}
space := strings.Index(sourceLine, " ")
ci.filename = sourceLine[0:colon]
// BUG with callstack where the error message has embedded newlines
// if colon > space {
// fmt.Println("lines", lines)
// }
// fmt.Println("SOURCELINE", sourceLine, "len", len(sourceLine), "COLON", colon, "SPACE", space)
numstr := sourceLine[colon+1 : space]
lineno, err := strconv.Atoi(numstr)
if err != nil {
InternalLog.Warn("Could not parse line number", "sourceLine", sourceLine, "numstr", numstr)
continue
}
ci.lineno = lineno
methodLine := lines[i+1]
colon = strings.Index(methodLine, ":")
ci.method = strings.Trim(methodLine[0:colon], "\t ")
frames = append(frames, ci)
}
return frames
}
// parseDebugStack parases a stack created by debug.Stack()
//
// This is what the string looks like
// /Users/mgutz/go/src/github.com/mgutz/logxi/v1/jsonFormatter.go:45 (0x5fa70)
// (*JSONFormatter).writeError: jf.writeString(buf, err.Error()+"\n"+string(debug.Stack()))
// /Users/mgutz/go/src/github.com/mgutz/logxi/v1/jsonFormatter.go:82 (0x5fdc3)
// (*JSONFormatter).appendValue: jf.writeError(buf, err)
// /Users/mgutz/go/src/github.com/mgutz/logxi/v1/jsonFormatter.go:109 (0x605ca)
// (*JSONFormatter).set: jf.appendValue(buf, val)
// ...
// /Users/mgutz/goroot/src/runtime/asm_amd64.s:2232 (0x38bf1)
// goexit:
func trimDebugStack(stack string) string {
buf := pool.Get()
defer pool.Put(buf)
lines := strings.Split(stack, "\n")
for i := 0; i < len(lines); i += 2 {
sourceLine := lines[i]
if sourceLine == "" {
break
}
colon := strings.Index(sourceLine, ":")
slash := strings.Index(sourceLine, "/")
if colon < slash {
// must be on Windows where paths look like c:/foo/bar.go:lineno
colon = strings.Index(sourceLine[slash:], ":") + slash
}
filename := sourceLine[0:colon]
// skip anything in the logxi package
if isLogxiCode(filename) {
continue
}
buf.WriteString(sourceLine)
buf.WriteRune('\n')
buf.WriteString(lines[i+1])
buf.WriteRune('\n')
}
return buf.String()
}
func parseLogxiStack(entry map[string]interface{}, skip int, ignoreRuntime bool) []*frameInfo {
kv := entry[KeyMap.CallStack]
if kv == nil {
return nil
}
var frames []*frameInfo
if stack, ok := kv.(string); ok {
frames = parseDebugStack(stack, skip, ignoreRuntime)
}
return frames
}