github.com/wangyougui/gf/v2@v2.6.5/errors/gerror/gerror_error_stack.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/wangyougui/gf. 6 7 package gerror 8 9 import ( 10 "bytes" 11 "container/list" 12 "fmt" 13 "runtime" 14 "strings" 15 16 "github.com/wangyougui/gf/v2/internal/consts" 17 "github.com/wangyougui/gf/v2/internal/errors" 18 ) 19 20 // stackInfo manages stack info of certain error. 21 type stackInfo struct { 22 Index int // Index is the index of current error in whole error stacks. 23 Message string // Error information string. 24 Lines *list.List // Lines contains all error stack lines of current error stack in sequence. 25 } 26 27 // stackLine manages each line info of stack. 28 type stackLine struct { 29 Function string // Function name, which contains its full package path. 30 FileLine string // FileLine is the source file name and its line number of Function. 31 } 32 33 // Stack returns the error stack information as string. 34 func (err *Error) Stack() string { 35 if err == nil { 36 return "" 37 } 38 var ( 39 loop = err 40 index = 1 41 infos []*stackInfo 42 isStackModeBrief = errors.IsStackModeBrief() 43 ) 44 for loop != nil { 45 info := &stackInfo{ 46 Index: index, 47 Message: fmt.Sprintf("%-v", loop), 48 } 49 index++ 50 infos = append(infos, info) 51 loopLinesOfStackInfo(loop.stack, info, isStackModeBrief) 52 if loop.error != nil { 53 if e, ok := loop.error.(*Error); ok { 54 loop = e 55 } else { 56 infos = append(infos, &stackInfo{ 57 Index: index, 58 Message: loop.error.Error(), 59 }) 60 index++ 61 break 62 } 63 } else { 64 break 65 } 66 } 67 filterLinesOfStackInfos(infos) 68 return formatStackInfos(infos) 69 } 70 71 // filterLinesOfStackInfos removes repeated lines, which exist in subsequent stacks, from top errors. 72 func filterLinesOfStackInfos(infos []*stackInfo) { 73 var ( 74 ok bool 75 set = make(map[string]struct{}) 76 info *stackInfo 77 line *stackLine 78 removes []*list.Element 79 ) 80 for i := len(infos) - 1; i >= 0; i-- { 81 info = infos[i] 82 if info.Lines == nil { 83 continue 84 } 85 for n, e := 0, info.Lines.Front(); n < info.Lines.Len(); n, e = n+1, e.Next() { 86 line = e.Value.(*stackLine) 87 if _, ok = set[line.FileLine]; ok { 88 removes = append(removes, e) 89 } else { 90 set[line.FileLine] = struct{}{} 91 } 92 } 93 if len(removes) > 0 { 94 for _, e := range removes { 95 info.Lines.Remove(e) 96 } 97 } 98 removes = removes[:0] 99 } 100 } 101 102 // formatStackInfos formats and returns error stack information as string. 103 func formatStackInfos(infos []*stackInfo) string { 104 var buffer = bytes.NewBuffer(nil) 105 for i, info := range infos { 106 buffer.WriteString(fmt.Sprintf("%d. %s\n", i+1, info.Message)) 107 if info.Lines != nil && info.Lines.Len() > 0 { 108 formatStackLines(buffer, info.Lines) 109 } 110 } 111 return buffer.String() 112 } 113 114 // formatStackLines formats and returns error stack lines as string. 115 func formatStackLines(buffer *bytes.Buffer, lines *list.List) string { 116 var ( 117 line *stackLine 118 space = " " 119 length = lines.Len() 120 ) 121 for i, e := 0, lines.Front(); i < length; i, e = i+1, e.Next() { 122 line = e.Value.(*stackLine) 123 // Graceful indent. 124 if i >= 9 { 125 space = " " 126 } 127 buffer.WriteString(fmt.Sprintf( 128 " %d).%s%s\n %s\n", 129 i+1, space, line.Function, line.FileLine, 130 )) 131 } 132 return buffer.String() 133 } 134 135 // loopLinesOfStackInfo iterates the stack info lines and produces the stack line info. 136 func loopLinesOfStackInfo(st stack, info *stackInfo, isStackModeBrief bool) { 137 if st == nil { 138 return 139 } 140 for _, p := range st { 141 if fn := runtime.FuncForPC(p - 1); fn != nil { 142 file, line := fn.FileLine(p - 1) 143 if isStackModeBrief { 144 // filter whole GoFrame packages stack paths. 145 if strings.Contains(file, consts.StackFilterKeyForGoFrame) { 146 continue 147 } 148 } else { 149 // package path stack filtering. 150 if strings.Contains(file, stackFilterKeyLocal) { 151 continue 152 } 153 } 154 // Avoid stack string like "`autogenerated`" 155 if strings.Contains(file, "<") { 156 continue 157 } 158 // Ignore GO ROOT paths. 159 if goRootForFilter != "" && 160 len(file) >= len(goRootForFilter) && 161 file[0:len(goRootForFilter)] == goRootForFilter { 162 continue 163 } 164 if info.Lines == nil { 165 info.Lines = list.New() 166 } 167 info.Lines.PushBack(&stackLine{ 168 Function: fn.Name(), 169 FileLine: fmt.Sprintf(`%s:%d`, file, line), 170 }) 171 } 172 } 173 }