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  }