From 412fc6b37bd498e15773e72593980ba3e7ad6a43 Mon Sep 17 00:00:00 2001 From: Yi Yang Date: Thu, 26 Dec 2024 19:08:15 +0800 Subject: [PATCH] refine: standardized error message --- README.md | 2 +- test/build_test.go | 10 +- test/flags_test.go | 10 +- test/infra.go | 17 ++- test/kitex/v0.5.1/go.mod | 2 - test/world_test.go | 4 +- tool/cmd/main.go | 63 ++++++----- tool/config/config.go | 62 +++++------ tool/errc/errcode.go | 114 +++++++++++++++++++ tool/instrument/inst_file.go | 15 ++- tool/instrument/inst_func.go | 83 +++++++------- tool/instrument/inst_struct.go | 10 +- tool/instrument/instrument.go | 50 +++++---- tool/instrument/optimize.go | 32 +++--- tool/instrument/trampoline.go | 144 ++++++++++++------------ tool/preprocess/fetch.go | 60 ++++------ tool/preprocess/match.go | 46 ++++---- tool/preprocess/pkgdep.go | 18 +-- tool/preprocess/preprocess.go | 188 ++++++++++++++++---------------- tool/preprocess/setup.go | 82 +++++++------- tool/resource/bundle.go | 31 +++--- tool/resource/resource.go | 3 +- tool/resource/ruledef.go | 24 ++-- tool/{shared => util}/ast.go | 28 ++--- tool/util/log.go | 17 +-- tool/{shared => util}/shared.go | 90 +++++++-------- tool/util/util.go | 65 ++++++----- 27 files changed, 689 insertions(+), 581 deletions(-) create mode 100644 tool/errc/errcode.go rename tool/{shared => util}/ast.go (94%) rename tool/{shared => util}/shared.go (80%) diff --git a/README.md b/README.md index 63fe5335..9e26244e 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ to engage with us. -## Adopters +# Adopters These are only part of the companies using this project, for reference only. If you are using this project, please [add your company here](https://github.com/alibaba/opentelemetry-go-auto-instrumentation/issues/225) to tell us your scenario to make this project better. diff --git a/test/build_test.go b/test/build_test.go index 02895fac..b7aea4a6 100644 --- a/test/build_test.go +++ b/test/build_test.go @@ -17,7 +17,7 @@ package test import ( "testing" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" ) func TestBuildProject(t *testing.T) { @@ -72,8 +72,8 @@ func TestBuildProject5(t *testing.T) { RunGoBuild(t, "go", "build", "m1") // both test_fmt.json and default.json rules should be available // because we always append new -rule to the default.json by default - ExpectPreprocessContains(t, shared.DebugLogFile, "fmt") - ExpectPreprocessContains(t, shared.DebugLogFile, "database/sql") + ExpectPreprocessContains(t, util.DebugLogFile, "fmt") + ExpectPreprocessContains(t, util.DebugLogFile, "database/sql") } func TestBuildProject6(t *testing.T) { @@ -83,6 +83,6 @@ func TestBuildProject6(t *testing.T) { RunSet(t, "-disabledefault=true", "-rule=../../pkg/data/test_fmt.json", "-verbose") RunGoBuild(t, "go", "build", "m1") // only test_fmt.json should be available because -disabledefault is set - ExpectPreprocessContains(t, shared.DebugLogFile, "fmt") - ExpectPreprocessNotContains(t, shared.DebugLogFile, "database/sql") + ExpectPreprocessContains(t, util.DebugLogFile, "fmt") + ExpectPreprocessNotContains(t, util.DebugLogFile, "database/sql") } diff --git a/test/flags_test.go b/test/flags_test.go index b0f51317..981e1f66 100644 --- a/test/flags_test.go +++ b/test/flags_test.go @@ -17,7 +17,7 @@ package test import ( "testing" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" ) const AppName = "flags" @@ -26,13 +26,13 @@ func TestFlags(t *testing.T) { UseApp(AppName) RunGoBuildFallible(t, "go", "build", "-thisisnotvalid") - ExpectPreprocessContains(t, shared.DebugLogFile, "failed to") + ExpectPreprocessContains(t, util.DebugLogFile, "Fatal Error") RunVersion(t) ExpectStdoutContains(t, "version") RunGoBuildFallible(t, "go", "build", "notevenaflag") - ExpectPreprocessContains(t, shared.DebugLogFile, "failed to") + ExpectPreprocessContains(t, util.DebugLogFile, "Fatal Error") RunSet(t, "-verbose") RunGoBuild(t, "go", "build", `-ldflags=-X main.Placeholder=replaced`) @@ -50,10 +50,10 @@ func TestFlagEnvOverwrite(t *testing.T) { RunSet(t, "-verbose=false") RunGoBuildWithEnv(t, []string{"OTELTOOL_VERBOSE=true"}, "go", "build") - ExpectPreprocessContains(t, shared.DebugLogFile, "Available") + ExpectPreprocessContains(t, util.DebugLogFile, "Available") RunSet(t, "-verbose=true") RunGoBuildWithEnv(t, []string{"OTELTOOL_VERBOSE=false"}, "go", "build") - ExpectPreprocessNotContains(t, shared.DebugLogFile, "Available") + ExpectPreprocessNotContains(t, util.DebugLogFile, "Available") } diff --git a/test/infra.go b/test/infra.go index 6ffd01e7..5d61b047 100644 --- a/test/infra.go +++ b/test/infra.go @@ -26,7 +26,6 @@ import ( "github.com/alibaba/opentelemetry-go-auto-instrumentation/test/verifier" "github.com/alibaba/opentelemetry-go-auto-instrumentation/test/version" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" ) @@ -55,7 +54,7 @@ func runCmd(args []string) *exec.Cmd { } func ReadInstrumentLog(t *testing.T, fileName string) string { - path := filepath.Join(shared.TempBuildDir, util.PInstrument, fileName) + path := filepath.Join(util.TempBuildDir, util.PInstrument, fileName) content, err := util.ReadFile(path) if err != nil { t.Fatal(err) @@ -64,7 +63,7 @@ func ReadInstrumentLog(t *testing.T, fileName string) string { } func ReadPreprocessLog(t *testing.T, fileName string) string { - path := filepath.Join(shared.TempBuildDir, util.PPreprocess, fileName) + path := filepath.Join(util.TempBuildDir, util.PPreprocess, fileName) content, err := util.ReadFile(path) if err != nil { t.Fatal(err) @@ -111,7 +110,7 @@ func RunGoBuild(t *testing.T, args ...string) { t.Log(stdout) t.Log("\n\n\n") t.Log(stderr) - log1 := ReadPreprocessLog(t, shared.DebugLogFile) + log1 := ReadPreprocessLog(t, util.DebugLogFile) text := fmt.Sprintf("failed to run instrument: %v\n", err) text += fmt.Sprintf("text: %v\n", log1) t.Fatal(text) @@ -130,7 +129,7 @@ func RunGoBuildWithEnv(t *testing.T, envs []string, args ...string) { t.Log(stdout) t.Log("\n\n\n") t.Log(stderr) - log1 := ReadPreprocessLog(t, shared.DebugLogFile) + log1 := ReadPreprocessLog(t, util.DebugLogFile) text := fmt.Sprintf("failed to run instrument: %v\n", err) text += fmt.Sprintf("text: %v\n", log1) t.Fatal(text) @@ -212,25 +211,25 @@ func ExpectStderrContains(t *testing.T, expect string) { } func ExpectInstrumentContains(t *testing.T, log string, rule string) { - path := filepath.Join(shared.TempBuildDir, util.PInstrument, log) + path := filepath.Join(util.TempBuildDir, util.PInstrument, log) content := readLog(t, path) ExpectContains(t, content, rule) } func ExpectInstrumentNotContains(t *testing.T, log string, rule string) { - path := filepath.Join(shared.TempBuildDir, util.PInstrument, log) + path := filepath.Join(util.TempBuildDir, util.PInstrument, log) content := readLog(t, path) ExpectNotContains(t, content, rule) } func ExpectPreprocessContains(t *testing.T, log string, rule string) { - path := filepath.Join(shared.TempBuildDir, util.PPreprocess, log) + path := filepath.Join(util.TempBuildDir, util.PPreprocess, log) content := readLog(t, path) ExpectContains(t, content, rule) } func ExpectPreprocessNotContains(t *testing.T, log string, rule string) { - path := filepath.Join(shared.TempBuildDir, util.PPreprocess, log) + path := filepath.Join(util.TempBuildDir, util.PPreprocess, log) content := readLog(t, path) ExpectNotContains(t, content, rule) } diff --git a/test/kitex/v0.5.1/go.mod b/test/kitex/v0.5.1/go.mod index 33f2c01d..2189ee5a 100644 --- a/test/kitex/v0.5.1/go.mod +++ b/test/kitex/v0.5.1/go.mod @@ -2,8 +2,6 @@ module kitex/v0.5.1 go 1.22.0 -toolchain go1.22.7 - replace github.com/alibaba/opentelemetry-go-auto-instrumentation => ../../../../opentelemetry-go-auto-instrumentation replace github.com/alibaba/opentelemetry-go-auto-instrumentation/test/verifier => ../../../../opentelemetry-go-auto-instrumentation/test/verifier diff --git a/test/world_test.go b/test/world_test.go index 3d7cc4e7..6f371057 100644 --- a/test/world_test.go +++ b/test/world_test.go @@ -19,7 +19,7 @@ import ( "regexp" "testing" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" ) const WorldAppName = "world" @@ -29,7 +29,7 @@ func TestCompileTheWorld(t *testing.T) { RunGoBuild(t, "go", "build") RunApp(t, WorldAppName) - text := ReadPreprocessLog(t, shared.DebugLogFile) + text := ReadPreprocessLog(t, util.DebugLogFile) regex := `\"ImportPath\":\"([^"]+)\"` r := regexp.MustCompile(regex) diff --git a/tool/cmd/main.go b/tool/cmd/main.go index 703638db..58b0901e 100644 --- a/tool/cmd/main.go +++ b/tool/cmd/main.go @@ -18,12 +18,13 @@ import ( "fmt" "os" "path/filepath" + "runtime" "strings" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/config" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/instrument" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/preprocess" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" ) @@ -48,17 +49,18 @@ Command: ` func printUsage() { - usage = strings.ReplaceAll(usage, "{}", util.GetToolName()) + name, _ := util.GetToolName() + usage = strings.ReplaceAll(usage, "{}", name) fmt.Print(usage) } func initLog() error { name := util.PPreprocess - path := shared.GetTempBuildDirWith(name) - logPath := filepath.Join(path, shared.DebugLogFile) + path := util.GetTempBuildDirWith(name) + logPath := filepath.Join(path, util.DebugLogFile) _, err := os.Create(logPath) if err != nil { - return fmt.Errorf("failed to create log file: %w", err) + return errc.New(errc.ErrCreateFile, err.Error()) } return nil } @@ -71,34 +73,32 @@ func initTempDir() error { } // Make temp build directory if not exists - if exist, _ := util.PathExists(shared.TempBuildDir); !exist { - err := os.MkdirAll(shared.TempBuildDir, 0777) + if util.PathNotExists(util.TempBuildDir) { + err := os.MkdirAll(util.TempBuildDir, 0777) if err != nil { - return fmt.Errorf("failed to make working directory: %w", err) + return errc.New(errc.ErrMkdirAll, err.Error()) } } // Make sub-directory of temp build directory for each phase. Specifaclly, // we always recreate the preprocess and instrument directories, but only // create the configure directory if it does not exist. This is because // the configure directory can be used across multiple runs. - exist, _ := util.PathExists(shared.GetTempBuildDirWith(util.PConfigure)) - if !exist { - err := os.MkdirAll(shared.GetTempBuildDirWith(util.PConfigure), 0777) + if util.PathNotExists(util.GetTempBuildDirWith(util.PConfigure)) { + err := os.MkdirAll(util.GetTempBuildDirWith(util.PConfigure), 0777) if err != nil { - return fmt.Errorf("failed to make log directory: %w", err) + return errc.New(errc.ErrMkdirAll, err.Error()) } } for _, subdir := range []string{util.PPreprocess, util.PInstrument} { - exist, _ = util.PathExists(shared.GetTempBuildDirWith(subdir)) - if exist { - err := os.RemoveAll(shared.GetTempBuildDirWith(subdir)) + if util.PathExists(util.GetTempBuildDirWith(subdir)) { + err := os.RemoveAll(util.GetTempBuildDirWith(subdir)) if err != nil { - return fmt.Errorf("failed to remove directory: %w", err) + return errc.New(errc.ErrRemoveAll, err.Error()) } } - err := os.MkdirAll(shared.GetTempBuildDirWith(subdir), 0777) + err := os.MkdirAll(util.GetTempBuildDirWith(subdir), 0777) if err != nil { - return fmt.Errorf("failed to make log directory: %w", err) + return errc.New(errc.ErrMkdirAll, err.Error()) } } @@ -126,14 +126,14 @@ func initEnv() error { // Create temp build directory err := initTempDir() if err != nil { - return fmt.Errorf("failed to init temp dir: %w", err) + return err } // Create log files under temp build directory if util.InPreprocess() { err := initLog() if err != nil { - return fmt.Errorf("failed to init logs: %w", err) + return err } } @@ -141,12 +141,27 @@ func initEnv() error { if util.InPreprocess() || util.InInstrument() { err = config.InitConfig() if err != nil { - return fmt.Errorf("failed to init config: %w", err) + return err } } return nil } +func fatal(err error) { + message := "===== Environment =====\n" + message += fmt.Sprintf("%-11s: %s\n", "Command", strings.Join(os.Args, " ")) + message += fmt.Sprintf("%-11s: %s\n", "ErrorLog", util.GetLoggerPath()) + message += fmt.Sprintf("%-11s: %s\n", "WorkDir", os.Getenv("PWD")) + path, _ := util.GetGoModPath() + message += fmt.Sprintf("%-11s: %s\n", "GoMod", path) + message += fmt.Sprintf("%-11s: %s, %s, %s\n", "Toolchain", + runtime.GOOS+"/"+runtime.GOARCH, + runtime.Version(), config.ToolVersion) + message += "===== Fatal Error ======\n" + message += err.Error() + util.LogFatal("\033[31m%s\033[0m", message) // log in red color +} + func main() { if len(os.Args) < 2 { printUsage() @@ -155,8 +170,7 @@ func main() { err := initEnv() if err != nil { - util.LogFatal("failed to init env: %v", err) - os.Exit(1) + fatal(err) } subcmd := os.Args[1] @@ -173,7 +187,6 @@ func main() { printUsage() } if err != nil { - util.LogFatal("failed to run command %s: %v", subcmd, err) - os.Exit(1) + fatal(err) } } diff --git a/tool/config/config.go b/tool/config/config.go index f68c0d94..bfe2e664 100644 --- a/tool/config/config.go +++ b/tool/config/config.go @@ -24,7 +24,7 @@ import ( "strings" "unicode" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" ) @@ -74,16 +74,12 @@ func (bc *BuildConfig) IsDisableDefault() bool { } func (bc *BuildConfig) makeRuleAbs(file string) (string, error) { - exist, err := util.PathExists(file) - if err != nil { - return "", fmt.Errorf("failed to check rule file: %w", err) - } - if !exist { - return "", fmt.Errorf("rule file %s not found", file) + if util.PathNotExists(file) { + return "", errc.New(errc.ErrNotExist, file) } - file, err = filepath.Abs(file) + file, err := filepath.Abs(file) if err != nil { - return "", fmt.Errorf("failed to get rule file: %w", err) + return "", errc.New(errc.ErrAbsPath, err.Error()) } return file, nil } @@ -103,7 +99,7 @@ func (bc *BuildConfig) parseRuleFiles() error { for i, file := range files { f, err := bc.makeRuleAbs(file) if err != nil { - return fmt.Errorf("failed to set rule file: %w", err) + return err } files[i] = f } @@ -111,7 +107,7 @@ func (bc *BuildConfig) parseRuleFiles() error { } else { f, err := bc.makeRuleAbs(bc.RuleJsonFiles) if err != nil { - return fmt.Errorf("failed to set rule file: %w", err) + return err } bc.RuleJsonFiles = f } @@ -122,14 +118,14 @@ func storeConfig(bc *BuildConfig) error { util.Assert(bc != nil, "build config is not initialized") util.Assert(util.InConfigure(), "sanity check") - file := shared.GetConfigureLogPath(shared.BuildConfFile) + file := util.GetConfigureLogPath(util.BuildConfFile) bs, err := json.Marshal(bc) if err != nil { - return fmt.Errorf("failed to marshal build config: %w", err) + return errc.New(errc.ErrInvalidJSON, err.Error()) } _, err = util.WriteFile(file, string(bs)) if err != nil { - return fmt.Errorf("failed to write build config: %w", err) + return err } return nil } @@ -137,22 +133,20 @@ func storeConfig(bc *BuildConfig) error { func loadConfig() (*BuildConfig, error) { util.Assert(conf == nil, "build config is already initialized") // If the build config file does not exist, return a default build config - confFile := shared.GetConfigureLogPath(shared.BuildConfFile) - exist, _ := util.PathExists(confFile) - if !exist { + confFile := util.GetConfigureLogPath(util.BuildConfFile) + if util.PathNotExists(confFile) { return &BuildConfig{}, nil } // Load build config from json file - file := shared.GetConfigureLogPath(shared.BuildConfFile) + file := util.GetConfigureLogPath(util.BuildConfFile) data, err := util.ReadFile(file) if err != nil { - return &BuildConfig{}, - fmt.Errorf("failed to read build config: %w", err) + return &BuildConfig{}, err } bc := &BuildConfig{} err = json.Unmarshal([]byte(data), bc) if err != nil { - return nil, fmt.Errorf("failed to unmarshal build config: %w", err) + return nil, errc.New(errc.ErrInvalidJSON, err.Error()) } return bc, nil } @@ -196,7 +190,7 @@ func loadConfigFromEnv(conf *BuildConfig) { case reflect.String: f.SetString(envVal) default: - util.LogFatal("Unsupported config type %s", f.Kind()) + util.ShouldNotReachHere() } } } @@ -206,13 +200,13 @@ func InitConfig() (err error) { // Load build config from json file conf, err = loadConfig() if err != nil { - return fmt.Errorf("failed to load build config: %w", err) + return err } loadConfigFromEnv(conf) err = conf.parseRuleFiles() if err != nil { - return fmt.Errorf("failed to parse rule files: %w", err) + return err } mode := os.O_WRONLY | os.O_APPEND @@ -223,25 +217,28 @@ func InitConfig() (err error) { } if conf.Log == "" { // Redirect log to file if flag is not set - debugLogPath := shared.GetPreprocessLogPath(shared.DebugLogFile) + debugLogPath := util.GetPreprocessLogPath(util.DebugLogFile) debugLog, _ := os.OpenFile(debugLogPath, mode, 0777) if debugLog != nil { - util.SetLogTo(debugLog) + util.SetLogger(debugLog) } } else { // Otherwise, log to the specified file logFile, err := os.OpenFile(conf.Log, mode, 0777) if err != nil { - return fmt.Errorf("failed to open log file: %w", err) + return errc.New(errc.ErrOpenFile, err.Error()) } - util.SetLogTo(logFile) + util.SetLogger(logFile) } return nil } func PrintVersion() error { - fmt.Printf("%s version %s\n", util.GetToolName(), ToolVersion) - os.Exit(0) + name, err := util.GetToolName() + if err != nil { + return err + } + fmt.Printf("%s version %s\n", name, ToolVersion) return nil } @@ -267,13 +264,12 @@ func Configure() error { "Disable default rules") flag.CommandLine.Parse(os.Args[2:]) - util.Log("Configured in %s", - shared.GetConfigureLogPath(shared.BuildConfFile)) + util.Log("Configured in %s", util.GetConfigureLogPath(util.BuildConfFile)) // Store build config for future phases err = storeConfig(bc) if err != nil { - return fmt.Errorf("failed to store build config: %w", err) + return err } return nil } diff --git a/tool/errc/errcode.go b/tool/errc/errcode.go new file mode 100644 index 00000000..c317fdcf --- /dev/null +++ b/tool/errc/errcode.go @@ -0,0 +1,114 @@ +// Copyright (c) 2025 Alibaba Group Holding Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package errc + +import ( + "fmt" + "runtime/debug" +) + +const ( + ErrOpenFile = 1000 + iota + ErrCreateFile + ErrCloseFile + ErrRemoveAll + ErrReadDir + ErrCopyFile + ErrWriteFile + ErrWalkDir + ErrStat + ErrMkdirAll + ErrNotExist + ErrInvalidRule + ErrMatchRule + ErrInternal + ErrRunCmd + ErrInvalidJSON + ErrGetwd + ErrSetupRule + ErrParseCode + ErrAbsPath + ErrNotModularized + ErrGetExecutable + ErrInstrument +) + +var errMessages = map[int]string{ + ErrOpenFile: "Failed to open file", + ErrCreateFile: "Failed to create file", + ErrCloseFile: "Failed to close file", + ErrRemoveAll: "Failed to remove all files", + ErrReadDir: "Failed to read directory", + ErrCopyFile: "Failed to copy file", + ErrWriteFile: "Failed to write file", + ErrWalkDir: "Failed to walk directory", + ErrStat: "Failed to get file info", + ErrMkdirAll: "Failed to create directory", + ErrNotExist: "File does not exist", + ErrInvalidRule: "Invalid rule", + ErrMatchRule: "Failed to match rule", + ErrInternal: "Internal error", + ErrRunCmd: "Failed to run command", + ErrInvalidJSON: "Invalid JSON", + ErrGetwd: "Failed to get working directory", + ErrSetupRule: "Failed to setup rule", + ErrParseCode: "Failed to parse Go source code", + ErrAbsPath: "Failed to get absolute path", + ErrNotModularized: "Not a modularized project", + ErrGetExecutable: "Failed to get executable", + ErrInstrument: "Failed to instrument", +} + +type PlentifulError struct { + ErrorMsg string + Reason string + Cause string + Details map[string]string +} + +func (e *PlentifulError) Error() string { + str := "" + str += fmt.Sprintf("%-11s: %v\n", "Error", e.ErrorMsg) + str += fmt.Sprintf("%-11s: %v\n", "Reason", e.Reason) + str += fmt.Sprintf("%-11s: %v\n", "Cause", e.Cause) + for k, v := range e.Details { + str += fmt.Sprintf("%-11s: %v\n", "Detail."+k, v) + } + return str +} + +func New(code int, message string) *PlentifulError { + e := &PlentifulError{ + ErrorMsg: errMessages[code], + Reason: message, + Details: make(map[string]string), + } + stackTrace := debug.Stack() + e.Cause = string(stackTrace) + return e +} + +func (pe *PlentifulError) With(key, value string) *PlentifulError { + pe.Details[key] = value + return pe +} + +func Adhere(err error, key, value string) error { + if perr, ok := err.(*PlentifulError); ok { + perr.Details[key] = value + return perr + } + return err +} diff --git a/tool/instrument/inst_file.go b/tool/instrument/inst_file.go index 10299002..38318c19 100644 --- a/tool/instrument/inst_file.go +++ b/tool/instrument/inst_file.go @@ -19,23 +19,23 @@ import ( "path/filepath" "strings" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/resource" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" ) func (rp *RuleProcessor) applyFileRules(bundle *resource.RuleBundle) (err error) { for _, rule := range bundle.FileRules { if rule.FileName == "" { - return fmt.Errorf("file rule must have a file name") + return errc.New(errc.ErrInvalidRule, "no file name") } // Decorate the source code to remove //go:build exclude // and rename package name source, err := util.ReadFile(rule.FileName) if err != nil { - return fmt.Errorf("failed to read file %s: %w", rule.FileName, err) + return errc.Adhere(err, "file", rule.FileName) } - source = shared.RemoveGoBuildComment(source) + source = util.RemoveGoBuildComment(source) // Get last section of file path as file name fileName := filepath.Base(rule.FileName) @@ -43,7 +43,7 @@ func (rp *RuleProcessor) applyFileRules(bundle *resource.RuleBundle) (err error) fmt.Sprintf("otel_inst_file_%s", fileName)) _, err = util.WriteFile(target, source) if err != nil { - return fmt.Errorf("failed to write extra file %s: %w", target, err) + return err } // Relocate the file dependency of the rule, any rules targeting the // file dependency specified by the rule should be updated to target the @@ -56,7 +56,10 @@ func (rp *RuleProcessor) applyFileRules(bundle *resource.RuleBundle) (err error) return strings.HasSuffix(arg, fileName) }) if err != nil { - return fmt.Errorf("failed to replace %v %w", fileName, err) + err = errc.Adhere(err, "compileArgs", + strings.Join(rp.compileArgs, " ")) + err = errc.Adhere(err, "newArg", target) + return err } } else { rp.addCompileArg(target) diff --git a/tool/instrument/inst_func.go b/tool/instrument/inst_func.go index 3c45c8f5..c4e099f9 100644 --- a/tool/instrument/inst_func.go +++ b/tool/instrument/inst_func.go @@ -16,14 +16,13 @@ package instrument import ( "fmt" - "os" "path/filepath" "regexp" "sort" "strings" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/resource" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" "github.com/dave/dst" ) @@ -47,7 +46,7 @@ func (rp *RuleProcessor) copyOtelApi(pkgName string) error { target := filepath.Join(rp.workDir, OtelAPIFile) file, err := resource.CopyAPITo(target, pkgName) if err != nil { - return fmt.Errorf("failed to copy otel api: %w", err) + return err } rp.addCompileArg(file) return nil @@ -55,22 +54,23 @@ func (rp *RuleProcessor) copyOtelApi(pkgName string) error { func (rp *RuleProcessor) loadAst(filePath string) (*dst.File, error) { file := rp.tryRelocated(filePath) - return shared.ParseAstFromFile(file) + return util.ParseAstFromFile(file) } func (rp *RuleProcessor) restoreAst(filePath string, root *dst.File) (string, error) { filePath = rp.tryRelocated(filePath) name := filepath.Base(filePath) - newFile, err := shared.WriteAstToFile(root, filepath.Join(rp.workDir, name)) + newFile, err := util.WriteAstToFile(root, filepath.Join(rp.workDir, name)) if err != nil { - return "", fmt.Errorf("failed to write ast to file: %w", err) + return "", err } err = rp.replaceCompileArg(newFile, func(arg string) bool { return arg == filePath }) if err != nil { - return "", fmt.Errorf("failed to replace compile arg: %w %v %v||%v", - err, filePath, rp.compileArgs, os.Args) + err = errc.Adhere(err, "compileArgs", strings.Join(rp.compileArgs, " ")) + err = errc.Adhere(err, "newArg", newFile) + return "", err } return newFile, nil } @@ -127,10 +127,10 @@ func (rp *RuleProcessor) insertTJump(t *resource.InstFuncRule, // Arguments for onEnter trampoline args := make([]dst.Expr, 0) // Receiver as argument for trampoline func, if any - if shared.HasReceiver(funcDecl) { + if util.HasReceiver(funcDecl) { if recv := funcDecl.Recv.List; recv != nil { receiver := recv[0].Names[0].Name - args = append(args, shared.AddressOf(shared.Ident(receiver))) + args = append(args, util.AddressOf(util.Ident(receiver))) } else { util.Unimplemented() } @@ -138,7 +138,7 @@ func (rp *RuleProcessor) insertTJump(t *resource.InstFuncRule, // Original function arguments as arguments for trampoline func for _, field := range funcDecl.Type.Params.List { for _, name := range field.Names { - args = append(args, shared.AddressOf(shared.Ident(name.Name))) + args = append(args, util.AddressOf(util.Ident(name.Name))) } } @@ -148,32 +148,32 @@ func (rp *RuleProcessor) insertTJump(t *resource.InstFuncRule, // Generate the trampoline-jump-if. N.B. Note that future optimization pass // heavily depends on the structure of trampoline-jump-if. Any change in it // should be carefully examined. - onEnterCall := shared.CallTo(rp.makeName(t, true), args) - onExitCall := shared.CallTo(rp.makeName(t, false), func() []dst.Expr { + onEnterCall := util.CallTo(rp.makeName(t, true), args) + onExitCall := util.CallTo(rp.makeName(t, false), func() []dst.Expr { // NB. DST framework disallows duplicated node in the // AST tree, we need to replicate the return values // as they are already used in return statement above clone := make([]dst.Expr, len(retVals)+1) - clone[0] = shared.Ident(TrampolineCallContextName + varSuffix) + clone[0] = util.Ident(TrampolineCallContextName + varSuffix) for i := 1; i < len(clone); i++ { - clone[i] = shared.AddressOf(retVals[i-1]) + clone[i] = util.AddressOf(retVals[i-1]) } return clone }()) - tjumpInit := shared.DefineStmts( - shared.Exprs( - shared.Ident(TrampolineCallContextName+varSuffix), - shared.Ident(TrampolineSkipName+varSuffix), + tjumpInit := util.DefineStmts( + util.Exprs( + util.Ident(TrampolineCallContextName+varSuffix), + util.Ident(TrampolineSkipName+varSuffix), ), - shared.Exprs(onEnterCall), + util.Exprs(onEnterCall), ) - tjumpCond := shared.Ident(TrampolineSkipName + varSuffix) - tjumpBody := shared.BlockStmts( - shared.ExprStmt(onExitCall), - shared.ReturnStmt(retVals), + tjumpCond := util.Ident(TrampolineSkipName + varSuffix) + tjumpBody := util.BlockStmts( + util.ExprStmt(onExitCall), + util.ReturnStmt(retVals), ) - tjumpElse := shared.Block(shared.DeferStmt(onExitCall)) - tjump := shared.IfStmt(tjumpInit, tjumpCond, tjumpBody, tjumpElse) + tjumpElse := util.Block(util.DeferStmt(onExitCall)) + tjump := util.IfStmt(tjumpInit, tjumpCond, tjumpBody, tjumpElse) // Add this trampoline-jump-if as optimization candidates rp.trampolineJumps = append(rp.trampolineJumps, &TJump{ target: funcDecl, @@ -214,7 +214,7 @@ func (rp *RuleProcessor) insertTJump(t *resource.InstFuncRule, if ifStmt, ok := firstStmt.(*dst.IfStmt); ok { point := findJumpPoint(ifStmt) if point != nil { - point.List = append(point.List, shared.EmptyStmt()) + point.List = append(point.List, util.EmptyStmt()) point.List = append(point.List, tjump) found = true } @@ -259,7 +259,7 @@ func (rp *RuleProcessor) insertRaw(r *resource.InstFuncRule, decl *dst.FuncDecl) util.Assert(r.OnEnter != "" || r.OnExit != "", "sanity check") if r.OnEnter != "" { // Prepend raw code snippet to function body for onEnter - onEnterSnippet, err := shared.ParseAstFromSnippet(r.OnEnter) + onEnterSnippet, err := util.ParseAstFromSnippet(r.OnEnter) if err != nil { return err } @@ -267,7 +267,7 @@ func (rp *RuleProcessor) insertRaw(r *resource.InstFuncRule, decl *dst.FuncDecl) } if r.OnExit != "" { // Use defer func(){ raw_code_snippet }() for onExit - onExitSnippet, err := shared.ParseAstFromSnippet( + onExitSnippet, err := util.ParseAstFromSnippet( fmt.Sprintf("defer func(){ %s }()", r.OnExit), ) if err != nil { @@ -284,7 +284,7 @@ func nameReturnValues(funcDecl *dst.FuncDecl) { for _, field := range funcDecl.Type.Results.List { if field.Names == nil { name := fmt.Sprintf("retVal%d", idx) - field.Names = []*dst.Ident{shared.Ident(name)} + field.Names = []*dst.Ident{util.Ident(name)} idx++ } } @@ -301,15 +301,15 @@ func sortFuncRules(fnRules []*resource.InstFuncRule) []*resource.InstFuncRule { func (rp *RuleProcessor) writeTrampoline(pkgName string) error { // Prepare trampoline code header code := "package " + pkgName - trampoline, err := shared.ParseAstFromSource(code) + trampoline, err := util.ParseAstFromSource(code) if err != nil { - return fmt.Errorf("failed to parse trampoline code header: %w", err) + return err } // One trampoline file shares common variable declarations trampoline.Decls = append(trampoline.Decls, rp.varDecls...) // Write trampoline code to file path := filepath.Join(rp.workDir, OtelTrampolineFile) - trampolineFile, err := shared.WriteAstToFile(trampoline, path) + trampolineFile, err := util.WriteAstToFile(trampoline, path) if err != nil { return err } @@ -326,7 +326,7 @@ func (rp *RuleProcessor) applyFuncRules(bundle *resource.RuleBundle) (err error) // Copy API file to compilation working directory err = rp.copyOtelApi(bundle.PackageName) if err != nil { - return fmt.Errorf("failed to copy otel api: %w", err) + return err } // Applied all matched func rules, either inserting raw code or inserting @@ -335,7 +335,7 @@ func (rp *RuleProcessor) applyFuncRules(bundle *resource.RuleBundle) (err error) util.Assert(filepath.IsAbs(file), "file path must be absolute") astRoot, err := rp.loadAst(file) if err != nil { - return fmt.Errorf("failed to load ast from file: %w", err) + return err } rp.target = astRoot rp.trampolineJumps = make([]*TJump, 0) @@ -344,7 +344,7 @@ func (rp *RuleProcessor) applyFuncRules(bundle *resource.RuleBundle) (err error) nameAndRecvType := strings.Split(fnName, ",") name := nameAndRecvType[0] recvType := nameAndRecvType[1] - if shared.MatchFuncDecl(decl, name, recvType) { + if util.MatchFuncDecl(decl, name, recvType) { fnDecl := decl.(*dst.FuncDecl) // Add explicit names for return values, they can be further // referenced if we're willing @@ -359,8 +359,7 @@ func (rp *RuleProcessor) applyFuncRules(bundle *resource.RuleBundle) (err error) err = rp.insertTJump(rule, fnDecl) } if err != nil { - return fmt.Errorf("failed to rewrite: %w for %v", - err, rule) + return err } util.Log("Apply func rule %s", rule) } @@ -371,25 +370,25 @@ func (rp *RuleProcessor) applyFuncRules(bundle *resource.RuleBundle) (err error) // Optimize generated trampoline-jump-ifs err = rp.optimizeTJumps() if err != nil { - return fmt.Errorf("failed to optimize trampoline jumps: %w", err) + return err } // Restore the ast to original file once all rules are applied filePath, err := rp.restoreAst(file, astRoot) if err != nil { - return fmt.Errorf("failed to restore ast: %w", err) + return err } // Wait, all above code snippets should be inlined to new line to // avoid potential misbehaviors during debugging err = rp.inliningTJump(filePath) if err != nil { - return fmt.Errorf("failed to inline trampoline call: %w", err) + return err } rp.saveDebugFile(filePath) } err = rp.writeTrampoline(bundle.PackageName) if err != nil { - return fmt.Errorf("failed to write trampoline: %w", err) + return err } return nil } diff --git a/tool/instrument/inst_struct.go b/tool/instrument/inst_struct.go index 1e1cc812..847c3335 100644 --- a/tool/instrument/inst_struct.go +++ b/tool/instrument/inst_struct.go @@ -15,11 +15,9 @@ package instrument import ( - "fmt" "path/filepath" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/resource" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" "github.com/dave/dst" ) @@ -28,7 +26,7 @@ func addStructField(rule *resource.InstStructRule, decl dst.Decl) { util.Assert(rule.FieldName != "" && rule.FieldType != "", "rule must have field and type") util.Log("Apply struct rule %v", rule) - shared.AddStructField(decl, rule.FieldName, rule.FieldType) + util.AddStructField(decl, rule.FieldName, rule.FieldType) } func (rp *RuleProcessor) applyStructRules(bundle *resource.RuleBundle) error { @@ -37,11 +35,11 @@ func (rp *RuleProcessor) applyStructRules(bundle *resource.RuleBundle) error { // Apply struct rules to the file astRoot, err := rp.loadAst(file) if err != nil { - return fmt.Errorf("failed to load ast from file: %w", err) + return err } for _, decl := range astRoot.Decls { for structName, rules := range struct2Rules { - if shared.MatchStructDecl(decl, structName) { + if util.MatchStructDecl(decl, structName) { for _, rule := range rules { addStructField(rule, decl) } @@ -52,7 +50,7 @@ func (rp *RuleProcessor) applyStructRules(bundle *resource.RuleBundle) error { // in future compilation newFile, err := rp.restoreAst(file, astRoot) if err != nil { - return fmt.Errorf("failed to restore ast: %w", err) + return err } rp.saveDebugFile(newFile) } diff --git a/tool/instrument/instrument.go b/tool/instrument/instrument.go index d09d95b5..67d18963 100644 --- a/tool/instrument/instrument.go +++ b/tool/instrument/instrument.go @@ -15,15 +15,13 @@ package instrument import ( - "errors" - "fmt" "os" "path/filepath" "strings" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/config" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/resource" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" "github.com/dave/dst" ) @@ -108,7 +106,7 @@ func (rp *RuleProcessor) replaceCompileArg(newArg string, pred func(string) bool // instrumented file(path), which is also an absolute path arg, err := filepath.Abs(arg) if err != nil { - return fmt.Errorf("failed to get absolute path: %w", err) + return errc.New(errc.ErrAbsPath, err.Error()) } if pred(arg) { rp.compileArgs[i] = newArg @@ -118,7 +116,7 @@ func (rp *RuleProcessor) replaceCompileArg(newArg string, pred func(string) bool return nil } } - return errors.New("no matching compile arg found") + return errc.New(errc.ErrInstrument, "can not innstrument the file "+newArg) } func (rp *RuleProcessor) saveDebugFile(path string) { @@ -130,7 +128,7 @@ func (rp *RuleProcessor) saveDebugFile(path string) { dest := filepath.Base(path) util.Assert(rp.packageName != "", "sanity check") dest = filepath.Join(escape(rp.packageName), dest) - dest = shared.GetInstrumentLogPath(dest) + dest = util.GetInstrumentLogPath(dest) err := os.MkdirAll(filepath.Dir(dest), os.ModePerm) if err != nil { // error is tolerable here util.Log("failed to create debug file directory %s: %v", dest, err) @@ -146,17 +144,17 @@ func (rp *RuleProcessor) applyRules(bundle *resource.RuleBundle) (err error) { // Apply file instrument rules first err = rp.applyFileRules(bundle) if err != nil { - return fmt.Errorf("failed to apply file rules: %w", err) + return err } err = rp.applyStructRules(bundle) if err != nil { - return fmt.Errorf("failed to apply struct rules: %w", err) + return err } err = rp.applyFuncRules(bundle) if err != nil { - return fmt.Errorf("failed to apply function rules: %w %v", err, bundle) + return err } return nil @@ -175,32 +173,41 @@ func matchImportPath(importPath string, args []string) bool { func guaranteeVersion(bundle *resource.RuleBundle, candidates []string) error { for _, candidate := range candidates { // It's not a go file, ignore silently - if !shared.IsGoFile(candidate) { + if !util.IsGoFile(candidate) { continue } - version := shared.ExtractVersion(candidate) + version := util.ExtractVersion(candidate) for _, funcRules := range bundle.File2FuncRules { for _, rules := range funcRules { for _, rule := range rules { - matched, err := shared.MatchVersion(version, rule.GetVersion()) + matched, err := util.MatchVersion(version, rule.GetVersion()) if err != nil || !matched { - return fmt.Errorf("failed to match version %v", err) + err = errc.Adhere(err, "rule", rule.String()) + err = errc.Adhere(err, "candidate", candidate) + err = errc.Adhere(err, "version", version) + return err } } } } for _, fileRule := range bundle.FileRules { - matched, err := shared.MatchVersion(version, fileRule.GetVersion()) + matched, err := util.MatchVersion(version, fileRule.GetVersion()) if err != nil || !matched { - return fmt.Errorf("failed to match version %v", err) + err = errc.Adhere(err, "rule", fileRule.String()) + err = errc.Adhere(err, "candidate", candidate) + err = errc.Adhere(err, "version", version) + return err } } for _, structRules := range bundle.File2StructRules { for _, rules := range structRules { for _, rule := range rules { - matched, err := shared.MatchVersion(version, rule.GetVersion()) + matched, err := util.MatchVersion(version, rule.GetVersion()) if err != nil || !matched { - return fmt.Errorf("failed to match version %v", err) + err = errc.Adhere(err, "rule", rule.String()) + err = errc.Adhere(err, "candidate", candidate) + err = errc.Adhere(err, "version", version) + return err } } } @@ -217,12 +224,11 @@ func compileRemix(bundle *resource.RuleBundle, args []string) error { rp := newRuleProcessor(args, bundle.PackageName) err := rp.applyRules(bundle) if err != nil { - return fmt.Errorf("failed to apply rules: %w", err) + return err } // Good, run final compilation after instrumentation err = util.RunCmd(rp.compileArgs...) - util.Log("RunCmd: %v (%v)", - bundle.ImportPath, rp.compileArgs) + util.Log("RunCmd: %v (%v)", bundle.ImportPath, rp.compileArgs) return err } @@ -230,13 +236,13 @@ func Instrument() error { // Remove the tool itself from the command line arguments args := os.Args[2:] // Is compile command? - if shared.IsCompileCommand(strings.Join(args, " ")) { + if util.IsCompileCommand(strings.Join(args, " ")) { if config.GetConf().Verbose { util.Log("RunCmd: %v", args) } bundles, err := resource.LoadRuleBundles() if err != nil { - return fmt.Errorf("failed to load rule bundles: %w", err) + return err } for _, bundle := range bundles { util.Assert(bundle.IsValid(), "sanity check") diff --git a/tool/instrument/optimize.go b/tool/instrument/optimize.go index d157e0c8..f67bed6d 100644 --- a/tool/instrument/optimize.go +++ b/tool/instrument/optimize.go @@ -19,8 +19,8 @@ import ( "strings" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/config" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/resource" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" "github.com/dave/dst" ) @@ -100,7 +100,7 @@ type TJump struct { } func newDecoratedEmptyStmt() *dst.EmptyStmt { - emptyStmt := shared.EmptyStmt() + emptyStmt := util.EmptyStmt() emptyStmt.Decorations().Start.Append(TrampolineNoNewlinePlaceholder) emptyStmt.Decorations().End.Append(TrampolineSemicolonPlaceholder) return emptyStmt @@ -139,7 +139,7 @@ func replenishCallContextLiteral(tjump *TJump, expr dst.Expr) { rawFunc := tjump.target names := make([]dst.Expr, 0) for _, name := range getNames(rawFunc.Type.Params) { - names = append(names, shared.AddressOf(shared.Ident(name))) + names = append(names, util.AddressOf(util.Ident(name))) } elems := expr.(*dst.UnaryExpr).X.(*dst.CompositeLit).Elts paramLiteral := elems[0].(*dst.KeyValueExpr).Value.(*dst.CompositeLit) @@ -150,9 +150,9 @@ func (rp *RuleProcessor) newCallContextImpl(tjump *TJump) (dst.Expr, error) { // One line please, otherwise debugging line number will be a nightmare tmpl := fmt.Sprintf("&CallContextImpl%s{Params:[]interface{}{},ReturnVals:[]interface{}{}}", rp.rule2Suffix[tjump.rule]) - astRoot, err := shared.ParseAstFromSnippet(tmpl) + astRoot, err := util.ParseAstFromSnippet(tmpl) if err != nil { - return nil, fmt.Errorf("failed to parse new CallContext: %w", err) + return nil, err } ctxExpr := astRoot[0].(*dst.ExprStmt).X // Replenish call context by passing addresses of all arguments @@ -164,7 +164,7 @@ func (rp *RuleProcessor) removeOnEnterTrampolineCall(tjump *TJump) error { // Construct CallContext on the fly and pass to onExit trampoline defer call callContextExpr, err := rp.newCallContextImpl(tjump) if err != nil { - return fmt.Errorf("failed to construct CallContext: %w", err) + return err } // Find defer call to onExit and replace its call context with new one found := false @@ -182,8 +182,8 @@ func (rp *RuleProcessor) removeOnEnterTrampolineCall(tjump *TJump) error { // Rewrite condition of trampoline-jump-if to always false and null out its // initialization statement and then block tjump.ifStmt.Init = nil - tjump.ifStmt.Cond = shared.BoolFalse() - tjump.ifStmt.Body = shared.Block(newDecoratedEmptyStmt()) + tjump.ifStmt.Cond = util.BoolFalse() + tjump.ifStmt.Body = util.Block(newDecoratedEmptyStmt()) if config.GetConf().Verbose { util.Log("Optimize tjump branch in %s", tjump.target.Name.Name) } @@ -196,7 +196,7 @@ func (rp *RuleProcessor) removeOnEnterTrampolineCall(tjump *TJump) error { return false }) if removed == nil { - return fmt.Errorf("failed to remove onEnter trampoline function") + return errc.New(errc.ErrInternal, "onEnter trampoline not found") } return nil } @@ -206,19 +206,19 @@ func flattenTJump(tjump *TJump, removedOnExit bool) { initStmt := ifStmt.Init.(*dst.AssignStmt) util.Assert(len(initStmt.Lhs) == 2, "must be") - ifStmt.Cond = shared.BoolFalse() - ifStmt.Body = shared.Block(newDecoratedEmptyStmt()) + ifStmt.Cond = util.BoolFalse() + ifStmt.Body = util.Block(newDecoratedEmptyStmt()) if removedOnExit { // We removed the last reference to call context after nulling out body // block, at this point, all lhs are unused, replace assignment to simple // function call - ifStmt.Init = shared.ExprStmt(initStmt.Rhs[0]) + ifStmt.Init = util.ExprStmt(initStmt.Rhs[0]) // TODO: Remove onExit declaration } else { // Otherwise, mark skipCall identifier as unused skipCallIdent := initStmt.Lhs[1].(*dst.Ident) - shared.MakeUnusedIdent(skipCallIdent) + util.MakeUnusedIdent(skipCallIdent) } if config.GetConf().Verbose { util.Log("Optimize skipCall in %s", tjump.target.Name.Name) @@ -239,7 +239,7 @@ func (rp *RuleProcessor) optimizeTJumps() (err error) { if rule.OnExit == "" { err = rp.removeOnExitTrampolineCall(tjump) if err != nil { - return fmt.Errorf("failed to optimize tjump: %w", err) + return err } removedOnExit = true } @@ -249,7 +249,7 @@ func (rp *RuleProcessor) optimizeTJumps() (err error) { if rule.OnEnter == "" { err = rp.removeOnEnterTrampolineCall(tjump) if err != nil { - return fmt.Errorf("failed to optimize tjump: %w", err) + return err } } @@ -261,7 +261,7 @@ func (rp *RuleProcessor) optimizeTJumps() (err error) { if rule.OnEnter != "" { onEnterHook, err := getHookFunc(rule, true) if err != nil { - return fmt.Errorf("failed to get hook: %w", err) + return err } foundPoison := false const poison = "SkipCall" diff --git a/tool/instrument/trampoline.go b/tool/instrument/trampoline.go index 52406bde..fa664f0f 100644 --- a/tool/instrument/trampoline.go +++ b/tool/instrument/trampoline.go @@ -16,13 +16,11 @@ package instrument import ( _ "embed" - "errors" - "fmt" "go/token" "strconv" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/resource" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" "github.com/dave/dst" ) @@ -73,9 +71,9 @@ var trampolineTemplate string func (rp *RuleProcessor) materializeTemplate() error { // Read trampoline template and materialize onEnter and onExit function // declarations based on that - astRoot, err := shared.ParseAstFromSource(trampolineTemplate) + astRoot, err := util.ParseAstFromSource(trampolineTemplate) if err != nil { - return fmt.Errorf("failed to parse trampoline template: %w", err) + return err } rp.varDecls = make([]dst.Decl, 0) @@ -89,7 +87,7 @@ func (rp *RuleProcessor) materializeTemplate() error { } else if decl.Name.Name == TrampolineOnExitName { rp.onExitHookFunc = decl rp.addDecl(decl) - } else if shared.HasReceiver(decl) { + } else if util.HasReceiver(decl) { // We know exactly this is CallContextImpl method t := decl.Recv.List[0].Type.(*dst.StarExpr).X.(*dst.Ident).Name util.Assert(t == TrampolineCallContextImplType, "sanity check") @@ -127,9 +125,9 @@ func getNames(list *dst.FieldList) []string { func makeOnXName(t *resource.InstFuncRule, onEnter bool) string { if onEnter { - return shared.GetVarNameOfFunc(t.OnEnter) + return util.GetVarNameOfFunc(t.OnEnter) } else { - return shared.GetVarNameOfFunc(t.OnExit) + return util.GetVarNameOfFunc(t.OnExit) } } @@ -142,42 +140,43 @@ type ParamTrait struct { func getHookFunc(t *resource.InstFuncRule, onEnter bool) (*dst.FuncDecl, error) { file, err := resource.FindHookFile(t) if err != nil { - return nil, fmt.Errorf("failed to read file %s: %w", t, err) + return nil, err } - astRoot, err := shared.ParseAstFromFile(file) + astRoot, err := util.ParseAstFromFile(file) if err != nil { - return nil, fmt.Errorf("failed to parse ast from file: %w", err) + return nil, err } var target *dst.FuncDecl if onEnter { - target = shared.FindFuncDecl(astRoot, t.OnEnter) + target = util.FindFuncDecl(astRoot, t.OnEnter) } else { - target = shared.FindFuncDecl(astRoot, t.OnExit) + target = util.FindFuncDecl(astRoot, t.OnExit) } if target != nil { return target, nil } if onEnter { - return nil, fmt.Errorf("failed to find hook func %s", t.OnEnter) + err = errc.Adhere(err, "hook", t.OnEnter) } else { - return nil, fmt.Errorf("failed to find hook func %s", t.OnExit) + err = errc.Adhere(err, "hook", t.OnExit) } + return nil, err } func getHookParamTraits(t *resource.InstFuncRule, onEnter bool) ([]ParamTrait, error) { target, err := getHookFunc(t, onEnter) if err != nil { - return nil, fmt.Errorf("failed to get hook func: %w", err) + return nil, err } var attrs []ParamTrait // Find which parameter is type of interface{} for i, field := range target.Type.Params.List { attr := ParamTrait{Index: i} - if shared.IsInterfaceType(field.Type) { + if util.IsInterfaceType(field.Type) { attr.IsInterfaceAny = true } - if shared.IsEllipsis(field.Type) { + if util.IsEllipsis(field.Type) { attr.IsVaradic = true } attrs = append(attrs, attr) @@ -187,7 +186,7 @@ func getHookParamTraits(t *resource.InstFuncRule, onEnter bool) ([]ParamTrait, e func (rp *RuleProcessor) callOnEnterHook(t *resource.InstFuncRule, traits []ParamTrait) error { if len(traits) != (len(rp.onEnterHookFunc.Type.Params.List) + 1 /*CallContext*/) { - return errors.New("hook param traits mismatch on enter hook") + return errc.New(errc.ErrInternal, "mismatched param traits") } // Hook: func onEnterFoo(callContext* CallContext, p*[]int) // Trampoline: func OtelOnEnterTrampoline_foo(p *[]int) @@ -196,18 +195,18 @@ func (rp *RuleProcessor) callOnEnterHook(t *resource.InstFuncRule, traits []Para trait := traits[idx+1 /*CallContext*/] for _, name := range field.Names { // syntax of n1,n2 type if trait.IsVaradic { - args = append(args, shared.DereferenceOf(shared.Ident(name.Name+"..."))) + args = append(args, util.DereferenceOf(util.Ident(name.Name+"..."))) } else { - args = append(args, shared.DereferenceOf(dst.NewIdent(name.Name))) + args = append(args, util.DereferenceOf(dst.NewIdent(name.Name))) } } } // Call onEnter if it exists fnName := makeOnXName(t, true) - call := shared.ExprStmt(shared.CallTo(fnName, args)) - iff := shared.IfNotNilStmt( + call := util.ExprStmt(util.CallTo(fnName, args)) + iff := util.IfNotNilStmt( dst.NewIdent(fnName), - shared.Block(call), + util.Block(call), nil, ) insertAt(rp.onEnterHookFunc, iff, len(rp.onEnterHookFunc.Body.List)-1) @@ -216,7 +215,7 @@ func (rp *RuleProcessor) callOnEnterHook(t *resource.InstFuncRule, traits []Para func (rp *RuleProcessor) callOnExitHook(t *resource.InstFuncRule, traits []ParamTrait) error { if len(traits) != len(rp.onExitHookFunc.Type.Params.List) { - return errors.New("hook param traits mismatch on exit hook") + return errc.New(errc.ErrInternal, "mismatched param traits") } // Hook: func onExitFoo(ctx* CallContext, p*[]int) // Trampoline: func OtelOnExitTrampoline_foo(ctx* CallContext, p *[]int) @@ -229,20 +228,20 @@ func (rp *RuleProcessor) callOnExitHook(t *resource.InstFuncRule, traits []Param trait := traits[idx] for _, name := range field.Names { // syntax of n1,n2 type if trait.IsVaradic { - arg := shared.DereferenceOf(shared.Ident(name.Name + "...")) + arg := util.DereferenceOf(util.Ident(name.Name + "...")) args = append(args, arg) } else { - arg := shared.DereferenceOf(dst.NewIdent(name.Name)) + arg := util.DereferenceOf(dst.NewIdent(name.Name)) args = append(args, arg) } } } // Call onExit if it exists fnName := makeOnXName(t, false) - call := shared.ExprStmt(shared.CallTo(fnName, args)) - iff := shared.IfNotNilStmt( + call := util.ExprStmt(util.CallTo(fnName, args)) + iff := util.IfNotNilStmt( dst.NewIdent(fnName), - shared.Block(call), + util.Block(call), nil, ) insertAtEnd(rp.onExitHookFunc, iff) @@ -251,14 +250,13 @@ func (rp *RuleProcessor) callOnExitHook(t *resource.InstFuncRule, traits []Param func rectifyAnyType(paramList *dst.FieldList, traits []ParamTrait) error { if len(paramList.List) != len(traits) { - return fmt.Errorf("param list length mismatch: %d vs %d", - len(paramList.List), len(traits)) + return errc.New(errc.ErrInternal, "mismatched param traits") } for i, field := range paramList.List { trait := traits[i] if trait.IsInterfaceAny { // Rectify type to "interface{}" - field.Type = shared.InterfaceType() + field.Type = util.InterfaceType() } } return nil @@ -272,11 +270,11 @@ func (rp *RuleProcessor) addHookFuncVar(t *resource.InstFuncRule, // raw function is not exposed err := rectifyAnyType(paramTypes, traits) if err != nil { - return fmt.Errorf("failed to rectify any type on enter: %w", err) + return err } // Generate var decl - varDecl := shared.NewVarDecl(makeOnXName(t, onEnter), paramTypes) + varDecl := util.NewVarDecl(makeOnXName(t, onEnter), paramTypes) rp.addDecl(varDecl) return nil } @@ -316,7 +314,7 @@ func (rp *RuleProcessor) renameFunc(t *resource.InstFuncRule) { } func addCallContext(list *dst.FieldList) { - callCtx := shared.NewField( + callCtx := util.NewField( TrampolineCallContextName, dst.NewIdent(TrampolineCallContextType), ) @@ -326,7 +324,7 @@ func addCallContext(list *dst.FieldList) { func (rp *RuleProcessor) buildTrampolineType(onEnter bool) *dst.FieldList { paramList := &dst.FieldList{List: []*dst.Field{}} if onEnter { - if shared.HasReceiver(rp.rawFunc) { + if util.HasReceiver(rp.rawFunc) { recvField := dst.Clone(rp.rawFunc.Recv.List[0]).(*dst.Field) paramList.List = append(paramList.List, recvField) } @@ -357,7 +355,7 @@ func (rp *RuleProcessor) rectifyTypes() { for i := 0; i < len(list.List); i++ { paramField := list.List[i] paramFieldType := desugarType(paramField) - paramField.Type = shared.DereferenceOf(paramFieldType) + paramField.Type = util.DereferenceOf(paramFieldType) } } addCallContext(onExitHookFunc.Type.Params) @@ -381,7 +379,7 @@ func (rp *RuleProcessor) replenishCallContext(onEnter bool) bool { // SKip first callContext parameter for onExit continue } - elems = append(elems, shared.Ident(name)) + elems = append(elems, util.Ident(name)) } compositeLit.Elts = elems return true @@ -432,19 +430,19 @@ func (rp *RuleProcessor) implementCallContext(t *resource.InstFuncRule) { func setValue(field string, idx int, typ dst.Expr) *dst.CaseClause { // *(c.Params[idx].(*int)) = val.(int) // c.Params[idx] = val iff type is interface{} - se := shared.SelectorExpr(shared.Ident(TrampolineCtxIdentifier), field) - ie := shared.IndexExpr(se, shared.IntLit(idx)) - te := shared.TypeAssertExpr(ie, shared.DereferenceOf(typ)) - pe := shared.ParenExpr(te) - de := shared.DereferenceOf(pe) - val := shared.Ident(TrampolineValIdentifier) - assign := shared.AssignStmt(de, shared.TypeAssertExpr(val, typ)) - if shared.IsInterfaceType(typ) { - assign = shared.AssignStmt(ie, val) - } - caseClause := shared.SwitchCase( - shared.Exprs(shared.IntLit(idx)), - shared.Stmts(assign), + se := util.SelectorExpr(util.Ident(TrampolineCtxIdentifier), field) + ie := util.IndexExpr(se, util.IntLit(idx)) + te := util.TypeAssertExpr(ie, util.DereferenceOf(typ)) + pe := util.ParenExpr(te) + de := util.DereferenceOf(pe) + val := util.Ident(TrampolineValIdentifier) + assign := util.AssignStmt(de, util.TypeAssertExpr(val, typ)) + if util.IsInterfaceType(typ) { + assign = util.AssignStmt(ie, val) + } + caseClause := util.SwitchCase( + util.Exprs(util.IntLit(idx)), + util.Stmts(assign), ) return caseClause } @@ -452,18 +450,18 @@ func setValue(field string, idx int, typ dst.Expr) *dst.CaseClause { func getValue(field string, idx int, typ dst.Expr) *dst.CaseClause { // return *(c.Params[idx].(*int)) // return c.Params[idx] iff type is interface{} - se := shared.SelectorExpr(shared.Ident(TrampolineCtxIdentifier), field) - ie := shared.IndexExpr(se, shared.IntLit(idx)) - te := shared.TypeAssertExpr(ie, shared.DereferenceOf(typ)) - pe := shared.ParenExpr(te) - de := shared.DereferenceOf(pe) - ret := shared.ReturnStmt(shared.Exprs(de)) - if shared.IsInterfaceType(typ) { - ret = shared.ReturnStmt(shared.Exprs(ie)) - } - caseClause := shared.SwitchCase( - shared.Exprs(shared.IntLit(idx)), - shared.Stmts(ret), + se := util.SelectorExpr(util.Ident(TrampolineCtxIdentifier), field) + ie := util.IndexExpr(se, util.IntLit(idx)) + te := util.TypeAssertExpr(ie, util.DereferenceOf(typ)) + pe := util.ParenExpr(te) + de := util.DereferenceOf(pe) + ret := util.ReturnStmt(util.Exprs(de)) + if util.IsInterfaceType(typ) { + ret = util.ReturnStmt(util.Exprs(ie)) + } + caseClause := util.SwitchCase( + util.Exprs(util.IntLit(idx)), + util.Stmts(ret), ) return caseClause } @@ -488,7 +486,7 @@ func setReturnValClause(idx int, typ dst.Expr) *dst.CaseClause { // is type of ...T, it will be converted to []T func desugarType(param *dst.Field) dst.Expr { if ft, ok := param.Type.(*dst.Ellipsis); ok { - return shared.ArrayType(ft.Elt) + return util.ArrayType(ft.Elt) } return param.Type } @@ -525,7 +523,7 @@ func (rp *RuleProcessor) rewriteCallContextImpl() { methodGetRetValBody.List = nil methodSetRetValBody.List = nil idx := 0 - if shared.HasReceiver(rp.rawFunc) { + if util.HasReceiver(rp.rawFunc) { recvType := rp.rawFunc.Recv.List[0].Type clause := setParamClause(idx, recvType) methodSetParamBody.List = append(methodSetParamBody.List, clause) @@ -567,11 +565,11 @@ func (rp *RuleProcessor) callHookFunc(t *resource.InstFuncRule, onEnter bool) error { traits, err := getHookParamTraits(t, onEnter) if err != nil { - return fmt.Errorf("failed to get hook param traits: %w", err) + return err } err = rp.addHookFuncVar(t, traits, onEnter) if err != nil { - return fmt.Errorf("failed to add onEnter var hook decl: %w", err) + return err } if onEnter { err = rp.callOnEnterHook(t, traits) @@ -579,10 +577,10 @@ func (rp *RuleProcessor) callHookFunc(t *resource.InstFuncRule, err = rp.callOnExitHook(t, traits) } if err != nil { - return fmt.Errorf("failed to call onEnter: %w", err) + return err } if !rp.replenishCallContext(onEnter) { - return errors.New("failed to replenish context in onEnter hook") + return errc.New(errc.ErrInstrument, "can not rewrite hook function") } return nil } @@ -594,7 +592,7 @@ func (rp *RuleProcessor) generateTrampoline(t *resource.InstFuncRule, // a bunch of manual AST code generation, isn't it? err := rp.materializeTemplate() if err != nil { - return fmt.Errorf("failed to materialize template: %w", err) + return err } // Implement CallContext interface rp.implementCallContext(t) @@ -608,13 +606,13 @@ func (rp *RuleProcessor) generateTrampoline(t *resource.InstFuncRule, if t.OnEnter != "" { err = rp.callHookFunc(t, true) if err != nil { - return fmt.Errorf("failed to call onEnter: %w", err) + return err } } if t.OnExit != "" { err = rp.callHookFunc(t, false) if err != nil { - return fmt.Errorf("failed to call onExit: %w", err) + return err } } return nil diff --git a/tool/preprocess/fetch.go b/tool/preprocess/fetch.go index 019c142b..0d3d8edb 100644 --- a/tool/preprocess/fetch.go +++ b/tool/preprocess/fetch.go @@ -16,14 +16,13 @@ package preprocess import ( "encoding/json" - "fmt" "io/fs" "os" "path/filepath" "strings" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/config" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" ) @@ -39,27 +38,19 @@ type moduleHolder struct { func fetchNetwork(path string) (string, error) { text, err := runModDownload(path) if err != nil { - return "", fmt.Errorf("failed to download rule: %w %v %v", - err, text, path) + return "", err + } var mod *moduleHolder err = json.Unmarshal([]byte(text), &mod) if err != nil { - return "", fmt.Errorf("failed to unmarshal rule: %w %v", - err, path) + return "", errc.New(errc.ErrInvalidJSON, "bad "+path) } if mod.Error != "" { - return "", fmt.Errorf("failed to download rule: %s %v", - mod.Error, path) - } - exist, err := util.PathExists(mod.Dir) - if err != nil { - return "", fmt.Errorf("failed to check path: %w %v", - err, mod.Dir) + return "", errc.New(errc.ErrInvalidJSON, mod.Error) } - if !exist { - return "", fmt.Errorf("failed to find module path: %s", - mod.Dir) + if util.PathNotExists(mod.Dir) { + return "", errc.New(errc.ErrNotExist, mod.Dir) } return mod.Dir, nil } @@ -89,14 +80,14 @@ func (dp *DepProcessor) fetchEmbed(path string) (string, error) { if err != nil { return err } - target := shared.GetPreprocessLogPath(filepath.Join(OtelRuleCache, p)) + target := util.GetPreprocessLogPath(filepath.Join(OtelRuleCache, p)) err = os.MkdirAll(filepath.Dir(target), 0777) if err != nil { - return fmt.Errorf("failed to create directory: %w", err) + return errc.New(errc.ErrMkdirAll, err.Error()) } _, err = util.WriteFile(target, string(data)) if err != nil { - return fmt.Errorf("failed to write file: %w", err) + return err } if config.GetConf().Verbose { util.Log("Copy embed file %v to %v", p, target) @@ -106,33 +97,29 @@ func (dp *DepProcessor) fetchEmbed(path string) (string, error) { err := fs.WalkDir(dp.ruleCache, path, walkFn) if err != nil { - return "", fmt.Errorf("failed to walk dir: %w", err) + return "", errc.New(errc.ErrWalkDir, err.Error()) } // Now all rule files are copied to the local file system, we can return // the path to corresponding local file system - dir := shared.GetPreprocessLogPath(filepath.Join(OtelRuleCache, path)) + dir := util.GetPreprocessLogPath(filepath.Join(OtelRuleCache, path)) return dir, nil } func (dp *DepProcessor) fetchFrom(path string) (string, error) { // Path to local file system, use local directory directly - exist, err := util.PathExists(path) - if err != nil { - return "", fmt.Errorf("failed to check path: %w", err) - } - if exist { + if util.PathExists(path) { util.Log("Fetch %s from local file system", path) return path, nil } // Path to network - if shared.IsModPath(path) { + if util.IsModPath(path) { // If the path points to the network but is an officially provided // module, then we can retrieve it from the embed cache instead of // downloading it from the network. if isStdRulePath(path) { dir, err := dp.fetchEmbed(path) if err != nil { - return "", fmt.Errorf("failed to fetch embed: %w", err) + return "", err } util.Log("Fetch %s from embed cache", path) return dir, nil @@ -141,7 +128,7 @@ func (dp *DepProcessor) fetchFrom(path string) (string, error) { // Download the module to the local file system dir, err := fetchNetwork(path) if err != nil { - return "", fmt.Errorf("failed to download module: %w", err) + return "", err } // Get path to the local module cache util.Log("Fetch %s from network %s", path, dir) @@ -149,7 +136,7 @@ func (dp *DepProcessor) fetchFrom(path string) (string, error) { } // Best effort to find the path but not found, give up - return "", fmt.Errorf("can not fetch path %s", path) + return "", errc.New(errc.ErrNotExist, "cannot fetch "+path) } // fetchRules fetches the rules via the network @@ -174,7 +161,7 @@ func (dp *DepProcessor) fetchRules() error { util.Assert(rule.GetPath() != "", "sanity check") path, err := dp.fetchFrom(rule.GetPath()) if err != nil { - return fmt.Errorf("failed to fetch func rules: %w", err) + return err } resolved[rule.GetPath()] = path rule.SetPath(path) @@ -193,7 +180,7 @@ func (dp *DepProcessor) fetchRules() error { } else { p, err := dp.fetchFrom(fileRule.GetPath()) if err != nil { - return fmt.Errorf("failed to fetch file rules: %w", err) + return err } path = p resolved[fileRule.GetPath()] = path @@ -201,13 +188,8 @@ func (dp *DepProcessor) fetchRules() error { // Further check if the joined file exists file := filepath.Join(path, fileRule.FileName) - exist, err := util.PathExists(file) - if err != nil { - return fmt.Errorf("failed to check file path: %w %v", - err, fileRule.FileName) - } - if !exist { - return fmt.Errorf("failed to find file %v", fileRule.FileName) + if util.PathNotExists(file) { + return errc.New(errc.ErrNotExist, file) } fileRule.FileName = file fileRule.SetPath(path) diff --git a/tool/preprocess/match.go b/tool/preprocess/match.go index ee3cd1b4..1341f927 100644 --- a/tool/preprocess/match.go +++ b/tool/preprocess/match.go @@ -17,24 +17,18 @@ package preprocess import ( "bufio" "encoding/json" - "fmt" "os" "path/filepath" "strings" "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/config" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/resource" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" "github.com/dave/dst" ) -const ( - CompileFlagPattern = "-p" - CompileFlagGoVer = "-goversion" -) - type ruleMatcher struct { availableRules map[string][]resource.InstRule moduleVersions map[string]string // vendor used only @@ -62,8 +56,8 @@ func loadRuleFile(path string) ([]resource.InstRule, error) { content, err := util.ReadFile(path) if err != nil { currentDir, _ := os.Getwd() - return nil, fmt.Errorf("failed to read rule file: %w %v", - err, currentDir) + err = errc.Adhere(err, "pwd", currentDir) + return nil, err } return loadRuleRaw(content) } @@ -72,7 +66,7 @@ func loadRuleRaw(content string) ([]resource.InstRule, error) { var h []*ruleHolder err := json.Unmarshal([]byte(content), &h) if err != nil { - return nil, fmt.Errorf("failed to unmarshal rules: %w", err) + return nil, errc.New(errc.ErrInvalidJSON, err.Error()) } rules := make([]resource.InstRule, 0) for _, rule := range h { @@ -150,7 +144,7 @@ func findAvailableRules() []resource.InstRule { // match gives compilation arguments and finds out all interested rules // for it. func (rm *ruleMatcher) match(cmdArgs []string) *resource.RuleBundle { - importPath := findFlagValue(cmdArgs, CompileFlagPattern) + importPath := findFlagValue(cmdArgs, util.BuildPattern) util.Assert(importPath != "", "sanity check") util.Log("RunMatch: %v (%v)", importPath, cmdArgs) availables := make([]resource.InstRule, len(rm.availableRules[importPath])) @@ -165,13 +159,13 @@ func (rm *ruleMatcher) match(cmdArgs []string) *resource.RuleBundle { parsedAst := make(map[string]*dst.File) bundle := resource.NewRuleBundle(importPath) - goVersion := findFlagValue(cmdArgs, CompileFlagGoVer) + goVersion := findFlagValue(cmdArgs, util.BuildGoVer) util.Assert(goVersion != "", "sanity check") util.Assert(strings.HasPrefix(goVersion, "go"), "sanity check") goVersion = strings.Replace(goVersion, "go", "v", 1) for _, candidate := range cmdArgs { // It's not a go file, ignore silently - if !shared.IsGoFile(candidate) { + if !util.IsGoFile(candidate) { continue } file := candidate @@ -179,7 +173,7 @@ func (rm *ruleMatcher) match(cmdArgs []string) *resource.RuleBundle { // If it's a vendor build, we need to extract the version of the module // from vendor/modules.txt, otherwise we find the version from source // code file path - version := shared.ExtractVersion(file) + version := util.ExtractVersion(file) if rm.moduleVersions != nil { if v, ok := rm.moduleVersions[importPath]; ok { version = v @@ -190,7 +184,7 @@ func (rm *ruleMatcher) match(cmdArgs []string) *resource.RuleBundle { rule := availables[i] // Check if the version is supported - matched, err := shared.MatchVersion(version, rule.GetVersion()) + matched, err := util.MatchVersion(version, rule.GetVersion()) if err != nil { util.Log("Failed to match version %v between %v and %v", err, file, rule) @@ -201,7 +195,7 @@ func (rm *ruleMatcher) match(cmdArgs []string) *resource.RuleBundle { } // Check if the rule requires a specific Go version(range) if rule.GetGoVersion() != "" { - matched, err = shared.MatchVersion(goVersion, rule.GetGoVersion()) + matched, err = util.MatchVersion(goVersion, rule.GetGoVersion()) if err != nil { util.Log("Failed to match Go version %v between %v and %v", err, file, rule) @@ -215,7 +209,7 @@ func (rm *ruleMatcher) match(cmdArgs []string) *resource.RuleBundle { // Check if it matches with file rule early as we try to avoid // parsing the file content, which is time consuming if _, ok := rule.(*resource.InstFileRule); ok { - ast, err := shared.ParseAstFromFileOnlyPackage(file) + ast, err := util.ParseAstFromFileOnlyPackage(file) if ast == nil || err != nil { util.Log("Failed to parse %s: %v", file, err) continue @@ -230,7 +224,7 @@ func (rm *ruleMatcher) match(cmdArgs []string) *resource.RuleBundle { // Fair enough, parse the file content var tree *dst.File if _, ok := parsedAst[file]; !ok { - fileAst, err := shared.ParseAstFromFileFast(file) + fileAst, err := util.ParseAstFromFileFast(file) if fileAst == nil || err != nil { util.Log("failed to parse file %s: %v", file, err) continue @@ -255,7 +249,7 @@ func (rm *ruleMatcher) match(cmdArgs []string) *resource.RuleBundle { for _, decl := range tree.Decls { if genDecl, ok := decl.(*dst.GenDecl); ok { if rl, ok := rule.(*resource.InstStructRule); ok { - if shared.MatchStructDecl(genDecl, rl.StructType) { + if util.MatchStructDecl(genDecl, rl.StructType) { util.Log("Match struct rule %s", rule) err = bundle.AddFile2StructRule(file, rl) if err != nil { @@ -268,7 +262,7 @@ func (rm *ruleMatcher) match(cmdArgs []string) *resource.RuleBundle { } } else if funcDecl, ok := decl.(*dst.FuncDecl); ok { if rl, ok := rule.(*resource.InstFuncRule); ok { - if shared.MatchFuncDecl(funcDecl, rl.Function, + if util.MatchFuncDecl(funcDecl, rl.Function, rl.ReceiverType) { util.Log("Match func rule %s", rule) err = bundle.AddFile2FuncRule(file, rl) @@ -301,16 +295,16 @@ func findFlagValue(cmd []string, flag string) string { } func parseVendorModules() (map[string]string, error) { - util.Assert(shared.IsVendorBuild(), "why not otherwise") + util.Assert(util.IsVendorBuild(), "why not otherwise") vendorFile := filepath.Join("vendor", "modules.txt") - if exist, _ := util.PathExists(vendorFile); !exist { - return nil, fmt.Errorf("vendor/modules.txt not found") + if util.PathNotExists(vendorFile) { + return nil, errc.New(errc.ErrNotExist, "vendor/modules.txt not found") } // Read the vendor/modules.txt file line by line and parse it in form of // #ImportPath Version file, err := os.Open(vendorFile) if err != nil { - return nil, err + return nil, errc.New(errc.ErrOpenFile, err.Error()) } defer func(dryRunLog *os.File) { err := dryRunLog.Close() @@ -342,7 +336,7 @@ func parseVendorModules() (map[string]string, error) { } func runMatch(matcher *ruleMatcher, cmd string, ch chan *resource.RuleBundle) { - bundle := matcher.match(shared.SplitCmds(cmd)) + bundle := matcher.match(util.SplitCmds(cmd)) ch <- bundle } @@ -355,7 +349,7 @@ func (dp *DepProcessor) matchRules(compileCmds []string) error { if dp.vendorBuild { modules, err := parseVendorModules() if err != nil { - return fmt.Errorf("failed to parse vendor/modules.txt: %w", err) + return err } if config.GetConf().Verbose { util.Log("Vendor modules: %v", modules) diff --git a/tool/preprocess/pkgdep.go b/tool/preprocess/pkgdep.go index 22d45dd5..6145a1f0 100644 --- a/tool/preprocess/pkgdep.go +++ b/tool/preprocess/pkgdep.go @@ -14,11 +14,9 @@ package preprocess import ( - "fmt" "strings" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/resource" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" ) @@ -35,28 +33,28 @@ func replaceImport(importPath string, code string) string { func (dp *DepProcessor) replaceOtelImports() error { moduleName, err := dp.getImportPathOf(OtelPkgDep) if err != nil { - return fmt.Errorf("failed to get import path of otel_pkg: %w", err) + return err } for _, dep := range []string{OtelRules, OtelPkgDep} { files, err := util.ListFiles(dep) if err != nil { - return fmt.Errorf("failed to list files: %w", err) + return err } for _, file := range files { // Skip non-go files as no imports within them - if !shared.IsGoFile(file) { + if !util.IsGoFile(file) { continue } // Read file content and replace content then write back content, err := util.ReadFile(file) if err != nil { - return fmt.Errorf("failed to read file content: %w", err) + return err } content = replaceImport(moduleName, content) _, err = util.WriteFile(file, content) if err != nil { - return fmt.Errorf("failed to write file content: %w", err) + return err } } } @@ -64,9 +62,5 @@ func (dp *DepProcessor) replaceOtelImports() error { } func (dp *DepProcessor) copyPkgDep() error { - err := resource.CopyPkgTo(OtelPkgDep) - if err != nil { - return fmt.Errorf("failed to copy pkg deps: %w", err) - } - return nil + return resource.CopyPkgTo(OtelPkgDep) } diff --git a/tool/preprocess/preprocess.go b/tool/preprocess/preprocess.go index 286d1b43..ee27f6d8 100644 --- a/tool/preprocess/preprocess.go +++ b/tool/preprocess/preprocess.go @@ -28,8 +28,8 @@ import ( "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/config" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/resource" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" "golang.org/x/mod/modfile" ) @@ -116,13 +116,13 @@ func newDepProcessor() *DepProcessor { importCandidates: nil, rule2Dir: map[*resource.InstFuncRule]string{}, ruleCache: pkg.ExportRuleCache(), - vendorBuild: shared.IsVendorBuild(), + vendorBuild: util.IsVendorBuild(), } // There is a tricky, all arguments after the tool itself are saved for // later use, which means the subcommand "go build" are also included dp.goBuildCmd = make([]string, len(os.Args)-1) copy(dp.goBuildCmd, os.Args[1:]) - shared.AssertGoBuild(dp.goBuildCmd) + util.AssertGoBuild(dp.goBuildCmd) // Register signal handler to catch up SIGINT/SIGTERM interrupt signals and // do necessary cleanup @@ -164,15 +164,15 @@ func (dp *DepProcessor) postProcess() { func (dp *DepProcessor) backupFile(origin string) error { util.GuaranteeInPreprocess() backup := filepath.Base(origin) + OtelBackupSuffix - backup = shared.GetLogPath(filepath.Join(OtelBackups, backup)) + backup = util.GetLogPath(filepath.Join(OtelBackups, backup)) err := os.MkdirAll(filepath.Dir(backup), 0777) if err != nil { - return fmt.Errorf("failed to create directory: %w", err) + return errc.New(errc.ErrMkdirAll, err.Error()) } if _, exist := dp.backups[origin]; !exist { err = util.CopyFile(origin, backup) if err != nil { - return fmt.Errorf("failed to backup file %v: %w", origin, err) + return err } dp.backups[origin] = backup util.Log("Backup %v", origin) @@ -195,7 +195,7 @@ func (dp *DepProcessor) restoreBackupFiles() error { } func getCompileCommands() ([]string, error) { - dryRunLog, err := os.Open(shared.GetLogPath(DryRunLog)) + dryRunLog, err := os.Open(util.GetLogPath(DryRunLog)) if err != nil { return nil, err } @@ -214,7 +214,7 @@ func getCompileCommands() ([]string, error) { scanner.Buffer(buffer, cap(buffer)) for scanner.Scan() { line := scanner.Text() - if shared.IsCompileCommand(line) { + if util.IsCompileCommand(line) { line = strings.Trim(line, " ") compileCmds = append(compileCmds, line) } @@ -241,7 +241,7 @@ func (dp *DepProcessor) getImportCandidates() ([]string, error) { // it later, which would cause fatal error if permission is not granted. // It's a golang file, good candidate - if shared.IsGoFile(buildArg) { + if util.IsGoFile(buildArg) { candidates = append(candidates, buildArg) found = true continue @@ -252,14 +252,14 @@ func (dp *DepProcessor) getImportCandidates() ([]string, error) { } // It's a directory, find all go files in it - if exist, _ := util.PathExists(buildArg); exist { + if util.PathExists(buildArg) { p2, err := util.ListFilesFlat(buildArg) if err != nil { // Error is tolerated here, as buildArg may be a file continue } for _, file := range p2 { - if shared.IsGoFile(file) { + if util.IsGoFile(file) { candidates = append(candidates, file) found = true } @@ -271,10 +271,10 @@ func (dp *DepProcessor) getImportCandidates() ([]string, error) { if !found { files, err := util.ListFilesFlat(".") if err != nil { - return nil, fmt.Errorf("failed to list files: %w", err) + return nil, err } for _, file := range files { - if shared.IsGoFile(file) { + if util.IsGoFile(file) { candidates = append(candidates, file) } } @@ -289,22 +289,22 @@ func (dp *DepProcessor) addExplicitImport(importPaths ...string) (err error) { // Find out where we should forcely import our init func candidate, err := dp.getImportCandidates() if err != nil { - return fmt.Errorf("failed to get import candidates: %w", err) + return err } addImport := false for _, file := range candidate { - if !shared.IsGoFile(file) { + if !util.IsGoFile(file) { continue } - astRoot, err := shared.ParseAstFromFile(file) + astRoot, err := util.ParseAstFromFile(file) if err != nil { - return fmt.Errorf("failed to parse ast from source: %w", err) + return err } - foundInit := shared.FindFuncDecl(astRoot, FuncInit) != nil + foundInit := util.FindFuncDecl(astRoot, FuncInit) != nil if !foundInit { - foundMain := shared.FindFuncDecl(astRoot, FuncMain) != nil + foundMain := util.FindFuncDecl(astRoot, FuncMain) != nil if !foundMain { continue } @@ -312,7 +312,7 @@ func (dp *DepProcessor) addExplicitImport(importPaths ...string) (err error) { // Prepend import path to the file for _, importPath := range importPaths { - shared.AddImportForcely(astRoot, importPath) + util.AddImportForcely(astRoot, importPath) if config.GetConf().Verbose { util.Log("Add %s import to %v", importPath, file) } @@ -321,16 +321,15 @@ func (dp *DepProcessor) addExplicitImport(importPaths ...string) (err error) { err = dp.backupFile(file) if err != nil { - return fmt.Errorf("failed to backup file %v: %w", file, err) + return err } - _, err = shared.WriteAstToFile(astRoot, filepath.Join(file)) + _, err = util.WriteAstToFile(astRoot, filepath.Join(file)) if err != nil { - return fmt.Errorf("failed to write ast to %v: %w", file, err) + return err } } if !addImport { - return fmt.Errorf("failed to add rule import, candidates are %v", - dp.importCandidates) + return errc.New(errc.ErrSetupRule, "no init or main function found") } return nil } @@ -339,12 +338,12 @@ func (dp *DepProcessor) addExplicitImport(importPaths ...string) (err error) { func getModuleName(gomod string) (string, error) { data, err := util.ReadFile(gomod) if err != nil { - return "", fmt.Errorf("failed to read go.mod: %w", err) + return "", err } - modFile, err := modfile.Parse(shared.GoModFile, []byte(data), nil) + modFile, err := modfile.Parse(util.GoModFile, []byte(data), nil) if err != nil { - return "", fmt.Errorf("failed to parse go.mod: %w", err) + return "", errc.New(errc.ErrParseCode, err.Error()) } moduleName := modFile.Module.Mod.Path @@ -355,18 +354,18 @@ func (dp *DepProcessor) findLocalImportPath() error { // Get absolute path of current working directory workingDir, err := filepath.Abs(".") if err != nil { - return fmt.Errorf("failed to get absolute path: %w", err) + return errc.New(errc.ErrAbsPath, err.Error()) } // Get absolute path of go.mod directory - gomod, err := shared.GetGoModPath() + gomod, err := util.GetGoModPath() if err != nil { - return fmt.Errorf("failed to get go.mod directory: %w", err) + return err } projectDir := filepath.Dir(gomod) // Replace go.mod directory with module name moduleName, err := getModuleName(gomod) if err != nil { - return fmt.Errorf("failed to get module name: %w", err) + return err } // Replace all backslashes with slashes. The import path is different from // the file path, which should always use slashes. @@ -384,9 +383,11 @@ func (dp *DepProcessor) getImportPathOf(dirName string) (string, error) { if dp.localImportPath == "" { err := dp.findLocalImportPath() if err != nil { - return "", fmt.Errorf("failed to find local import path: %w", err) + return "", err } } + // This is the import path in Go source code, not the file path, so we + // should always use slashes. return dp.localImportPath + "/" + dirName, nil } @@ -399,40 +400,40 @@ func (dp *DepProcessor) addOtelImports() error { } err := dp.addExplicitImport(deps...) if err != nil { - return fmt.Errorf("failed to add otel import: %w", err) + return err } return nil } +// Note in this function, error is tolerated as we are best-effort to clean up +// any obsolete materials, but it's not fatal if we fail to do so. func (dp *DepProcessor) preclean() { - // err is tolerated here as this is a best-effort operation - // Clean obsolete imports from last run candidate, _ := dp.getImportCandidates() ruleImport, _ := dp.getImportPathOf(OtelRules) for _, file := range candidate { - if !shared.IsGoFile(file) { + if !util.IsGoFile(file) { continue } - astRoot, _ := shared.ParseAstFromFile(file) + astRoot, _ := util.ParseAstFromFile(file) if astRoot == nil { continue } - if shared.RemoveImport(astRoot, ruleImport) != nil { + if util.RemoveImport(astRoot, ruleImport) != nil { if config.GetConf().Verbose { util.Log("Remove obsolete import %v from %v", ruleImport, file) } } - _, err := shared.WriteAstToFile(astRoot, file) + _, err := util.WriteAstToFile(astRoot, file) if err != nil { util.Log("Failed to write ast to %v: %v", file, err) } } // Clean otel_rules/otel_pkgdep directory - if exist, _ := util.PathExists(OtelRules); exist { + if util.PathExists(OtelRules) { _ = os.RemoveAll(OtelRules) } - if exist, _ := util.PathExists(OtelPkgDep); exist { + if util.PathExists(OtelPkgDep) { _ = os.RemoveAll(OtelPkgDep) } } @@ -440,7 +441,7 @@ func (dp *DepProcessor) preclean() { func (dp *DepProcessor) storeRuleBundles() error { err := resource.StoreRuleBundles(dp.bundles) if err != nil { - return fmt.Errorf("failed to store rule bundles: %w", err) + return err } // No longer valid from now on dp.bundles = nil @@ -449,21 +450,29 @@ func (dp *DepProcessor) storeRuleBundles() error { // runDryBuild runs a dry build to get all dependencies needed for the project. func runDryBuild(goBuildCmd []string) error { - dryRunLog, err := os.Create(shared.GetLogPath(DryRunLog)) + dryRunLog, err := os.Create(util.GetLogPath(DryRunLog)) if err != nil { - return err + return errc.New(errc.ErrCreateFile, err.Error()) } // The full build command is: "go build -a -x -n {...}" args := []string{"go", "build", "-a", "-x", "-n"} args = append(args, goBuildCmd[2:]...) - shared.AssertGoBuild(args) + util.AssertGoBuild(args) // Run the dry build util.Log("Run dry build %v", args) cmd := exec.Command(args[0], args[1:]...) - cmd.Stdout = dryRunLog + // This is a little anti-intuitive as the error message is not printed to + // the stderr, instead it is printed to the stdout, only the build tool + // knows the reason why. + cmd.Stdout = os.Stdout cmd.Stderr = dryRunLog - return cmd.Run() + err = cmd.Run() + if err != nil { + return errc.New(errc.ErrRunCmd, err.Error()). + With("command", fmt.Sprintf("%v", args)) + } + return nil } func runModTidy() error { @@ -496,10 +505,6 @@ func runGoModEdit(require string) error { return err } -func runCleanCache() error { - return util.RunCmd("go", "clean", "-cache") -} - func nullDevice() string { if runtime.GOOS == "windows" { return "NUL" @@ -510,7 +515,7 @@ func nullDevice() string { func runBuildWithToolexec(goBuildCmd []string) error { exe, err := os.Executable() if err != nil { - return err + return errc.New(errc.ErrGetExecutable, err.Error()) } args := []string{ "go", @@ -520,7 +525,7 @@ func runBuildWithToolexec(goBuildCmd []string) error { } // Leave the temporary compilation directory - args = append(args, "-work") + args = append(args, util.BuildWork) // Force rebuilding args = append(args, "-a") @@ -542,7 +547,7 @@ func runBuildWithToolexec(goBuildCmd []string) error { if config.GetConf().Verbose { util.Log("Run go build with args %v in toolexec mode", args) } - shared.AssertGoBuild(args) + util.AssertGoBuild(args) out, err := util.RunCmdOutput(args...) util.Log("Run go build with toolexec: %v", out) return err @@ -551,11 +556,11 @@ func runBuildWithToolexec(goBuildCmd []string) error { func fetchDep(path string) error { err := runGoModDownload(path) if err != nil { - return fmt.Errorf("failed to fetch dependency %v: %w", path, err) + return err } err = runGoModEdit(path) if err != nil { - return fmt.Errorf("failed to edit go.mod: %w", err) + return err } return nil } @@ -581,7 +586,7 @@ func (dp *DepProcessor) pinDepVersion() error { util.Log("Failed to pin dependency %v: %v", p, err) continue } - return fmt.Errorf("failed to pin dependency %v: %w", dep, err) + return err } } return nil @@ -591,14 +596,14 @@ func precheck() error { // Check if the project is modularized go11module := os.Getenv("GO111MODULE") if go11module == "off" { - return fmt.Errorf("GO111MODULE is set to off") + return errc.New(errc.ErrNotModularized, "GO111MODULE is off") } - found, err := shared.IsExistGoMod() + found, err := util.IsExistGoMod() if !found { - return fmt.Errorf("go.mod not found %w", err) + return err } if err != nil { - return fmt.Errorf("failed to check go.mod: %w", err) + return err } // Check if the build arguments is sane @@ -618,19 +623,19 @@ func precheck() error { } func (dp *DepProcessor) backupMod() error { - gomodDir, err := shared.GetGoModDir() + gomodDir, err := util.GetGoModDir() if err != nil { - return fmt.Errorf("failed to get go.mod directory: %w", err) + return err } files := []string{} - files = append(files, filepath.Join(gomodDir, shared.GoModFile)) - files = append(files, filepath.Join(gomodDir, shared.GoSumFile)) - files = append(files, filepath.Join(gomodDir, shared.GoWorkSumFile)) + files = append(files, filepath.Join(gomodDir, util.GoModFile)) + files = append(files, filepath.Join(gomodDir, util.GoSumFile)) + files = append(files, filepath.Join(gomodDir, util.GoWorkSumFile)) for _, file := range files { - if exist, _ := util.PathExists(file); exist { + if util.PathExists(file) { err = dp.backupFile(file) if err != nil { - return fmt.Errorf("failed to backup %s: %w", file, err) + return err } } } @@ -638,12 +643,12 @@ func (dp *DepProcessor) backupMod() error { } func (dp *DepProcessor) saveDebugFiles() { - dir := filepath.Join(shared.GetTempBuildDir(), OtelRules) + dir := filepath.Join(util.GetTempBuildDir(), OtelRules) err := os.MkdirAll(dir, os.ModePerm) if err == nil { util.CopyDir(OtelRules, dir) } - dir = filepath.Join(shared.GetTempBuildDir(), OtelUser) + dir = filepath.Join(util.GetTempBuildDir(), OtelUser) err = os.MkdirAll(dir, os.ModePerm) if err == nil { for origin := range dp.backups { @@ -658,25 +663,25 @@ func (dp *DepProcessor) setupDeps() error { err := dp.addOtelImports() if err != nil { - return fmt.Errorf("failed to add otel imports: %w", err) + return err } // Pinning otel version in go.mod err = dp.pinDepVersion() if err != nil { - return fmt.Errorf("failed to update otel: %w", err) + return err } // Run go mod tidy first to fetch all dependencies err = runModTidy() if err != nil { - return fmt.Errorf("failed to run mod tidy: %w", err) + return err } if dp.vendorBuild { err = runModVendor() if err != nil { - return fmt.Errorf("failed to run mod vendor: %w", err) + return err } } @@ -684,48 +689,49 @@ func (dp *DepProcessor) setupDeps() error { err = runDryBuild(dp.goBuildCmd) if err != nil { // Tell us more about what happened in the dry run - errLog, _ := util.ReadFile(shared.GetLogPath(DryRunLog)) - return fmt.Errorf("failed to run dry build: %w\n%v", err, errLog) + errLog, _ := util.ReadFile(util.GetLogPath(DryRunLog)) + err = errc.Adhere(err, "reason", errLog) + return err } // Find compile commands from dry run log compileCmds, err := getCompileCommands() if err != nil { - return fmt.Errorf("failed to get compile commands: %w", err) + return err } err = dp.copyPkgDep() if err != nil { - return fmt.Errorf("failed to copy pkgdep: %w", err) + return err } // Find used rules according to compile commands err = dp.matchRules(compileCmds) if err != nil { - return fmt.Errorf("failed to find dependencies: %w", err) + return err } err = dp.fetchRules() if err != nil { - return fmt.Errorf("failed to fetch rules: %w", err) + return err } // Setup rules according to compile commands err = dp.setupRules() if err != nil { - return fmt.Errorf("failed to setup dependencies: %w", err) + return err } err = dp.replaceOtelImports() if err != nil { - return fmt.Errorf("failed to replace otel imports: %w", err) + return err } // Save matched rules into file, from this point on, we no longer modify // the rules err = dp.storeRuleBundles() if err != nil { - return fmt.Errorf("failed to store rule bundles: %w", err) + return err } return nil } @@ -734,7 +740,7 @@ func Preprocess() error { // Make sure the project is modularized otherwise we cannot proceed err := precheck() if err != nil { - return fmt.Errorf("not modularized project: %w", err) + return err } dp := newDepProcessor() @@ -745,7 +751,7 @@ func Preprocess() error { // Backup go.mod as we are likely modifing it later err = dp.backupMod() if err != nil { - return fmt.Errorf("failed to backup go.mod: %w", err) + return err } // Run a dry build to get all dependencies needed for the project @@ -753,25 +759,25 @@ func Preprocess() error { // for the actual instrumentation err = dp.setupDeps() if err != nil { - return fmt.Errorf("failed to setup prerequisites: %w", err) + return err } // Pinning dependencies version in go.mod err = dp.pinDepVersion() if err != nil { - return fmt.Errorf("failed to update otel: %w", err) + return err } // Run go mod tidy to fetch dependencies err = runModTidy() if err != nil { - return fmt.Errorf("failed to run mod tidy: %w", err) + return err } if dp.vendorBuild { err = runModVendor() if err != nil { - return fmt.Errorf("failed to run mod vendor: %w", err) + return err } } @@ -785,7 +791,7 @@ func Preprocess() error { // Run go build with toolexec to start instrumentation err = runBuildWithToolexec(dp.goBuildCmd) if err != nil { - return fmt.Errorf("failed to run go toolexec build: %w", err) + return err } } util.Log("Build completed successfully") diff --git a/tool/preprocess/setup.go b/tool/preprocess/setup.go index 2c682437..5c02397c 100644 --- a/tool/preprocess/setup.go +++ b/tool/preprocess/setup.go @@ -22,8 +22,8 @@ import ( "strings" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/config" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/resource" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" "github.com/dave/dst" ) @@ -35,15 +35,15 @@ const ( ) func initRuleDir() (err error) { - if exist, _ := util.PathExists(OtelRules); exist { + if util.PathExists(OtelRules) { err = os.RemoveAll(OtelRules) if err != nil { - return fmt.Errorf("failed to remove dir %v: %w", OtelRules, err) + return errc.New(errc.ErrRemoveAll, OtelRules) } } err = os.MkdirAll(OtelRules, os.ModePerm) if err != nil { - return fmt.Errorf("failed to create dir %v: %w", OtelRules, err) + return errc.New(errc.ErrMkdirAll, OtelRules) } return nil } @@ -70,7 +70,7 @@ func (dp *DepProcessor) copyRules(pkgName string) (err error) { return err } if len(files) == 0 { - return fmt.Errorf("can not find resource for %v", rule) + return errc.New(errc.ErrInvalidRule, rule.String()) } // Although different rule hooks may instrument the same // function, we still need to create separate directories @@ -81,21 +81,19 @@ func (dp *DepProcessor) copyRules(pkgName string) (err error) { dp.rule2Dir[rule] = dir for _, file := range files { - if !shared.IsGoFile(file) || shared.IsGoTestFile(file) { + if !util.IsGoFile(file) || util.IsGoTestFile(file) { continue } ruleDir := filepath.Join(pkgName, dir) err = os.MkdirAll(ruleDir, 0777) if err != nil { - return fmt.Errorf("failed to create dir %v: %w", - ruleDir, err) + return errc.New(errc.ErrMkdirAll, err.Error()) } ruleFile := filepath.Join(ruleDir, filepath.Base(file)) err = dp.copyRule(file, ruleFile, bundle) if err != nil { - return fmt.Errorf("failed to copy rule %v: %w", - file, err) + return err } } } @@ -105,6 +103,7 @@ func (dp *DepProcessor) copyRules(pkgName string) (err error) { return nil } + func rectifyCallContext(astRoot *dst.File, bundle *resource.RuleBundle) { // We write hook code by using api.CallContext as the first parameter, but // the actual package name is not api. Given net/http package, the actual @@ -115,7 +114,7 @@ func rectifyCallContext(astRoot *dst.File, bundle *resource.RuleBundle) { // omit the package name before, but we can't do that now because of renaming newAliasName := bundle.PackageName + util.RandomString(5) alias := ApiPackage - spec := shared.FindImport(astRoot, ApiImportPath) + spec := util.FindImport(astRoot, ApiImportPath) if spec != nil { if spec.Name != nil { alias = spec.Name.Name @@ -146,6 +145,7 @@ func rectifyCallContext(astRoot *dst.File, bundle *resource.RuleBundle) { } } } + func makeHookPublic(astRoot *dst.File, bundle *resource.RuleBundle) { // Only make hook public, keep it as it is if it's not a hook hooks := make(map[string]bool) @@ -181,12 +181,12 @@ func (dp *DepProcessor) copyRule(path, target string, bundle *resource.RuleBundle) error { text, err := util.ReadFile(path) if err != nil { - return fmt.Errorf("failed to read rule file %v: %w", path, err) + return err } - text = shared.RemoveGoBuildComment(text) - astRoot, err := shared.ParseAstFromSource(text) + text = util.RemoveGoBuildComment(text) + astRoot, err := util.ParseAstFromSource(text) if err != nil { - return fmt.Errorf("failed to parse ast from source: %w", err) + return err } // Rename package name nevertheless astRoot.Name.Name = filepath.Base(filepath.Dir(target)) @@ -198,9 +198,9 @@ func (dp *DepProcessor) copyRule(path, target string, makeHookPublic(astRoot, bundle) // Copy used rule into project - _, err = shared.WriteAstToFile(astRoot, target) + _, err = util.WriteAstToFile(astRoot, target) if err != nil { - return fmt.Errorf("failed to write ast to %v: %w", target, err) + return err } if config.GetConf().Verbose { util.Log("Copy dependency %v to %v", path, target) @@ -245,16 +245,15 @@ func (dp *DepProcessor) initRules(pkgName string) (err error) { rd := fmt.Sprintf("%s/%s", OtelRules, dp.rule2Dir[rule]) path, err := dp.getImportPathOf(rd) if err != nil { - return fmt.Errorf("failed to get import path: %w", - err) + return err } imports[path] = dp.rule2Dir[rule] assigns = append(assigns, fmt.Sprintf("\t%s.%s = %s.%s\n", aliasPkg, - shared.GetVarNameOfFunc(rule.OnEnter), + util.GetVarNameOfFunc(rule.OnEnter), dp.rule2Dir[rule], - shared.MakePublic(rule.OnEnter), + util.MakePublic(rule.OnEnter), ), ) } @@ -262,17 +261,16 @@ func (dp *DepProcessor) initRules(pkgName string) (err error) { rd := fmt.Sprintf("%s/%s", OtelRules, dp.rule2Dir[rule]) path, err := dp.getImportPathOf(rd) if err != nil { - return fmt.Errorf("failed to get import path: %w", - err) + return err } imports[path] = dp.rule2Dir[rule] assigns = append(assigns, fmt.Sprintf( "\t%s.%s = %s.%s\n", aliasPkg, - shared.GetVarNameOfFunc(rule.OnExit), + util.GetVarNameOfFunc(rule.OnExit), dp.rule2Dir[rule], - shared.MakePublic(rule.OnExit), + util.MakePublic(rule.OnExit), ), ) } @@ -320,11 +318,11 @@ func (dp *DepProcessor) initRules(pkgName string) (err error) { func (dp *DepProcessor) addRuleImport() error { ruleImportPath, err := dp.getImportPathOf(OtelRules) if err != nil { - return fmt.Errorf("failed to get import path: %w", err) + return err } err = dp.addExplicitImport(ruleImportPath) if err != nil { - return fmt.Errorf("failed to add rule import: %w", err) + return err } return nil } @@ -346,9 +344,9 @@ func (dp *DepProcessor) rewriteRules() error { if !strings.HasSuffix(rule.FileName, ReorderInitFile) { continue } - astRoot, err := shared.ParseAstFromFile(rule.FileName) + astRoot, err := util.ParseAstFromFile(rule.FileName) if err != nil { - return fmt.Errorf("failed to parse ast from source: %w", err) + return err } found := false dst.Inspect(astRoot, func(n dst.Node) bool { @@ -356,7 +354,7 @@ func (dp *DepProcessor) rewriteRules() error { if basicLit.Kind == token.STRING { quoted := fmt.Sprintf("%q", ReorderLocalPrefix) if basicLit.Value == quoted { - gomod, err := shared.GetGoModPath() + gomod, err := util.GetGoModPath() if err != nil { return false } @@ -373,13 +371,11 @@ func (dp *DepProcessor) rewriteRules() error { return true }) if !found { - return fmt.Errorf("failed to find rewrite local prefix in %v", - rule.FileName) + return errc.New(errc.ErrInternal, "no localPrefix found") } else { - _, err = shared.WriteAstToFile(astRoot, rule.FileName) + _, err = util.WriteAstToFile(astRoot, rule.FileName) if err != nil { - return fmt.Errorf("failed to write ast to %v: %w", - rule.FileName, err) + return err } } } @@ -391,37 +387,37 @@ func (dp *DepProcessor) setupOtelSDK(pkgName string) error { setupTarget := filepath.Join(OtelRules, OtelSetupSDK) _, err := resource.CopyOtelSetupTo(pkgName, setupTarget) if err != nil { - return fmt.Errorf("failed to copy otel setup sdk: %w", err) + return err } - return err + return nil } func (dp *DepProcessor) setupRules() (err error) { defer util.PhaseTimer("Setup")() err = initRuleDir() if err != nil { - return fmt.Errorf("failed to create directory: %w", err) + return err } err = dp.copyRules(OtelRules) if err != nil { - return fmt.Errorf("failed to setup rules: %w", err) + return err } err = dp.initRules(OtelRules) if err != nil { - return fmt.Errorf("failed to setup initiator: %w", err) + return err } err = dp.rewriteRules() if err != nil { - return fmt.Errorf("failed to rewrite rules: %w", err) + return err } err = dp.setupOtelSDK(OtelRules) if err != nil { - return fmt.Errorf("failed to setup otel sdk: %w", err) + return err } // Add rule import to all candidates err = dp.addRuleImport() if err != nil { - return fmt.Errorf("failed to add rule import: %w", err) + return err } return nil } diff --git a/tool/resource/bundle.go b/tool/resource/bundle.go index 866403f7..0a0e3764 100644 --- a/tool/resource/bundle.go +++ b/tool/resource/bundle.go @@ -16,10 +16,9 @@ package resource import ( "encoding/json" - "fmt" "path/filepath" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" "github.com/dave/dst" ) @@ -62,7 +61,7 @@ func (rb *RuleBundle) IsValid() bool { func (rb *RuleBundle) AddFile2FuncRule(file string, rule *InstFuncRule) error { file, err := filepath.Abs(file) if err != nil { - return fmt.Errorf("failed to get abs path: %w", err) + return errc.New(errc.ErrAbsPath, err.Error()) } fn := rule.Function + "," + rule.ReceiverType util.Assert(fn != "", "sanity check") @@ -79,7 +78,7 @@ func (rb *RuleBundle) AddFile2FuncRule(file string, rule *InstFuncRule) error { func (rb *RuleBundle) AddFile2StructRule(file string, rule *InstStructRule) error { file, err := filepath.Abs(file) if err != nil { - return fmt.Errorf("failed to get abs path: %w", err) + return errc.New(errc.ErrAbsPath, err.Error()) } st := rule.StructType util.Assert(st != "", "sanity check") @@ -104,12 +103,12 @@ func (rb *RuleBundle) AddFileRule(rule *InstFileRule) { func isHookDefined(root *dst.File, rule *InstFuncRule) bool { util.Assert(rule.OnEnter != "" || rule.OnExit != "", "hook must be set") if rule.OnEnter != "" { - if shared.FindFuncDecl(root, rule.OnEnter) == nil { + if util.FindFuncDecl(root, rule.OnEnter) == nil { return false } } if rule.OnExit != "" { - if shared.FindFuncDecl(root, rule.OnExit) == nil { + if util.FindFuncDecl(root, rule.OnExit) == nil { return false } } @@ -119,15 +118,15 @@ func isHookDefined(root *dst.File, rule *InstFuncRule) bool { func FindHookFile(rule *InstFuncRule) (string, error) { files, err := FindRuleFiles(rule) if err != nil { - return "", fmt.Errorf("failed to find rule files: %w", err) + return "", err } for _, file := range files { - if !shared.IsGoFile(file) { + if !util.IsGoFile(file) { continue } - root, err := shared.ParseAstFromFileFast(file) + root, err := util.ParseAstFromFileFast(file) if err != nil { - return "", fmt.Errorf("failed to read hook file: %w", err) + return "", err } if isHookDefined(root, rule) { return file, nil @@ -152,14 +151,14 @@ func FindRuleFiles(rule InstRule) ([]string, error) { func StoreRuleBundles(bundles []*RuleBundle) error { util.GuaranteeInPreprocess() - ruleFile := shared.GetPreprocessLogPath(RuleBundleJsonFile) + ruleFile := util.GetPreprocessLogPath(RuleBundleJsonFile) bs, err := json.Marshal(bundles) if err != nil { - return fmt.Errorf("failed to store used rules: %w", err) + return errc.New(errc.ErrInvalidJSON, err.Error()) } _, err = util.WriteFile(ruleFile, string(bs)) if err != nil { - return fmt.Errorf("failed to write used rules: %w", err) + return err } return nil } @@ -167,15 +166,15 @@ func StoreRuleBundles(bundles []*RuleBundle) error { func LoadRuleBundles() ([]*RuleBundle, error) { util.GuaranteeInInstrument() - ruleFile := shared.GetPreprocessLogPath(RuleBundleJsonFile) + ruleFile := util.GetPreprocessLogPath(RuleBundleJsonFile) data, err := util.ReadFile(ruleFile) if err != nil { - return nil, fmt.Errorf("failed to read used rules: %w", err) + return nil, err } var bundles []*RuleBundle err = json.Unmarshal([]byte(data), &bundles) if err != nil { - return nil, fmt.Errorf("failed to load used rules: %w", err) + return nil, errc.New(errc.ErrInvalidJSON, "bad "+ruleFile) } return bundles, nil } diff --git a/tool/resource/resource.go b/tool/resource/resource.go index f8de6444..6d3ddda1 100644 --- a/tool/resource/resource.go +++ b/tool/resource/resource.go @@ -21,7 +21,6 @@ import ( "strings" "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" ) @@ -84,7 +83,7 @@ func CopyOtelSetupTo(pkgName, target string) (string, error) { template := pkg.ExportOtelSetupSDKTemplate() snippet := strings.Replace(template, "package pkg", "package "+pkgName, 1) - snippet = shared.RemoveGoBuildComment(snippet) + snippet = util.RemoveGoBuildComment(snippet) return util.WriteFile(target, snippet) } diff --git a/tool/resource/ruledef.go b/tool/resource/ruledef.go index cd5e3cb4..36d2e133 100644 --- a/tool/resource/ruledef.go +++ b/tool/resource/ruledef.go @@ -15,10 +15,10 @@ package resource import ( "encoding/json" - "fmt" "strings" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/shared" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" ) // ----------------------------------------------------------------------------- @@ -135,12 +135,12 @@ func (rule *InstFileRule) String() string { func verifyRule(rule *InstBaseRule, checkPath bool) error { if checkPath { if rule.Path == "" { - return fmt.Errorf("local path is empty") + return errc.New(errc.ErrInvalidRule, "local path is empty") } } // Import path should not be empty if rule.ImportPath == "" { - return fmt.Errorf("import path is empty") + return errc.New(errc.ErrInvalidRule, "import path is empty") } // If version is specified, it should be in the format of [start,end) for _, v := range []string{rule.Version, rule.GoVersion} { @@ -149,7 +149,7 @@ func verifyRule(rule *InstBaseRule, checkPath bool) error { !strings.Contains(v, ")") || !strings.Contains(v, ",") || strings.Contains(v, "v") { - return fmt.Errorf("invalid version format %s", v) + return errc.New(errc.ErrInvalidRule, "bad version "+v) } } } @@ -170,10 +170,10 @@ func (rule *InstFileRule) Verify() error { return err } if rule.FileName == "" { - return fmt.Errorf("file name is empty") + return errc.New(errc.ErrInvalidRule, "empty file name") } - if !shared.IsGoFile(rule.FileName) { - return fmt.Errorf("file name should not end with .go") + if !util.IsGoFile(rule.FileName) { + return errc.New(errc.ErrInvalidRule, "not a go file") } return nil } @@ -189,10 +189,10 @@ func (rule *InstFuncRule) Verify() error { return err } if rule.Function == "" { - return fmt.Errorf("function name is empty") + return errc.New(errc.ErrInvalidRule, "empty function name") } if rule.OnEnter == "" && rule.OnExit == "" { - return fmt.Errorf("both onEnter and onExit are empty") + return errc.New(errc.ErrInvalidRule, "empty hook") } return nil } @@ -203,10 +203,10 @@ func (rule *InstStructRule) Verify() error { return err } if rule.StructType == "" { - return fmt.Errorf("struct type is empty") + return errc.New(errc.ErrInvalidRule, "empty struct type") } if rule.FieldName == "" || rule.FieldType == "" { - return fmt.Errorf("field name is empty") + return errc.New(errc.ErrInvalidRule, "empty field name or type") } return nil } diff --git a/tool/shared/ast.go b/tool/util/ast.go similarity index 94% rename from tool/shared/ast.go rename to tool/util/ast.go index 95ac3f5e..2bacc914 100644 --- a/tool/shared/ast.go +++ b/tool/util/ast.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package shared +package util import ( "fmt" @@ -21,7 +21,7 @@ import ( "os" "path/filepath" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc" "github.com/dave/dst" "github.com/dave/dst/decorator" ) @@ -234,7 +234,7 @@ func SwitchCase(list []dst.Expr, stmts []dst.Stmt) *dst.CaseClause { func AddStructField(decl dst.Decl, name string, typ string) { gen, ok := decl.(*dst.GenDecl) if !ok { - util.LogFatal("decl is not a GenDecl") + LogFatal("decl is not a GenDecl") } fd := NewField(name, Ident(typ)) st := gen.Specs[0].(*dst.TypeSpec).Type.(*dst.StructType) @@ -359,7 +359,7 @@ func MatchFuncDecl(decl dst.Decl, function string, receiverType string) bool { case *dst.Ident: return recvTypeExpr.Name == receiverType default: - util.Unimplemented() + Unimplemented() } } else { if HasReceiver(funcDecl) { @@ -400,18 +400,18 @@ func ParseAstFromSnippet(codeSnippnet string) ([]dst.Stmt, error) { snippet := "package main; func _() {" + codeSnippnet + "}" file, err := decorator.ParseFile(fset, "", snippet, 0) if err != nil { - return nil, err + return nil, errc.New(errc.ErrParseCode, err.Error()) } return file.Decls[0].(*dst.FuncDecl).Body.List, nil } // ParseAstFromSource parses the AST from complete source code. func ParseAstFromSource(source string) (*dst.File, error) { - util.Assert(source != "", "empty source") + Assert(source != "", "empty source") dec := decorator.NewDecorator(token.NewFileSet()) dstRoot, err := dec.Parse(source) if err != nil { - return nil, err + return nil, errc.New(errc.ErrParseCode, err.Error()) } return dstRoot, nil } @@ -421,22 +421,22 @@ func parseAstMode(filePath string, mode parser.Mode) (*dst.File, error) { fset := token.NewFileSet() file, err := os.Open(filePath) if err != nil { - return nil, err + return nil, errc.New(errc.ErrOpenFile, err.Error()) } defer func(file *os.File) { err := file.Close() if err != nil { - util.LogFatal("failed to close file %s: %v", file.Name(), err) + LogFatal("failed to close file %s: %v", file.Name(), err) } }(file) astFile, err := parser.ParseFile(fset, name, file, mode) if err != nil { - return nil, err + return nil, errc.New(errc.ErrParseCode, err.Error()) } dec := decorator.NewDecorator(fset) dstFile, err := dec.DecorateFile(astFile) if err != nil { - return nil, err + return nil, errc.New(errc.ErrParseCode, err.Error()) } return dstFile, nil } @@ -458,19 +458,19 @@ func ParseAstFromFile(filePath string) (*dst.File, error) { func WriteAstToFile(astRoot *dst.File, filePath string) (string, error) { file, err := os.Create(filePath) if err != nil { - return "", err + return "", errc.New(errc.ErrCreateFile, err.Error()) } defer func(file *os.File) { err := file.Close() if err != nil { - util.LogFatal("failed to close file %s: %v", file.Name(), err) + LogFatal("failed to close file %s: %v", file.Name(), err) } }(file) r := decorator.NewRestorer() err = r.Fprint(file, astRoot) if err != nil { - return "", err + return "", errc.New(errc.ErrParseCode, err.Error()) } return file.Name(), nil } diff --git a/tool/util/log.go b/tool/util/log.go index e5455fac..4a2c1fb5 100644 --- a/tool/util/log.go +++ b/tool/util/log.go @@ -25,10 +25,15 @@ var logMutex sync.Mutex var Guarantee = Assert // More meaningful name:) -func SetLogTo(w *os.File) { +// Be caution it's not thread safe +func SetLogger(w *os.File) { logWriter = w } +func GetLoggerPath() string { + return logWriter.Name() +} + func Log(format string, args ...interface{}) { template := "[" + GetRunPhase().String() + "] " + format + "\n" logMutex.Lock() @@ -37,13 +42,9 @@ func Log(format string, args ...interface{}) { } func LogFatal(format string, args ...interface{}) { - // Log errors to debug file Log(format, args...) - // And print to stderr then, in red color - template := "Build error:\033[31m\n" + format + "\033[0m\n" - fmt.Fprintf(os.Stderr, template, - args...) - fmt.Fprintf(os.Stderr, "See build log %s for details.\n", - logWriter.Name()) + if InPreprocess() { + fmt.Fprintf(os.Stderr, format, args...) + } os.Exit(1) } diff --git a/tool/shared/shared.go b/tool/util/shared.go similarity index 80% rename from tool/shared/shared.go rename to tool/util/shared.go index 919c4786..861c861f 100644 --- a/tool/shared/shared.go +++ b/tool/util/shared.go @@ -12,19 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -package shared +package util import ( "encoding/json" - "errors" "fmt" "hash/fnv" - "os/exec" "path/filepath" "regexp" "strings" - "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc" "golang.org/x/mod/module" "golang.org/x/mod/semver" ) @@ -37,50 +35,59 @@ const ( DebugLogFile = "debug.log" TempBuildDir = ".otel-build" VendorDir = "vendor" - BuildModeVendor = "-mod=vendor" - BuildModeMod = "-mod=mod" BuildConfFile = "build_conf.json" ) +const ( + BuildPattern = "-p" + BuildGoVer = "-goversion" + BuildPgoProfile = "-pgoprofile" + BuildModeVendor = "-mod=vendor" + BuildModeMod = "-mod=mod" + BuildWork = "-work" +) + func AssertGoBuild(args []string) { if len(args) < 2 { - util.Assert(false, "empty go build command") + Assert(false, "empty go build command") } if !strings.Contains(args[0], "go") { - util.Assert(false, "invalid go build command %v", args) + Assert(false, "invalid go build command %v", args) } if args[1] != "build" { - util.Assert(false, "invalid go build command %v", args) + Assert(false, "invalid go build command %v", args) } } func IsCompileCommand(line string) bool { check := []string{"-o", "-p", "-buildid"} - if util.IsWindows() { + if IsWindows() { check = append(check, "compile.exe") - } else if util.IsUnix() { + } else if IsUnix() { check = append(check, "compile") } else { - util.ShouldNotReachHere() + ShouldNotReachHere() } + + // Check if the line contains all the required fields for _, id := range check { if !strings.Contains(line, id) { return false } } - // PGO compile command is different from normal compile command, we + // @@PGO compile command is different from normal compile command, we // should skip it, otherwise the same package will be compiled twice // (one for PGO and one for normal), which finally leads to the same // rule being applied twice. - if strings.Contains(line, "-pgoprofile=") { + if strings.Contains(line, BuildPgoProfile) { return false } return true } func GetTempBuildDir() string { - return filepath.Join(TempBuildDir, util.GetRunPhase().String()) + return filepath.Join(TempBuildDir, GetRunPhase().String()) } func GetTempBuildDirWith(name string) string { @@ -92,15 +99,15 @@ func GetLogPath(name string) string { } func GetInstrumentLogPath(name string) string { - return filepath.Join(TempBuildDir, util.PInstrument, name) + return filepath.Join(TempBuildDir, PInstrument, name) } func GetPreprocessLogPath(name string) string { - return filepath.Join(TempBuildDir, util.PPreprocess, name) + return filepath.Join(TempBuildDir, PPreprocess, name) } func GetConfigureLogPath(name string) string { - return filepath.Join(TempBuildDir, util.PConfigure, name) + return filepath.Join(TempBuildDir, PConfigure, name) } func GetVarNameOfFunc(fn string) string { @@ -137,21 +144,18 @@ func GetGoModPath() (string, error) { // If module-aware mode is enabled, but there is no go.mod, GOMOD will be // os.DevNull ("/dev/null" on Unix-like systems, "NUL" on Windows). // If module-aware mode is disabled, GOMOD will be the empty string. - cmd := exec.Command("go", "env", "GOMOD") - out, err := cmd.CombinedOutput() + out, err := RunCmdOutput("go", "env", "GOMOD") if err != nil { - return "", fmt.Errorf("failed to check go.mod existence: %w\n%v", - err, string(out)) + return "", err } - path := strings.TrimSpace(string(out)) - return path, nil + return strings.TrimSpace(out), nil } // GetGoModDir returns the directory of go.mod file. func GetGoModDir() (string, error) { gomod, err := GetGoModPath() if err != nil { - return "", fmt.Errorf("failed to get go.mod directory: %w", err) + return "", err } projectDir := filepath.Dir(gomod) return projectDir, nil @@ -191,10 +195,10 @@ func IsGoTestFile(path string) bool { func IsExistGoMod() (bool, error) { gomod, err := GetGoModPath() if err != nil { - return false, fmt.Errorf("failed to get go.mod path: %w", err) + return false, err } if gomod == "" { - return false, errors.New("failed to get go.mod path: not module-aware") + return false, errc.New(errc.ErrNotExist, "cannot find go.mod") } return strings.HasSuffix(gomod, GoModFile), nil } @@ -202,12 +206,12 @@ func IsExistGoMod() (bool, error) { func HashStruct(st interface{}) (uint64, error) { bs, err := json.Marshal(st) if err != nil { - return 0, err + return 0, errc.New(errc.ErrInvalidJSON, err.Error()) } hasher := fnv.New64a() _, err = hasher.Write(bs) if err != nil { - return 0, err + return 0, errc.New(errc.ErrInternal, err.Error()) } return hasher.Sum64(), nil } @@ -218,9 +222,9 @@ func MakePublic(name string) string { // splitVersionRange splits the version range into two parts, start and end. func splitVersionRange(vr string) (string, string) { - util.Assert(strings.Contains(vr, ","), "invalid version range format") - util.Assert(strings.Contains(vr, "["), "invalid version range format") - util.Assert(strings.Contains(vr, ")"), "invalid version range format") + Assert(strings.Contains(vr, ","), "invalid version range format") + Assert(strings.Contains(vr, "["), "invalid version range format") + Assert(strings.Contains(vr, ")"), "invalid version range format") start := vr[1:strings.Index(vr, ",")] end := vr[strings.Index(vr, ",")+1 : len(vr)-1] @@ -250,15 +254,15 @@ func MatchVersion(version string, ruleVersion string) (bool, error) { } // Check if both rule version and package version are in sane if !strings.Contains(version, "v") { - return false, fmt.Errorf("invalid version %v %v", - version, ruleVersion) + return false, errc.New(errc.ErrMatchRule, + fmt.Sprintf("invalid version %v", version)) } if !strings.Contains(ruleVersion, "[") || !strings.Contains(ruleVersion, ")") || !strings.Contains(ruleVersion, ",") || strings.Contains(ruleVersion, "v") { - return false, fmt.Errorf("invalid version format in rule %v", - ruleVersion) + return false, errc.New(errc.ErrMatchRule, + fmt.Sprintf("invalid rule version %v", ruleVersion)) } // Remove extra whitespace from the rule version string ruleVersion = strings.ReplaceAll(ruleVersion, " ", "") @@ -276,18 +280,19 @@ func MatchVersion(version string, ruleVersion string) (bool, error) { } case ruleVersionStart == "v": // Only end is specified - util.Assert(ruleVersionEnd != "v", "sanity check") + Assert(ruleVersionEnd != "v", "sanity check") if semver.Compare(version, ruleVersionEnd) < 0 { return true, nil } case ruleVersionEnd == "v": // Only start is specified - util.Assert(ruleVersionStart != "v", "sanity check") + Assert(ruleVersionStart != "v", "sanity check") if semver.Compare(version, ruleVersionStart) >= 0 { return true, nil } default: - return false, fmt.Errorf("invalid version range %v", ruleVersion) + return false, errc.New(errc.ErrMatchRule, + fmt.Sprintf("invalid rule version range %v", ruleVersion)) } return false, nil } @@ -323,7 +328,7 @@ func SplitCmds(input string) []string { } // Fix the escaped backslashes on Windows - if util.IsWindows() { + if IsWindows() { for i, arg := range args { args[i] = strings.ReplaceAll(arg, `\\`, `\`) } @@ -337,8 +342,5 @@ func IsVendorBuild() bool { return false } vendor := filepath.Join(projRoot, VendorDir) - if exist, _ := util.PathExists(vendor); exist { - return true - } - return false + return PathExists(vendor) } diff --git a/tool/util/util.go b/tool/util/util.go index 4b6d0974..a6985df1 100644 --- a/tool/util/util.go +++ b/tool/util/util.go @@ -25,6 +25,8 @@ import ( "runtime" "strings" "time" + + "github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc" ) type RunPhase string @@ -122,7 +124,12 @@ func RunCmd(args ...string) error { cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - return cmd.Run() + err := cmd.Run() + if err != nil { + return errc.New(errc.ErrRunCmd, err.Error()). + With("command", fmt.Sprintf("%v", args)) + } + return nil } func RunCmdOutput(args ...string) (string, error) { @@ -130,13 +137,17 @@ func RunCmdOutput(args ...string) (string, error) { args = args[1:] cmd := exec.Command(path, args...) out, err := cmd.CombinedOutput() - return string(out), err + if err != nil { + return "", errc.New(errc.ErrRunCmd, string(out)). + With("command", fmt.Sprintf("%v", args)) + } + return string(out), nil } func CopyFile(src, dst string) error { sourceFile, err := os.Open(src) if err != nil { - return err + return errc.New(errc.ErrOpenFile, err.Error()) } defer func(sourceFile *os.File) { err := sourceFile.Close() @@ -147,7 +158,7 @@ func CopyFile(src, dst string) error { destFile, err := os.Create(dst) if err != nil { - return err + return errc.New(errc.ErrCreateFile, err.Error()) } defer func(destFile *os.File) { err := destFile.Close() @@ -158,7 +169,7 @@ func CopyFile(src, dst string) error { _, err = io.Copy(destFile, sourceFile) if err != nil { - return err + return errc.New(errc.ErrCopyFile, err.Error()) } return nil } @@ -166,7 +177,7 @@ func CopyFile(src, dst string) error { func ReadFile(filePath string) (string, error) { file, err := os.Open(filePath) if err != nil { - return "", err + return "", errc.New(errc.ErrOpenFile, err.Error()) } defer func(file *os.File) { err := file.Close() @@ -178,7 +189,7 @@ func ReadFile(filePath string) (string, error) { buf := new(strings.Builder) _, err = io.Copy(buf, file) if err != nil { - return "", err + return "", errc.New(errc.ErrCopyFile, err.Error()) } return buf.String(), nil @@ -187,7 +198,7 @@ func ReadFile(filePath string) (string, error) { func WriteFile(filePath string, content string) (string, error) { file, err := os.Create(filePath) if err != nil { - return "", err + return "", errc.New(errc.ErrOpenFile, err.Error()) } defer func(file *os.File) { err := file.Close() @@ -198,7 +209,7 @@ func WriteFile(filePath string, content string) (string, error) { _, err = file.WriteString(content) if err != nil { - return "", err + return "", errc.New(errc.ErrWriteFile, err.Error()) } return file.Name(), nil } @@ -207,7 +218,7 @@ func ListFiles(dir string) ([]string, error) { var files []string walkFn := func(path string, info os.FileInfo, err error) error { if err != nil { - return err + return errc.New(errc.ErrWalkDir, err.Error()) } // Dont list files under hidden directories if strings.HasPrefix(info.Name(), ".") { @@ -219,14 +230,17 @@ func ListFiles(dir string) ([]string, error) { return nil } err := filepath.Walk(dir, walkFn) - return files, err + if err != nil { + return nil, errc.New(errc.ErrWalkDir, err.Error()) + } + return files, nil } func ListFilesFlat(dir string) ([]string, error) { // no recursive files, err := os.ReadDir(dir) if err != nil { - return nil, fmt.Errorf("failed to read directory %s: %w", dir, err) + return nil, errc.New(errc.ErrReadDir, err.Error()) } var paths []string for _, file := range files { @@ -239,18 +253,18 @@ func CopyDir(src string, dst string) error { // Get the properties of the source directory sourceInfo, err := os.Stat(src) if err != nil { - return err + return errc.New(errc.ErrStat, err.Error()) } // Create the destination directory if err := os.MkdirAll(dst, sourceInfo.Mode()); err != nil { - return err + return errc.New(errc.ErrMkdirAll, err.Error()) } // Read the contents of the source directory entries, err := ioutil.ReadDir(src) if err != nil { - return err + return errc.New(errc.ErrReadDir, err.Error()) } // Iterate through each entry in the source directory @@ -274,15 +288,13 @@ func CopyDir(src string, dst string) error { return nil } -func PathExists(path string) (bool, error) { +func PathExists(path string) bool { _, err := os.Stat(path) - if err == nil { - return true, nil - } - if os.IsNotExist(err) { - return false, nil - } - return false, err + return err == nil +} + +func PathNotExists(path string) bool { + return !PathExists(path) } func IsWindows() bool { @@ -300,12 +312,11 @@ func PhaseTimer(name string) func() { } } -func GetToolName() string { +func GetToolName() (string, error) { // Get the path of the current executable ex, err := os.Executable() if err != nil { - LogFatal("failed to get executable: %v", err) - os.Exit(0) + return "", errc.New(errc.ErrGetExecutable, err.Error()) } - return filepath.Base(ex) + return filepath.Base(ex), nil }