package logging import ( "fmt" "go/ast" "go/parser" "go/token" "os" "path/filepath" "regexp" "strconv" "strings" "sync" "time" ) type stackFrame struct { File string Line int Func string } var stackFileLinePattern = regexp.MustCompile(`\s*(.+\.go):(\d+)\s*$`) type sourceFunction struct { name string source string startLine, endLine int } type sourceFileCache struct { size int64 modTime time.Time items []sourceFunction } var sourceCache = struct { sync.RWMutex files map[string]sourceFileCache }{files: make(map[string]sourceFileCache)} func finalApplicationCaller(stack string) (stackFrame, bool) { if stack == "" { return stackFrame{}, false } functionName := "" for _, raw := range strings.Split(stack, "\n") { line := strings.TrimSpace(raw) if line == "" { continue } matches := stackFileLinePattern.FindStringSubmatch(line) if matches == nil { functionName = line continue } lineNumber, _ := strconv.Atoi(matches[2]) if skipStackFile(matches[1]) { functionName = "" continue } return stackFrame{File: matches[1], Line: lineNumber, Func: functionName}, true } return stackFrame{}, false } func skipStackFile(filename string) bool { normalized := strings.ReplaceAll(filename, "\\", "/") for _, marker := range []string{ "/go/pkg/mod/", "/go.uber.org/", "/gorm.io/", "/internal/logging/", "/internal/transport/middleware/", "/internal/transport/router/", "/internal/server/handler/", "/internal/server/middleware/", "/internal/server/router/", "/internal/server/httpx/", "/internal/server/middleware_", "/internal/server/route_", } { if strings.Contains(normalized, marker) { return true } } return strings.Contains(normalized, "/src/") && (strings.Contains(normalized, "/go/go") || strings.Contains(normalized, "/go/src/")) } func functionSourceAt(filename string, line int) (name, source string, startLine, endLine int, err error) { filename = filepath.Clean(filename) info, err := os.Stat(filename) if err != nil { return "", "", 0, 0, fmt.Errorf("stat file failed: %w", err) } sourceCache.RLock() cached, found := sourceCache.files[filename] sourceCache.RUnlock() if found && cached.size == info.Size() && cached.modTime.Equal(info.ModTime()) { return sourceFunctionAtLine(cached.items, line, filename) } content, err := os.ReadFile(filename) if err != nil { return "", "", 0, 0, fmt.Errorf("read file failed: %w", err) } files := token.NewFileSet() parsed, err := parser.ParseFile(files, filename, content, parser.ParseComments) if err != nil { return "", "", 0, 0, fmt.Errorf("parse file failed: %w", err) } sourceCache.Lock() sourceCache.files[filename] = sourceFileCache{size: info.Size(), modTime: info.ModTime(), items: allFunctions(parsed, files, content)} cached = sourceCache.files[filename] sourceCache.Unlock() return sourceFunctionAtLine(cached.items, line, filename) } func allFunctions(parsed *ast.File, files *token.FileSet, content []byte) []sourceFunction { items := make([]sourceFunction, 0) ast.Inspect(parsed, func(node ast.Node) bool { declaration, ok := node.(*ast.FuncDecl) if !ok { return true } start := files.Position(declaration.Pos()).Line end := files.Position(declaration.End()).Line startOffset := files.Position(declaration.Pos()).Offset endOffset := files.Position(declaration.End()).Offset if startOffset >= 0 && endOffset <= len(content) && startOffset < endOffset { items = append(items, sourceFunction{name: declaration.Name.Name, source: string(content[startOffset:endOffset]), startLine: start, endLine: end}) } return true }) return items } func sourceFunctionAtLine(items []sourceFunction, line int, filename string) (string, string, int, int, error) { for _, item := range items { if line >= item.startLine && line <= item.endLine { return item.name, item.source, item.startLine, item.endLine, nil } } return "", "", 0, 0, fmt.Errorf("no function encloses line %d in %s", line, filename) }