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