diff --git a/.changeset/log_startup_errors_to_stderr.md b/.changeset/log_startup_errors_to_stderr.md new file mode 100644 index 0000000..347e743 --- /dev/null +++ b/.changeset/log_startup_errors_to_stderr.md @@ -0,0 +1,5 @@ +--- +default: minor +--- + +# Log startup errors to stderr diff --git a/cmd/walletd/config.go b/cmd/walletd/config.go index c1ae246..5c41067 100644 --- a/cmd/walletd/config.go +++ b/cmd/walletd/config.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "errors" "fmt" "net" "os" @@ -18,9 +19,7 @@ import ( func readPasswordInput(context string) string { fmt.Printf("%s: ", context) input, err := term.ReadPassword(int(os.Stdin.Fd())) - if err != nil { - fatalError(fmt.Errorf("could not read password input: %w", err)) - } + checkFatalError("failed to read password input", err) fmt.Println("") return string(input) } @@ -29,9 +28,7 @@ func readInput(context string) string { fmt.Printf("%s: ", context) r := bufio.NewReader(os.Stdin) input, err := r.ReadString('\n') - if err != nil { - fatalError(fmt.Errorf("could not read input: %w", err)) - } + checkFatalError("failed to read input", err) return strings.TrimSpace(input) } @@ -115,9 +112,7 @@ func setDataDirectory() { } dir, err := filepath.Abs(cfg.Directory) - if err != nil { - fatalError(fmt.Errorf("failed to get absolute path of data directory: %w", err)) - } + checkFatalError("failed to get absolute path of data directory", err) fmt.Println("The data directory is where walletd will store its metadata and consensus data.") fmt.Println("This directory should be on a fast, reliable storage device, preferably an SSD.") @@ -200,7 +195,7 @@ func setAdvancedConfig() { case strings.EqualFold(mode, "full"): cfg.Index.Mode = wallet.IndexModeFull default: - fatalError(fmt.Errorf("invalid index mode: %q", mode)) + checkFatalError("invalid index mode", errors.New("must be either 'personal' or 'full'")) } fmt.Println("") @@ -236,21 +231,12 @@ func buildConfig() { // write the config file f, err := os.Create(configPath) - if err != nil { - fatalError(fmt.Errorf("failed to create config file: %w", err)) - return - } + checkFatalError("failed to create config file", err) defer f.Close() enc := yaml.NewEncoder(f) - if err := enc.Encode(cfg); err != nil { - fatalError(fmt.Errorf("failed to encode config file: %w", err)) - return - } else if err := f.Sync(); err != nil { - fatalError(fmt.Errorf("failed to sync config file: %w", err)) - return - } else if err := f.Close(); err != nil { - fatalError(fmt.Errorf("failed to close config file: %w", err)) - return - } + defer enc.Close() + + checkFatalError("failed to encode config file", enc.Encode(cfg)) + checkFatalError("failed to sync config file", f.Sync()) } diff --git a/cmd/walletd/main.go b/cmd/walletd/main.go index 536844c..3fcbfd9 100644 --- a/cmd/walletd/main.go +++ b/cmd/walletd/main.go @@ -3,7 +3,6 @@ package main import ( "context" "fmt" - "log" "os" "os/signal" "path/filepath" @@ -85,12 +84,6 @@ var cfg = config.Config{ }, } -func check(context string, err error) { - if err != nil { - log.Fatalf("%v: %v", context, err) - } -} - func mustSetAPIPassword() { if cfg.HTTP.Password != "" { return @@ -111,8 +104,12 @@ func mustSetAPIPassword() { } } -func fatalError(err error) { - os.Stderr.WriteString(err.Error() + "\n") +// checkFatalError prints an error message to stderr and exits with a 1 exit code. If err is nil, this is a no-op. +func checkFatalError(context string, err error) { + if err == nil { + return + } + os.Stderr.WriteString(fmt.Sprintf("%s: %s\n", context, err)) os.Exit(1) } @@ -130,19 +127,13 @@ func tryLoadConfig() { } f, err := os.Open(configPath) - if err != nil { - fatalError(fmt.Errorf("failed to open config file: %w", err)) - return - } + checkFatalError("failed to open config file", err) defer f.Close() dec := yaml.NewDecoder(f) dec.KnownFields(true) - if err := dec.Decode(&cfg); err != nil { - fmt.Println("failed to decode config file:", err) - os.Exit(1) - } + checkFatalError("failed to decode config file", dec.Decode(&cfg)) } // jsonEncoder returns a zapcore.Encoder that encodes logs as JSON intended for @@ -243,12 +234,14 @@ func main() { ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, syscall.SIGKILL) defer cancel() - if err := os.MkdirAll(cfg.Directory, 0700); err != nil { - fatalError(fmt.Errorf("failed to create data directory: %w", err)) + if cfg.Directory != "" { + checkFatalError("failed to create data directory", os.MkdirAll(cfg.Directory, 0700)) } mustSetAPIPassword() + checkFatalError("failed to parse index mode", cfg.Index.Mode.UnmarshalText([]byte(indexModeStr))) + var logCores []zapcore.Core if cfg.Log.StdOut.Enabled { // if no log level is set for stdout, use the global log level @@ -290,10 +283,7 @@ func main() { } fileWriter, closeFn, err := zap.Open(cfg.Log.File.Path) - if err != nil { - fatalError(fmt.Errorf("failed to open log file: %w", err)) - return - } + checkFatalError("failed to open log file", err) defer closeFn() // create the file logger @@ -312,13 +302,7 @@ func main() { // redirect stdlib log to zap zap.RedirectStdLog(log.Named("stdlib")) - if err := cfg.Index.Mode.UnmarshalText([]byte(indexModeStr)); err != nil { - log.Fatal("failed to parse index mode", zap.Error(err)) - } - - if err := runNode(ctx, cfg, log, enableDebug); err != nil { - log.Fatal("failed to run node", zap.Error(err)) - } + checkFatalError("failed to run node", runNode(ctx, cfg, log, enableDebug)) case versionCmd: if len(cmd.Args()) != 0 { cmd.Usage() @@ -334,9 +318,7 @@ func main() { } recoveryPhrase := cwallet.NewSeedPhrase() var seed [32]byte - if err := cwallet.SeedFromPhrase(&seed, recoveryPhrase); err != nil { - log.Fatal(err) - } + checkFatalError("failed to parse mnemonic phrase", cwallet.SeedFromPhrase(&seed, recoveryPhrase)) addr := types.StandardUnlockHash(cwallet.KeyFromSeed(&seed, 0).PublicKey()) fmt.Println("Recovery Phrase:", recoveryPhrase) @@ -355,10 +337,7 @@ func main() { } minerAddr, err := types.ParseAddress(minerAddrStr) - if err != nil { - log.Fatal(err) - } - + checkFatalError("failed to parse miner address", err) mustSetAPIPassword() c := api.NewClient("http://"+cfg.HTTP.Address+"/api", cfg.HTTP.Password) runCPUMiner(c, minerAddr, minerBlocks) diff --git a/cmd/walletd/miner.go b/cmd/walletd/miner.go index a531fcd..05be576 100644 --- a/cmd/walletd/miner.go +++ b/cmd/walletd/miner.go @@ -23,13 +23,13 @@ func runCPUMiner(c *api.Client, minerAddr types.Address, n int) { } elapsed := time.Since(start) cs, err := c.ConsensusTipState() - check("Couldn't get consensus tip state:", err) + checkFatalError("failed to get consensus tip state:", err) d, _ := new(big.Int).SetString(cs.Difficulty.String(), 10) d.Mul(d, big.NewInt(int64(1+elapsed))) fmt.Printf("\rMining block %4v...(%.2f blocks/day), difficulty %v)", cs.Index.Height+1, float64(blocksFound)*float64(24*time.Hour)/float64(elapsed), cs.Difficulty) txns, v2txns, err := c.TxpoolTransactions() - check("Couldn't get txpool transactions:", err) + checkFatalError("failed to get pool transactions:", err) b := types.Block{ ParentID: cs.Index.ID, Nonce: cs.NonceFactor() * frand.Uint64n(100), @@ -56,7 +56,7 @@ func runCPUMiner(c *api.Client, minerAddr types.Address, n int) { blocksFound++ index := types.ChainIndex{Height: cs.Index.Height + 1, ID: b.ID()} tip, err := c.ConsensusTip() - check("Couldn't get consensus tip:", err) + checkFatalError("failed to get consensus tip:", err) if tip != cs.Index { fmt.Printf("\nMined %v but tip changed, starting over\n", index) } else if err := c.SyncerBroadcastBlock(b); err != nil {