github.com/zhongdalu/gf@v1.0.0/g/internal/debug/stack.go (about)

     1  // Copyright 2019 gf Author(https://github.com/zhongdalu/gf). 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/zhongdalu/gf.
     6  
     7  // Package debug contains facilities for programs to debug themselves while
     8  // they are running.
     9  package debug
    10  
    11  import (
    12  	"bytes"
    13  	"fmt"
    14  	"runtime"
    15  	"strings"
    16  )
    17  
    18  const (
    19  	gMAX_DEPTH  = 1000
    20  	gFILTER_KEY = "/g/internal/debug/stack.go"
    21  )
    22  
    23  var (
    24  	// goRootForFilter is used for stack filtering purpose.
    25  	goRootForFilter = runtime.GOROOT()
    26  )
    27  
    28  func init() {
    29  	if goRootForFilter != "" {
    30  		goRootForFilter = strings.Replace(goRootForFilter, "\\", "/", -1)
    31  	}
    32  }
    33  
    34  // PrintStack prints to standard error the stack trace returned by runtime.Stack.
    35  func PrintStack(skip ...int) {
    36  	fmt.Print(Stack(skip...))
    37  }
    38  
    39  // Stack returns a formatted stack trace of the goroutine that calls it.
    40  // It calls runtime.Stack with a large enough buffer to capture the entire trace.
    41  func Stack(skip ...int) string {
    42  	return StackWithFilter("", skip...)
    43  }
    44  
    45  // StackWithFilter returns a formatted stack trace of the goroutine that calls it.
    46  // It calls runtime.Stack with a large enough buffer to capture the entire trace.
    47  //
    48  // The parameter <filter> is used to filter the path of the caller.
    49  func StackWithFilter(filter string, skip ...int) string {
    50  	number := 0
    51  	if len(skip) > 0 {
    52  		number = skip[0]
    53  	}
    54  	name := ""
    55  	space := "  "
    56  	index := 1
    57  	buffer := bytes.NewBuffer(nil)
    58  	for i := callerFromIndex(filter) + number; i < gMAX_DEPTH; i++ {
    59  		if pc, file, line, ok := runtime.Caller(i); ok {
    60  			if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter {
    61  				continue
    62  			}
    63  			if filter != "" && strings.Contains(file, filter) {
    64  				continue
    65  			}
    66  			if strings.Contains(file, gFILTER_KEY) {
    67  				continue
    68  			}
    69  			if fn := runtime.FuncForPC(pc); fn == nil {
    70  				name = "unknown"
    71  			} else {
    72  				name = fn.Name()
    73  			}
    74  			if index > 9 {
    75  				space = " "
    76  			}
    77  			buffer.WriteString(fmt.Sprintf("%d.%s%s\n    %s:%d\n", index, space, name, file, line))
    78  			index++
    79  		} else {
    80  			break
    81  		}
    82  	}
    83  	return buffer.String()
    84  }
    85  
    86  // CallerPath returns the absolute file path along with its line number of the caller.
    87  func Caller(skip ...int) string {
    88  	return CallerWithFilter("", skip...)
    89  }
    90  
    91  // CallerPathWithFilter returns the absolute file path along with its line number of the caller.
    92  //
    93  // The parameter <filter> is used to filter the path of the caller.
    94  func CallerWithFilter(filter string, skip ...int) string {
    95  	number := 0
    96  	if len(skip) > 0 {
    97  		number = skip[0]
    98  	}
    99  	for i := callerFromIndex(filter) + number; i < gMAX_DEPTH; i++ {
   100  		if _, file, line, ok := runtime.Caller(i); ok {
   101  			if filter != "" && strings.Contains(file, filter) {
   102  				continue
   103  			}
   104  			if strings.Contains(file, gFILTER_KEY) {
   105  				continue
   106  			}
   107  			return fmt.Sprintf(`%s:%d`, file, line)
   108  		} else {
   109  			break
   110  		}
   111  	}
   112  	return ""
   113  }
   114  
   115  // callerFromIndex returns the caller position exclusive of the debug package.
   116  func callerFromIndex(filter string) int {
   117  	for i := 0; i < gMAX_DEPTH; i++ {
   118  		if _, file, _, ok := runtime.Caller(i); ok {
   119  			if filter != "" && strings.Contains(file, filter) {
   120  				continue
   121  			}
   122  			if strings.Contains(file, gFILTER_KEY) {
   123  				continue
   124  			}
   125  			// exclude the depth from the function of current package.
   126  			return i - 1
   127  		}
   128  	}
   129  	return 0
   130  }