diff --git a/cmd/spawn/local-interchain.go b/cmd/spawn/local-interchain.go index 03141cf9..3542330d 100644 --- a/cmd/spawn/local-interchain.go +++ b/cmd/spawn/local-interchain.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "os" "os/exec" "path" @@ -31,30 +30,32 @@ var LocalICCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { debugBinaryLoc, _ := cmd.Flags().GetBool(FlagLocationPath) + logger := GetLogger() + loc := whereIsLocalICInstalled() if loc == "" { - fmt.Println("local-ic not found. Please run `make get-localic`") + logger.Error("local-ic not found. Please run `make get-localic`") return } if debugBinaryLoc { - fmt.Println(loc) + logger.Debug("local-ic binary", "location", loc) return } if err := os.Chmod(loc, 0755); err != nil { - fmt.Println("Error setting local-ic permissions:", err) + logger.Error("Error setting local-ic permissions", "err", err) } // set to use the current dir if it is not overrriden if os.Getenv("ICTEST_HOME") == "" { if err := os.Setenv("ICTEST_HOME", "."); err != nil { - fmt.Println("Error setting ICTEST_HOME:", err) + logger.Error("Error setting ICTEST_HOME", "err", err) } } if err := spawn.ExecCommand(loc, args...); err != nil { - fmt.Println("Error calling local-ic:", err) + logger.Error("Error calling local-ic", "err", err) } }, } diff --git a/cmd/spawn/main.go b/cmd/spawn/main.go index 98d52ab0..0decbe65 100644 --- a/cmd/spawn/main.go +++ b/cmd/spawn/main.go @@ -3,25 +3,52 @@ package main import ( "fmt" "log" + "log/slog" "os" + "strings" + "time" + "github.com/lmittmann/tint" + "github.com/mattn/go-isatty" "github.com/spf13/cobra" ) // Set in the makefile ld_flags on compile var SpawnVersion = "" +var LogLevelFlag = "log-level" + func main() { rootCmd.AddCommand(newChain) rootCmd.AddCommand(LocalICCmd) rootCmd.AddCommand(versionCmd) + rootCmd.PersistentFlags().String(LogLevelFlag, "info", "log level (debug, info, warn, error)") + if err := rootCmd.Execute(); err != nil { fmt.Fprintf(os.Stderr, "error while executing your CLI. Err: %v\n", err) os.Exit(1) } } +func GetLogger() *slog.Logger { + w := os.Stderr + + logLevel := parseLogLevelFromFlags() + + slog.SetDefault(slog.New( + // TODO: Windows support colored logs: https://github.com/mattn/go-colorable `tint.NewHandler(colorable.NewColorable(w), nil)` + tint.NewHandler(w, &tint.Options{ + Level: logLevel, + TimeFormat: time.Kitchen, + // Enables colors only if the terminal supports it + NoColor: !isatty.IsTerminal(w.Fd()), + }), + )) + + return slog.Default() +} + var rootCmd = &cobra.Command{ Use: "spawn", Short: "Entry into the Interchain", @@ -42,3 +69,27 @@ var versionCmd = &cobra.Command{ fmt.Println(SpawnVersion) }, } + +func parseLogLevelFromFlags() slog.Level { + logLevel, err := rootCmd.PersistentFlags().GetString(LogLevelFlag) + if err != nil { + log.Fatal(err) + } + + var lvl slog.Level + + switch strings.ToLower(logLevel) { + case "debug", "d", "dbg": + lvl = slog.LevelDebug + case "info", "i", "inf": + lvl = slog.LevelInfo + case "warn", "w", "wrn": + lvl = slog.LevelWarn + case "error", "e", "err", "fatal", "f", "ftl": + lvl = slog.LevelError + default: + lvl = slog.LevelInfo + } + + return lvl +} diff --git a/cmd/spawn/new-chain.go b/cmd/spawn/new-chain.go index 99e09526..948a814d 100644 --- a/cmd/spawn/new-chain.go +++ b/cmd/spawn/new-chain.go @@ -53,6 +53,8 @@ var newChain = &cobra.Command{ Args: cobra.ExactArgs(1), Aliases: []string{"new", "init", "create"}, Run: func(cmd *cobra.Command, args []string) { + logger := GetLogger() + projName := strings.ToLower(args[0]) homeDir := "." + projName @@ -60,7 +62,6 @@ var newChain = &cobra.Command{ walletPrefix, _ := cmd.Flags().GetString(FlagWalletPrefix) binName, _ := cmd.Flags().GetString(FlagBinDaemon) denom, _ := cmd.Flags().GetString(FlagTokenDenom) - debug, _ := cmd.Flags().GetBool(FlagDebugging) ignoreGitInit, _ := cmd.Flags().GetBool(FlagNoGit) githubOrg, _ := cmd.Flags().GetString(FlagGithubOrg) @@ -68,7 +69,7 @@ var newChain = &cobra.Command{ if len(disabled) == 0 && !bypassPrompt { items, err := selectItems(0, SupportedFeatures, true) if err != nil { - fmt.Println("Error selecting disabled:", err) + logger.Error("Error selecting disabled", "err", err) return } disabled = items.NOTSlice() @@ -80,13 +81,13 @@ var newChain = &cobra.Command{ HomeDir: homeDir, BinDaemon: binName, Denom: denom, - Debug: debug, GithubOrg: githubOrg, IgnoreGitInit: ignoreGitInit, DisabledModules: disabled, + Logger: logger, } if err := cfg.Validate(); err != nil { - fmt.Println("Error validating config:", err) + logger.Error("Error validating config", "err", err) return } diff --git a/go.mod b/go.mod index 4ab92051..7e7d4a29 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,8 @@ require ( github.com/chzyer/readline v1.5.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/lmittmann/tint v1.0.4 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect golang.org/x/sys v0.17.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index da9c2a7c..d26fccb2 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,12 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc= +github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -27,6 +31,7 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/spawn/chain_config.go b/spawn/chain_config.go index 03e2d347..5257a31f 100644 --- a/spawn/chain_config.go +++ b/spawn/chain_config.go @@ -4,6 +4,7 @@ import ( "embed" "fmt" "io/fs" + "log/slog" "os" "path" "strings" @@ -30,10 +31,10 @@ type NewChainConfig struct { GithubOrg string // IgnoreGitInit is a flag to ignore git init IgnoreGitInit bool - // Debug is a flag to enable debug logging - Debug bool DisabledModules []string + + Logger *slog.Logger } func (cfg *NewChainConfig) Validate() error { @@ -41,6 +42,10 @@ func (cfg *NewChainConfig) Validate() error { return fmt.Errorf("project name cannot contain special characters %s", cfg.ProjectName) } + if cfg.Logger == nil { + cfg.Logger = slog.Default() + } + return nil } @@ -48,6 +53,7 @@ func (cfg *NewChainConfig) AnnounceSuccessfulBuild() { projName := cfg.ProjectName bin := cfg.BinDaemon + // no logger here, straight to stdout fmt.Printf("\n\n🎉 New blockchain '%s' generated!\n", projName) fmt.Println("🏅Getting started:") fmt.Println(" - $ cd " + projName) @@ -64,20 +70,21 @@ func (cfg *NewChainConfig) GithubPath() string { func (cfg *NewChainConfig) NewChain() { NewDirName := cfg.ProjectName disabled := cfg.DisabledModules + logger := cfg.Logger - fmt.Println("Spawning new app:", NewDirName) - fmt.Println("Disabled features:", disabled) + logger.Info("Spawning new app", "app", NewDirName) + logger.Info("Disabled features", "features", disabled) if err := os.MkdirAll(NewDirName, 0755); err != nil { panic(err) } if err := cfg.SetupMainChainApp(); err != nil { - fmt.Println(fmt.Errorf("error setting up main chain app: %s", err)) + logger.Error("Error setting up main chain app", "err", err) } if err := cfg.SetupInterchainTest(); err != nil { - fmt.Println(fmt.Errorf("error setting up interchain test: %s", err)) + logger.Error("Error setting up interchain test", "err", err) } if !cfg.IgnoreGitInit { @@ -117,13 +124,12 @@ func (cfg *NewChainConfig) SetupMainChainApp() error { // *All Files fc.ReplaceEverywhere(cfg) - return fc.Save(cfg.Debug) + return fc.Save() }) } func (cfg *NewChainConfig) SetupInterchainTest() error { newDirName := cfg.ProjectName - debug := cfg.Debug // Interchaintest e2e is a nested submodule. go.mod is renamed to go.mod_ to avoid conflicts // It will be unwound during unpacking to properly nest it. @@ -153,14 +159,14 @@ func (cfg *NewChainConfig) SetupInterchainTest() error { fc.ReplaceAll(`Binary = "wasmd"`, fmt.Sprintf(`Binary = "%s"`, cfg.BinDaemon)) // else it would replace the Cosmwasm/wasmd import path fc.ReplaceAll(`Bech32 = "wasm"`, fmt.Sprintf(`Bech32 = "%s"`, cfg.Bech32Prefix)) - fc.FindAndReplaceAddressBech32("wasm", cfg.Bech32Prefix, debug) + fc.FindAndReplaceAddressBech32("wasm", cfg.Bech32Prefix) } // Removes any modules references after we modify interchaintest values fc.RemoveDisabledFeatures(cfg) - return fc.Save(cfg.Debug) + return fc.Save() }) } @@ -173,19 +179,13 @@ func (cfg *NewChainConfig) getFileContent(newFilePath string, fs embed.FS, relPa return nil, nil } - fc := NewFileContent(relPath, newFilePath) + fc := NewFileContent(cfg.Logger, relPath, newFilePath) if fc.HasIgnoreFile() { - if cfg.Debug { - fmt.Println("[!] Ignoring File: ", fc.NewPath) - } + cfg.Logger.Debug("Ignoring file", "file", fc.NewPath) return nil, nil } - if cfg.Debug { - fmt.Println(fc) - } - // Read the file contents from the embedded FS if fileContent, err := fs.ReadFile(relPath); err != nil { return nil, err diff --git a/spawn/command.go b/spawn/command.go index aaacb6f3..8e3b72b6 100644 --- a/spawn/command.go +++ b/spawn/command.go @@ -1,7 +1,6 @@ package spawn import ( - "fmt" "os" "os/exec" ) @@ -15,15 +14,15 @@ func ExecCommand(command string, args ...string) error { func (cfg *NewChainConfig) GitInitNewProjectRepo() { if err := ExecCommand("git", "init", cfg.ProjectName, "--quiet"); err != nil { - fmt.Println("Error initializing git:", err) + cfg.Logger.Error("Error initializing git", "err", err) } if err := os.Chdir(cfg.ProjectName); err != nil { - fmt.Println("Error changing to project directory:", err) + cfg.Logger.Error("Error changing to project directory", "err", err) } if err := ExecCommand("git", "add", "."); err != nil { - fmt.Println("Error adding files to git:", err) + cfg.Logger.Error("Error adding files to git", "err", err) } if err := ExecCommand("git", "commit", "-m", "initial commit", "--quiet"); err != nil { - fmt.Println("Error committing initial files:", err) + cfg.Logger.Error("Error committing initial files", "err", err) } } diff --git a/spawn/file_content.go b/spawn/file_content.go index 3afb61cd..c6174599 100644 --- a/spawn/file_content.go +++ b/spawn/file_content.go @@ -2,6 +2,7 @@ package spawn import ( "fmt" + "log/slog" "os" "path" "regexp" @@ -17,13 +18,16 @@ type FileContent struct { NewPath string // The contents of the file from the embededFileSystem (initially unmodified) Contents string + + Logger *slog.Logger } -func NewFileContent(relativePath, newPath string) *FileContent { +func NewFileContent(logger *slog.Logger, relativePath, newPath string) *FileContent { return &FileContent{ RelativePath: relativePath, NewPath: newPath, Contents: "", + Logger: logger, } } @@ -67,7 +71,7 @@ func (fc *FileContent) HasIgnoreFile() bool { func (fc *FileContent) DeleteContents(path string) { if fc.IsPath(path) { - fmt.Println("Deleting contents for", path) + fc.Logger.Debug("Deleting contents for", "path", path) fc.Contents = "" } } @@ -139,14 +143,14 @@ func (fc *FileContent) ReplaceLocalInterchainJSON(cfg *NewChainConfig) { fc.ReplaceAll("mydenom", cfg.Denom) fc.ReplaceAll("wasmd", cfg.BinDaemon) - fc.FindAndReplaceAddressBech32("wasm", cfg.Bech32Prefix, cfg.Debug) + fc.FindAndReplaceAddressBech32("wasm", cfg.Bech32Prefix) } } // FindAndReplaceStandardWalletsBech32 finds a prefix1... address and replaces it with a new prefix1... address // This works for both standard wallets (38 length after prefix1) and also smart contracts (58) -func (fc *FileContent) FindAndReplaceAddressBech32(oldPrefix, newPrefix string, isDebugging bool) { +func (fc *FileContent) FindAndReplaceAddressBech32(oldPrefix, newPrefix string) { oldPrefix = strings.TrimSuffix(oldPrefix, "1") newPrefix = strings.TrimSuffix(newPrefix, "1") @@ -155,9 +159,7 @@ func (fc *FileContent) FindAndReplaceAddressBech32(oldPrefix, newPrefix string, r := regexp.MustCompile(oldPrefix + `1([0-9a-z]{58}|[0-9a-z]{38})`) foundAddrs := r.FindAllString(fc.Contents, -1) - if isDebugging { - fmt.Println("Regex: Found Addresses:", foundAddrs, fc.NewPath) - } + fc.Logger.Debug("Regex: Found Addresses", "addresses", foundAddrs, "path", fc.NewPath) for _, addr := range foundAddrs { _, bz, err := bech32.Decode(addr, 100) @@ -180,7 +182,7 @@ func (fc *FileContent) RemoveGoModImport(importPath string) { return } - fmt.Println("removing go.mod import", fc.RelativePath, "for", importPath) + fc.Logger.Debug("removing go.mod import", "path", fc.RelativePath, "import", importPath) lines := strings.Split(fc.Contents, "\n") @@ -194,11 +196,9 @@ func (fc *FileContent) RemoveGoModImport(importPath string) { fc.Contents = strings.Join(newLines, "\n") } -func (fc *FileContent) Save(debug bool) error { +func (fc *FileContent) Save() error { if fc.Contents == "" { - if debug { - fmt.Printf("Save() No contents for %s. Not saving\n", fc.NewPath) - } + fc.Logger.Debug("Save() No contents for", "path", fc.NewPath) return nil } diff --git a/spawn/parser.go b/spawn/parser.go index 6e88af75..c4cdf3e8 100644 --- a/spawn/parser.go +++ b/spawn/parser.go @@ -69,7 +69,7 @@ func (fc *FileContent) RemoveTaggedLines(name string, deleteLine bool) { // the line which has the closing multiline end tag, we then continue to add lines as normal startMultiLineDelete = false - fmt.Println("endIdx:", idx, line) + fc.Logger.Debug("endIdx", "idx", idx, "line", line) continue } @@ -138,7 +138,7 @@ func (fc *FileContent) RemoveModuleFromText(removeText string, pathSuffix ...str fmt.Printf("rm %s startIdx: %d, %s\n", removeText, idx, line) if strings.TrimSpace(line) == ")" || strings.TrimSpace(line) == "}" { - fmt.Println("endIdx:", idx, line) + fc.Logger.Debug("endIdx", "idx", idx, "line", line) startBatchDelete = false continue } diff --git a/spawn/parser_test.go b/spawn/parser_test.go index 389a1cd7..27a74342 100644 --- a/spawn/parser_test.go +++ b/spawn/parser_test.go @@ -1,7 +1,7 @@ package spawn import ( - "fmt" + "log/slog" "path" "strings" "testing" @@ -33,7 +33,6 @@ func TestRemoveLineWithTag(t *testing.T) { deleteLine := true fc.RemoveTaggedLines("test", deleteLine) - // fmt.Println(fc.Contents) require.Equal(t, 2, contentLen(fc)) } @@ -61,6 +60,7 @@ func TestBatchRemoveText(t *testing.T) { ) third line of source // some comment`, + Logger: slog.Default(), } fc.RemoveModuleFromText("test", path.Join("wrong-dir", "app.go")) @@ -82,6 +82,7 @@ final line` fc := &FileContent{ Contents: content, + Logger: slog.Default(), } require.Equal(t, 8, contentLen(fc)) @@ -89,8 +90,6 @@ final line` deleteLine := true fc.RemoveTaggedLines("test", deleteLine) - fmt.Println(fc.Contents) - require.Equal(t, 2, contentLen(fc)) }