github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/stack/record.go (about)

     1  package stack
     2  
     3  import (
     4  	"fmt"
     5  	"path"
     6  	"runtime"
     7  	"strings"
     8  
     9  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring"
    10  )
    11  
    12  type recordOptions struct {
    13  	packagePath  bool
    14  	packageName  bool
    15  	structName   bool
    16  	functionName bool
    17  	fileName     bool
    18  	line         bool
    19  	lambdas      bool
    20  }
    21  
    22  type functionDetails struct {
    23  	pkgPath    string
    24  	pkgName    string
    25  	structName string
    26  	funcName   string
    27  	lambdas    []string
    28  }
    29  
    30  type recordOption func(opts *recordOptions)
    31  
    32  func PackageName(b bool) recordOption {
    33  	return func(opts *recordOptions) {
    34  		opts.packageName = b
    35  	}
    36  }
    37  
    38  func FunctionName(b bool) recordOption {
    39  	return func(opts *recordOptions) {
    40  		opts.functionName = b
    41  	}
    42  }
    43  
    44  func FileName(b bool) recordOption {
    45  	return func(opts *recordOptions) {
    46  		opts.fileName = b
    47  	}
    48  }
    49  
    50  func Line(b bool) recordOption {
    51  	return func(opts *recordOptions) {
    52  		opts.line = b
    53  	}
    54  }
    55  
    56  func StructName(b bool) recordOption {
    57  	return func(opts *recordOptions) {
    58  		opts.structName = b
    59  	}
    60  }
    61  
    62  func Lambda(b bool) recordOption {
    63  	return func(opts *recordOptions) {
    64  		opts.lambdas = b
    65  	}
    66  }
    67  
    68  func PackagePath(b bool) recordOption {
    69  	return func(opts *recordOptions) {
    70  		opts.packagePath = b
    71  	}
    72  }
    73  
    74  var _ Caller = call{}
    75  
    76  type call struct {
    77  	function uintptr
    78  	file     string
    79  	line     int
    80  }
    81  
    82  func Call(depth int) (c call) {
    83  	c.function, c.file, c.line, _ = runtime.Caller(depth + 1)
    84  
    85  	return c
    86  }
    87  
    88  func (c call) Record(opts ...recordOption) string {
    89  	optionsHolder := recordOptions{
    90  		packagePath:  true,
    91  		packageName:  true,
    92  		structName:   true,
    93  		functionName: true,
    94  		fileName:     true,
    95  		line:         true,
    96  		lambdas:      true,
    97  	}
    98  	for _, opt := range opts {
    99  		if opt != nil {
   100  			opt(&optionsHolder)
   101  		}
   102  	}
   103  
   104  	name, file := extractName(c.function, c.file)
   105  	fnDetails := parseFunctionName(name)
   106  
   107  	return buildRecordString(optionsHolder, &fnDetails, file, c.line)
   108  }
   109  
   110  func extractName(function uintptr, file string) (name, fileName string) {
   111  	name = runtime.FuncForPC(function).Name()
   112  	_, fileName = path.Split(file)
   113  	name = strings.ReplaceAll(name, "[...]", "")
   114  
   115  	return name, fileName
   116  }
   117  
   118  func parseFunctionName(name string) functionDetails {
   119  	var details functionDetails
   120  	if i := strings.LastIndex(name, "/"); i > -1 {
   121  		details.pkgPath, name = name[:i], name[i+1:]
   122  	}
   123  	split := strings.Split(name, ".")
   124  	details.lambdas = make([]string, 0, len(split))
   125  	for i := range split {
   126  		elem := split[len(split)-i-1]
   127  		if !strings.HasPrefix(elem, "func") {
   128  			break
   129  		}
   130  		details.lambdas = append(details.lambdas, elem)
   131  	}
   132  	split = split[:len(split)-len(details.lambdas)]
   133  	if len(split) > 0 {
   134  		details.pkgName = split[0]
   135  	}
   136  	if len(split) > 1 {
   137  		details.funcName = split[len(split)-1]
   138  	}
   139  	if len(split) > 2 { //nolint:gomnd
   140  		details.structName = split[1]
   141  	}
   142  
   143  	return details
   144  }
   145  
   146  func buildRecordString(
   147  	optionsHolder recordOptions,
   148  	fnDetails *functionDetails,
   149  	file string,
   150  	line int,
   151  ) string {
   152  	buffer := xstring.Buffer()
   153  	defer buffer.Free()
   154  	if optionsHolder.packagePath {
   155  		buffer.WriteString(fnDetails.pkgPath)
   156  	}
   157  	if optionsHolder.packageName {
   158  		if buffer.Len() > 0 {
   159  			buffer.WriteByte('/')
   160  		}
   161  		buffer.WriteString(fnDetails.pkgName)
   162  	}
   163  	if optionsHolder.structName && len(fnDetails.structName) > 0 {
   164  		if buffer.Len() > 0 {
   165  			buffer.WriteByte('.')
   166  		}
   167  		buffer.WriteString(fnDetails.structName)
   168  	}
   169  	if optionsHolder.functionName {
   170  		if buffer.Len() > 0 {
   171  			buffer.WriteByte('.')
   172  		}
   173  		buffer.WriteString(fnDetails.funcName)
   174  		if optionsHolder.lambdas {
   175  			for i := range fnDetails.lambdas {
   176  				buffer.WriteByte('.')
   177  				buffer.WriteString(fnDetails.lambdas[len(fnDetails.lambdas)-i-1])
   178  			}
   179  		}
   180  	}
   181  	if optionsHolder.fileName {
   182  		var closeBrace bool
   183  		if buffer.Len() > 0 {
   184  			buffer.WriteByte('(')
   185  			closeBrace = true
   186  		}
   187  		buffer.WriteString(file)
   188  		if optionsHolder.line {
   189  			buffer.WriteByte(':')
   190  			fmt.Fprintf(buffer, "%d", line)
   191  		}
   192  		if closeBrace {
   193  			buffer.WriteByte(')')
   194  		}
   195  	}
   196  
   197  	return buffer.String()
   198  }
   199  
   200  func (c call) FunctionID() string {
   201  	return c.Record(Lambda(false), FileName(false))
   202  }
   203  
   204  func Record(depth int, opts ...recordOption) string {
   205  	return Call(depth + 1).Record(opts...)
   206  }