package main import ( "flag" "fmt" "go/ast" "go/parser" "go/token" "log" "os" "path/filepath" "strings" "sync" ) var ( customLogs = flag.String("funcs", "", "Custom functions to export delimited by ','.") directory = flag.String("dir", "", "Source code directory.") methodsToSearchOnce = &sync.Once{} methodsToSearch = []string{"Debug", "Info", "Warn", "Error"} ) func main() { flag.Parse() if len(*directory) == 0 { log.Fatalln("directory must be provided") } directories, err := getAllSubdirectories(*directory) if err != nil { log.Fatalf("failed to list subdirs: %v", err) } for _, dir := range directories { fset := token.NewFileSet() packages, err := parser.ParseDir(fset, dir, nil, 0) if err != nil { log.Fatalf("failed to parse directory %s: %v", dir, err) } for _, p := range packages { for _, f := range p.Files { ast.Inspect(f, func(n ast.Node) bool { expr, ok := n.(*ast.CallExpr) if !ok { return true } isLog, logMethod := isLogDot(expr.Fun) if isLog && len(expr.Args) > 0 && isStringValue(expr.Args[0]) { position := fset.Position(expr.Pos()) fmt.Printf("%v %s:%s\n", position, logMethod, stringValue(expr.Args[0])) return false } return true }) } } } } func getAllSubdirectories(dir string) ([]string, error) { result := make([]string, 0) stack := make([]string, 0) stack = append(stack, dir) for len(stack) > 0 { current := stack[len(stack)-1] stack = stack[:len(stack)-1] entities, err := os.ReadDir(current) if err != nil { return nil, err } for _, e := range entities { if e.IsDir() { path := filepath.Join(current, e.Name()) result = append(result, path) stack = append(stack, path) } } } return result, nil } func isLogDot(expr ast.Expr) (bool, string) { sel, ok := expr.(*ast.SelectorExpr) if !ok { return false, "" } methodsToSearchOnce.Do(func() { for _, cl := range strings.Split(*customLogs, ",") { cl = strings.Trim(cl, " ") if len(cl) > 0 { methodsToSearch = append(methodsToSearch, cl) } } }) for _, method := range methodsToSearch { if isIdent(sel.Sel, method) { return true, method } } return false, "" } func isIdent(expr ast.Expr, ident string) bool { id, ok := expr.(*ast.Ident) if !ok { return false } if id.Name == ident { return true } return false } func isStringValue(expr ast.Expr) bool { basicLit, ok := expr.(*ast.BasicLit) return ok && basicLit.Kind == token.STRING } func stringValue(expr ast.Expr) string { basicLit, _ := expr.(*ast.BasicLit) return basicLit.Value }