diff --git a/runtimehelper/go.mod b/runtimehelper/go.mod new file mode 100644 index 0000000..11b2ac2 --- /dev/null +++ b/runtimehelper/go.mod @@ -0,0 +1,3 @@ +module git.blauwelle.com/go/crate/runtimehelper + +go 1.20 diff --git a/runtimehelper/helper.go b/runtimehelper/helper.go new file mode 100644 index 0000000..54d4783 --- /dev/null +++ b/runtimehelper/helper.go @@ -0,0 +1,162 @@ +package runtimehelper + +import ( + "fmt" + "path/filepath" + "runtime" +) + +// Frame 调用相关信息 +type Frame struct { + Function string `json:"func"` + File string `json:"file"` + Line int `json:"line"` +} + +// IsValid 表示是否有效 +func (frame Frame) IsValid() bool { + return frame.Line > 0 +} + +// GetFilename 返回文件名 +func (frame Frame) GetFilename() string { + _, file := filepath.Split(frame.File) + return file +} + +// SplitFilePath 返回 File 对应的路径和文件名 +func (frame Frame) SplitFilePath() (string, string) { + return filepath.Split(frame.File) +} + +// SplitFunction 返回 Function 对应的包路径和函数名 +func (frame Frame) SplitFunction() (string, string) { + return PackagePathQualifiedFunctionName(frame.Function).Split() +} + +// Caller 返回当前调用对应的 Frame, +// skip=0 表示调用 Caller 处. +func Caller(skip int) Frame { + pc := make([]uintptr, 1) + n := runtime.Callers(skip+2, pc) + frame, _ := runtime.CallersFrames(pc[:n]).Next() + if frame.PC == 0 { + return Frame{} + } + return Frame{ + Function: frame.Function, + File: frame.File, + Line: frame.Line, + } +} + +// Stack 返回调用栈, +// skip=0 表示调用 Stack 处. +func Stack(skip, maximumFrames int) []Frame { + pc := make([]uintptr, maximumFrames) + n := runtime.Callers(skip+2, pc) + stack := make([]Frame, 0, n) + frames := runtime.CallersFrames(pc[:n]) + for { + frame, more := frames.Next() + if frame.PC != 0 { + stack = append(stack, Frame{ + Function: frame.Function, + File: frame.File, + Line: frame.Line, + }) + } + if !more { + break + } + } + return stack +} + +// CallerFrame 函数调用对应的 [runtime.Frame], +// 从 [runtime.Frame] 中可以获取调用路径上的函数的名字、文件、行号等信息; +// skip 值对应的 [runtime.Frame]: +// - -1: CallerFrame +// - 0: 调用 CallerFrame 处 +// +// 可以使用 [runtime.Frame.PC] != 0 判断 runtime.Frame 有效 +func CallerFrame(skip int) runtime.Frame { + pc := make([]uintptr, 1) + n := runtime.Callers(skip+2, pc) + frame, _ := runtime.CallersFrames(pc[:n]).Next() + return frame +} + +// CallersFrames 返回函数调用对应的 [runtime.Frames], +// 从 Frames 中可以获取调用路径上的函数的名字、文件、行号等信息; +// skip 值对应的第1个 [runtime.Frame]: +// - -2: [runtime.Callers] +// - -1: CallersFrames +// - 0: 调用 CallersFrames 处 +func CallersFrames(skip, maximumFrames int) *runtime.Frames { + pc := make([]uintptr, maximumFrames) + n := runtime.Callers(skip+2, pc) + return runtime.CallersFrames(pc[:n]) +} + +// PrintCallersFrames 打印函数调用栈, +// 从调用 PrintCallersFrames 的地方开始打印 +func PrintCallersFrames() { + frames := CallersFrames(3, 32) + for { + frame, more := frames.Next() + if frame.PC != 0 { + fmt.Printf("%s:%d in %s\n", frame.File, frame.Line, frame.Function) + } + if !more { + break + } + } +} + +// PackagePathQualifiedFunctionName 是 [runtime.Frame.Function] (package path-qualified function name) +type PackagePathQualifiedFunctionName string + +// Split 把完整的函数路径切割成包路径和函数名 +func (f PackagePathQualifiedFunctionName) Split() (string, string) { + var period int +loop: + for i := len(f) - 1; i >= 0; i-- { + switch f[i] { + case '/': + break loop + case '.': + period = i + } + } + if period == 0 { + return "", "" + } + return string(f[:period]), string(f[period+1:]) +} + +// PackageName 把完整的函数路径切割成包路径和函数名 +func (f PackagePathQualifiedFunctionName) PackageName() string { + var period int + for i := len(f) - 1; i >= 0; i-- { + switch f[i] { + case '/': + return string(f[:period]) + case '.': + period = i + } + } + return string(f[:period]) +} + +// SplitPackageFunctionName 把完整的函数路径切割成包路径和函数名 +// 参数 f 是 [runtime.Frame.Function] (package path-qualified function name), +func SplitPackageFunctionName(f string) (string, string) { + return PackagePathQualifiedFunctionName(f).Split() +} + +// GetPackageName 把完整的函数路径切割成包路径和函数名 +// 参数 f 是 [runtime.Frame.Function] (package path-qualified function name), +func GetPackageName(f string) string { + return PackagePathQualifiedFunctionName(f).PackageName() +}