github.com/gogf/gf/v2@v2.7.4/debug/gdebug/gdebug_caller.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/gogf/gf.
     6  
     7  package gdebug
     8  
     9  import (
    10  	"fmt"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"reflect"
    15  	"runtime"
    16  	"strings"
    17  )
    18  
    19  const (
    20  	maxCallerDepth = 1000
    21  	stackFilterKey = "/debug/gdebug/gdebug"
    22  )
    23  
    24  var (
    25  	goRootForFilter  = runtime.GOROOT() // goRootForFilter is used for stack filtering purpose.
    26  	binaryVersion    = ""               // The version of current running binary(uint64 hex).
    27  	binaryVersionMd5 = ""               // The version of current running binary(MD5).
    28  	selfPath         = ""               // Current running binary absolute path.
    29  )
    30  
    31  func init() {
    32  	if goRootForFilter != "" {
    33  		goRootForFilter = strings.ReplaceAll(goRootForFilter, "\\", "/")
    34  	}
    35  	// Initialize internal package variable: selfPath.
    36  	selfPath, _ = exec.LookPath(os.Args[0])
    37  	if selfPath != "" {
    38  		selfPath, _ = filepath.Abs(selfPath)
    39  	}
    40  	if selfPath == "" {
    41  		selfPath, _ = filepath.Abs(os.Args[0])
    42  	}
    43  }
    44  
    45  // Caller returns the function name and the absolute file path along with its line
    46  // number of the caller.
    47  func Caller(skip ...int) (function string, path string, line int) {
    48  	return CallerWithFilter(nil, skip...)
    49  }
    50  
    51  // CallerWithFilter returns the function name and the absolute file path along with
    52  // its line number of the caller.
    53  //
    54  // The parameter `filters` is used to filter the path of the caller.
    55  func CallerWithFilter(filters []string, skip ...int) (function string, path string, line int) {
    56  	var (
    57  		number = 0
    58  		ok     = true
    59  	)
    60  	if len(skip) > 0 {
    61  		number = skip[0]
    62  	}
    63  	pc, file, line, start := callerFromIndex(filters)
    64  	if start != -1 {
    65  		for i := start + number; i < maxCallerDepth; i++ {
    66  			if i != start {
    67  				pc, file, line, ok = runtime.Caller(i)
    68  			}
    69  			if ok {
    70  				if filterFileByFilters(file, filters) {
    71  					continue
    72  				}
    73  				function = ""
    74  				if fn := runtime.FuncForPC(pc); fn == nil {
    75  					function = "unknown"
    76  				} else {
    77  					function = fn.Name()
    78  				}
    79  				return function, file, line
    80  			} else {
    81  				break
    82  			}
    83  		}
    84  	}
    85  	return "", "", -1
    86  }
    87  
    88  // callerFromIndex returns the caller position and according information exclusive of the
    89  // debug package.
    90  //
    91  // VERY NOTE THAT, the returned index value should be `index - 1` as the caller's start point.
    92  func callerFromIndex(filters []string) (pc uintptr, file string, line int, index int) {
    93  	var ok bool
    94  	for index = 0; index < maxCallerDepth; index++ {
    95  		if pc, file, line, ok = runtime.Caller(index); ok {
    96  			if filterFileByFilters(file, filters) {
    97  				continue
    98  			}
    99  			if index > 0 {
   100  				index--
   101  			}
   102  			return
   103  		}
   104  	}
   105  	return 0, "", -1, -1
   106  }
   107  
   108  func filterFileByFilters(file string, filters []string) (filtered bool) {
   109  	// Filter empty file.
   110  	if file == "" {
   111  		return true
   112  	}
   113  	// Filter gdebug package callings.
   114  	if strings.Contains(file, stackFilterKey) {
   115  		return true
   116  	}
   117  	for _, filter := range filters {
   118  		if filter != "" && strings.Contains(file, filter) {
   119  			return true
   120  		}
   121  	}
   122  	// GOROOT filter.
   123  	if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter {
   124  		// https://github.com/gogf/gf/issues/2047
   125  		fileSeparator := file[len(goRootForFilter)]
   126  		if fileSeparator == filepath.Separator || fileSeparator == '\\' || fileSeparator == '/' {
   127  			return true
   128  		}
   129  	}
   130  	return false
   131  }
   132  
   133  // CallerPackage returns the package name of the caller.
   134  func CallerPackage() string {
   135  	function, _, _ := Caller()
   136  	// it defines a new internal function to retrieve the package name from caller function name,
   137  	// which is for unit testing purpose for core logic of this function.
   138  	return getPackageFromCallerFunction(function)
   139  }
   140  
   141  func getPackageFromCallerFunction(function string) string {
   142  	indexSplit := strings.LastIndexByte(function, '/')
   143  	if indexSplit == -1 {
   144  		return function[:strings.IndexByte(function, '.')]
   145  	}
   146  	var (
   147  		leftPart  = function[:indexSplit+1]
   148  		rightPart = function[indexSplit+1:]
   149  		indexDot  = strings.IndexByte(rightPart, '.')
   150  	)
   151  	if indexDot >= 0 {
   152  		rightPart = rightPart[:indexDot]
   153  	}
   154  	return leftPart + rightPart
   155  }
   156  
   157  // CallerFunction returns the function name of the caller.
   158  func CallerFunction() string {
   159  	function, _, _ := Caller()
   160  	function = function[strings.LastIndexByte(function, '/')+1:]
   161  	function = function[strings.IndexByte(function, '.')+1:]
   162  	return function
   163  }
   164  
   165  // CallerFilePath returns the file path of the caller.
   166  func CallerFilePath() string {
   167  	_, path, _ := Caller()
   168  	return path
   169  }
   170  
   171  // CallerDirectory returns the directory of the caller.
   172  func CallerDirectory() string {
   173  	_, path, _ := Caller()
   174  	return filepath.Dir(path)
   175  }
   176  
   177  // CallerFileLine returns the file path along with the line number of the caller.
   178  func CallerFileLine() string {
   179  	_, path, line := Caller()
   180  	return fmt.Sprintf(`%s:%d`, path, line)
   181  }
   182  
   183  // CallerFileLineShort returns the file name along with the line number of the caller.
   184  func CallerFileLineShort() string {
   185  	_, path, line := Caller()
   186  	return fmt.Sprintf(`%s:%d`, filepath.Base(path), line)
   187  }
   188  
   189  // FuncPath returns the complete function path of given `f`.
   190  func FuncPath(f interface{}) string {
   191  	return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
   192  }
   193  
   194  // FuncName returns the function name of given `f`.
   195  func FuncName(f interface{}) string {
   196  	path := FuncPath(f)
   197  	if path == "" {
   198  		return ""
   199  	}
   200  	index := strings.LastIndexByte(path, '/')
   201  	if index < 0 {
   202  		index = strings.LastIndexByte(path, '\\')
   203  	}
   204  	return path[index+1:]
   205  }