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  }