From 55e8dbe9fd2923a5babab454938608060bb5396c Mon Sep 17 00:00:00 2001 From: John Guo Date: Thu, 13 Oct 2022 19:12:01 +0800 Subject: [PATCH] remove repeated error stack file lines among stacks for package gerror (#2199) * remove repeated error stack file lines among stacks for package gerror * fix nil pointer error for package gerror --- errors/gerror/gerror_error_format.go | 49 --------- errors/gerror/gerror_error_stack.go | 145 +++++++++++++++++++++++++-- 2 files changed, 137 insertions(+), 57 deletions(-) diff --git a/errors/gerror/gerror_error_format.go b/errors/gerror/gerror_error_format.go index 2705b11b5..cb8a5fcb4 100644 --- a/errors/gerror/gerror_error_format.go +++ b/errors/gerror/gerror_error_format.go @@ -7,13 +7,8 @@ package gerror import ( - "bytes" "fmt" "io" - "runtime" - "strings" - - "github.com/gogf/gf/v2/internal/consts" ) // Format formats the frame according to the fmt.Formatter interface. @@ -43,47 +38,3 @@ func (err *Error) Format(s fmt.State, verb rune) { } } } - -// formatSubStack formats the stack for error. -func formatSubStack(st stack, buffer *bytes.Buffer) { - if st == nil { - return - } - index := 1 - space := " " - for _, p := range st { - if fn := runtime.FuncForPC(p - 1); fn != nil { - file, line := fn.FileLine(p - 1) - if isUsingBriefStack { - // filter whole GoFrame packages stack paths. - if strings.Contains(file, consts.StackFilterKeyForGoFrame) { - continue - } - } else { - // package path stack filtering. - if strings.Contains(file, stackFilterKeyLocal) { - continue - } - } - // Avoid stack string like "`autogenerated`" - if strings.Contains(file, "<") { - continue - } - // Ignore GO ROOT paths. - if goRootForFilter != "" && - len(file) >= len(goRootForFilter) && - file[0:len(goRootForFilter)] == goRootForFilter { - continue - } - // Graceful indent. - if index > 9 { - space = " " - } - buffer.WriteString(fmt.Sprintf( - " %d).%s%s\n \t%s:%d\n", - index, space, fn.Name(), file, line, - )) - index++ - } - } -} diff --git a/errors/gerror/gerror_error_stack.go b/errors/gerror/gerror_error_stack.go index 72769ca23..598d8cacb 100644 --- a/errors/gerror/gerror_error_stack.go +++ b/errors/gerror/gerror_error_stack.go @@ -8,29 +8,53 @@ package gerror import ( "bytes" + "container/list" "fmt" + "runtime" + "strings" + + "github.com/gogf/gf/v2/internal/consts" ) -// Stack returns the stack callers as string. -// It returns an empty string if the `err` does not support stacks. +// stackInfo manages stack info of certain error. +type stackInfo struct { + Index int // Index is the index of current error in whole error stacks. + Message string // Error information string. + Lines *list.List // Lines contains all error stack lines of current error stack in sequence. +} + +// stackLine manages each line info of stack. +type stackLine struct { + Function string // Function name, which contains its full package path. + FileLine string // FileLine is the source file name and its line number of Function. +} + +// Stack returns the error stack information as string. func (err *Error) Stack() string { if err == nil { return "" } var ( - loop = err - index = 1 - buffer = bytes.NewBuffer(nil) + loop = err + index = 1 + infos []*stackInfo ) for loop != nil { - buffer.WriteString(fmt.Sprintf("%d. %-v\n", index, loop)) + info := &stackInfo{ + Index: index, + Message: fmt.Sprintf("%-v", loop), + } index++ - formatSubStack(loop.stack, buffer) + infos = append(infos, info) + loopLinesOfStackInfo(loop.stack, info) if loop.error != nil { if e, ok := loop.error.(*Error); ok { loop = e } else { - buffer.WriteString(fmt.Sprintf("%d. %s\n", index, loop.error.Error())) + infos = append(infos, &stackInfo{ + Index: index, + Message: loop.error.Error(), + }) index++ break } @@ -38,5 +62,110 @@ func (err *Error) Stack() string { break } } + filterLinesOfStackInfos(infos) + return formatStackInfos(infos) +} + +// filterLinesOfStackInfos removes repeated lines, which exist in subsequent stacks, from top errors. +func filterLinesOfStackInfos(infos []*stackInfo) { + var ( + ok bool + set = make(map[string]struct{}) + info *stackInfo + line *stackLine + removes []*list.Element + ) + for i := len(infos) - 1; i >= 0; i-- { + info = infos[i] + if info.Lines == nil { + continue + } + for n, e := 0, info.Lines.Front(); n < info.Lines.Len(); n, e = n+1, e.Next() { + line = e.Value.(*stackLine) + if _, ok = set[line.FileLine]; ok { + removes = append(removes, e) + } else { + set[line.FileLine] = struct{}{} + } + } + if len(removes) > 0 { + for _, e := range removes { + info.Lines.Remove(e) + } + } + removes = removes[:0] + } +} + +// formatStackInfos formats and returns error stack information as string. +func formatStackInfos(infos []*stackInfo) string { + var buffer = bytes.NewBuffer(nil) + for i, info := range infos { + buffer.WriteString(fmt.Sprintf("%d. %s\n", i+1, info.Message)) + if info.Lines != nil && info.Lines.Len() > 0 { + formatStackLines(buffer, info.Lines) + } + } return buffer.String() } + +// formatStackLines formats and returns error stack lines as string. +func formatStackLines(buffer *bytes.Buffer, lines *list.List) string { + var ( + line *stackLine + space = " " + length = lines.Len() + ) + for i, e := 0, lines.Front(); i < length; i, e = i+1, e.Next() { + line = e.Value.(*stackLine) + // Graceful indent. + if i >= 9 { + space = " " + } + buffer.WriteString(fmt.Sprintf( + " %d).%s%s\n %s\n", + i+1, space, line.Function, line.FileLine, + )) + } + return buffer.String() +} + +// loopLinesOfStackInfo iterates the stack info lines and produces the stack line info. +func loopLinesOfStackInfo(st stack, info *stackInfo) { + if st == nil { + return + } + for _, p := range st { + if fn := runtime.FuncForPC(p - 1); fn != nil { + file, line := fn.FileLine(p - 1) + if isUsingBriefStack { + // filter whole GoFrame packages stack paths. + if strings.Contains(file, consts.StackFilterKeyForGoFrame) { + continue + } + } else { + // package path stack filtering. + if strings.Contains(file, stackFilterKeyLocal) { + continue + } + } + // Avoid stack string like "`autogenerated`" + if strings.Contains(file, "<") { + continue + } + // Ignore GO ROOT paths. + if goRootForFilter != "" && + len(file) >= len(goRootForFilter) && + file[0:len(goRootForFilter)] == goRootForFilter { + continue + } + if info.Lines == nil { + info.Lines = list.New() + } + info.Lines.PushBack(&stackLine{ + Function: fn.Name(), + FileLine: fmt.Sprintf(`%s:%d`, file, line), + }) + } + } +}