diff --git a/tools/cosmovisor/CHANGELOG.md b/tools/cosmovisor/CHANGELOG.md index 2782966af87e..bd69633b71aa 100644 --- a/tools/cosmovisor/CHANGELOG.md +++ b/tools/cosmovisor/CHANGELOG.md @@ -38,8 +38,9 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## Features -* [#12457](https://github.com/cosmos/cosmos-sdk/issues/12457) Add `cosmovisor pre-upgrade` command to manually add an upgrade to cosmovisor. +* [#16573](https://github.com/cosmos/cosmos-sdk/pull/16573) Extend `cosmovisor` configuration with new log format options * [#15361](https://github.com/cosmos/cosmos-sdk/pull/15361) Add `cosmovisor config` command to display the configuration used by cosmovisor. +* [#12457](https://github.com/cosmos/cosmos-sdk/issues/12457) Add `cosmovisor pre-upgrade` command to manually add an upgrade to cosmovisor. ## Improvements diff --git a/tools/cosmovisor/README.md b/tools/cosmovisor/README.md index 5070ba139be5..84712a8c6842 100644 --- a/tools/cosmovisor/README.md +++ b/tools/cosmovisor/README.md @@ -95,6 +95,8 @@ Use of `cosmovisor` without one of the action arguments is deprecated. For backw * `UNSAFE_SKIP_BACKUP` (defaults to `false`), if set to `true`, upgrades directly without performing a backup. Otherwise (`false`, default) backs up the data before trying the upgrade. The default value of false is useful and recommended in case of failures and when a backup needed to rollback. We recommend using the default backup option `UNSAFE_SKIP_BACKUP=false`. * `DAEMON_PREUPGRADE_MAX_RETRIES` (defaults to `0`). The maximum number of times to call [`pre-upgrade`](https://docs.cosmos.network/main/building-apps/app-upgrade#pre-upgrade-handling) in the application after exit status of `31`. After the maximum number of retries, Cosmovisor fails the upgrade. * `COSMOVISOR_DISABLE_LOGS` (defaults to `false`). If set to true, this will disable Cosmovisor logs (but not the underlying process) completely. This may be useful, for example, when a Cosmovisor subcommand you are executing returns a valid JSON you are then parsing, as logs added by Cosmovisor make this output not a valid JSON. +* `COSMOVISOR_COLOR_LOGS` (defaults to `true`). If set to true, this will colorise Cosmovisor logs (but not the underlying process). +* `COSMOVISOR_TIMEFORMAT_LOGS` (defaults to `kitchen`). If set to a value (`layout|ansic|unixdate|rubydate|rfc822|rfc822z|rfc850|rfc1123|rfc1123z|rfc3339|rfc3339nano|kitchen`), this will add timestamp prefix to Cosmovisor logs (but not the underlying process). ### Folder Layout diff --git a/tools/cosmovisor/args.go b/tools/cosmovisor/args.go index 198924e016ca..1df116df2f19 100644 --- a/tools/cosmovisor/args.go +++ b/tools/cosmovisor/args.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "net/url" "os" "path/filepath" @@ -11,8 +12,10 @@ import ( "strings" "time" + "cosmossdk.io/log" "cosmossdk.io/x/upgrade/plan" upgradetypes "cosmossdk.io/x/upgrade/types" + "github.com/rs/zerolog" ) // environment variable names @@ -28,6 +31,8 @@ const ( EnvInterval = "DAEMON_POLL_INTERVAL" EnvPreupgradeMaxRetries = "DAEMON_PREUPGRADE_MAX_RETRIES" EnvDisableLogs = "COSMOVISOR_DISABLE_LOGS" + EnvColorLogs = "COSMOVISOR_COLOR_LOGS" + EnvTimeFormatLogs = "COSMOVISOR_TIMEFORMAT_LOGS" ) const ( @@ -53,7 +58,8 @@ type Config struct { DataBackupPath string PreupgradeMaxRetries int DisableLogs bool - + ColorLogs bool + TimeFormatLogs string // currently running upgrade currentUpgrade upgradetypes.Plan } @@ -151,19 +157,25 @@ func GetConfigFromEnv() (*Config, error) { } var err error - if cfg.AllowDownloadBinaries, err = booleanOption(EnvDownloadBin, false); err != nil { + if cfg.AllowDownloadBinaries, err = BooleanOption(EnvDownloadBin, false); err != nil { + errs = append(errs, err) + } + if cfg.DownloadMustHaveChecksum, err = BooleanOption(EnvDownloadMustHaveChecksum, false); err != nil { + errs = append(errs, err) + } + if cfg.RestartAfterUpgrade, err = BooleanOption(EnvRestartUpgrade, true); err != nil { errs = append(errs, err) } - if cfg.DownloadMustHaveChecksum, err = booleanOption(EnvDownloadMustHaveChecksum, false); err != nil { + if cfg.UnsafeSkipBackup, err = BooleanOption(EnvSkipBackup, false); err != nil { errs = append(errs, err) } - if cfg.RestartAfterUpgrade, err = booleanOption(EnvRestartUpgrade, true); err != nil { + if cfg.DisableLogs, err = BooleanOption(EnvDisableLogs, false); err != nil { errs = append(errs, err) } - if cfg.UnsafeSkipBackup, err = booleanOption(EnvSkipBackup, false); err != nil { + if cfg.ColorLogs, err = BooleanOption(EnvColorLogs, true); err != nil { errs = append(errs, err) } - if cfg.DisableLogs, err = booleanOption(EnvDisableLogs, false); err != nil { + if cfg.TimeFormatLogs, err = TimeFormatOptionFromEnv(EnvTimeFormatLogs, time.Kitchen); err != nil { errs = append(errs, err) } @@ -203,6 +215,20 @@ func GetConfigFromEnv() (*Config, error) { return cfg, nil } +func (cfg *Config) Logger(dst io.Writer) log.Logger { + var logger log.Logger + + if cfg.DisableLogs { + logger = log.NewCustomLogger(zerolog.Nop()) + } else { + logger = log.NewLogger(dst, + log.ColorOption(cfg.ColorLogs), + log.TimeFormatOption(cfg.TimeFormatLogs)).With(log.ModuleKey, "cosmovisor") + } + + return logger +} + func parseEnvDuration(input string) (time.Duration, error) { duration, err := time.ParseDuration(input) if err != nil { @@ -338,7 +364,7 @@ returnError: } // checks and validates env option -func booleanOption(name string, defaultVal bool) (bool, error) { +func BooleanOption(name string, defaultVal bool) (bool, error) { p := strings.ToLower(os.Getenv(name)) switch p { case "": @@ -351,6 +377,43 @@ func booleanOption(name string, defaultVal bool) (bool, error) { return false, fmt.Errorf("env variable %q must have a boolean value (\"true\" or \"false\"), got %q", name, p) } +// checks and validates env option +func TimeFormatOptionFromEnv(env, defaultVal string) (string, error) { + val, set := os.LookupEnv(env) + if !set { + return defaultVal, nil + } + switch val { + case "layout": + return time.Layout, nil + case "ansic": + return time.ANSIC, nil + case "unixdate": + return time.UnixDate, nil + case "rubydate": + return time.RubyDate, nil + case "rfc822": + return time.RFC822, nil + case "rfc822z": + return time.RFC822Z, nil + case "rfc850": + return time.RFC850, nil + case "rfc1123": + return time.RFC1123, nil + case "rfc1123z": + return time.RFC1123Z, nil + case "rfc3339": + return time.RFC3339, nil + case "rfc3339nano": + return time.RFC3339Nano, nil + case "kitchen": + return time.Kitchen, nil + case "": + return "", nil + } + return "", fmt.Errorf("env variable %q must have a timeformat value (\"layout|ansic|unixdate|rubydate|rfc822|rfc822z|rfc850|rfc1123|rfc1123z|rfc3339|rfc3339nano|kitchen\"), got %q", EnvTimeFormatLogs, val) +} + // DetailString returns a multi-line string with details about this config. func (cfg Config) DetailString() string { configEntries := []struct{ name, value string }{ @@ -365,6 +428,8 @@ func (cfg Config) DetailString() string { {EnvDataBackupPath, cfg.DataBackupPath}, {EnvPreupgradeMaxRetries, fmt.Sprintf("%d", cfg.PreupgradeMaxRetries)}, {EnvDisableLogs, fmt.Sprintf("%t", cfg.DisableLogs)}, + {EnvColorLogs, fmt.Sprintf("%t", cfg.ColorLogs)}, + {EnvTimeFormatLogs, cfg.TimeFormatLogs}, } derivedEntries := []struct{ name, value string }{ diff --git a/tools/cosmovisor/args_test.go b/tools/cosmovisor/args_test.go index 3edcdaf2b2cd..b6f836acf31d 100644 --- a/tools/cosmovisor/args_test.go +++ b/tools/cosmovisor/args_test.go @@ -35,22 +35,31 @@ type cosmovisorEnv struct { Interval string PreupgradeMaxRetries string DisableLogs string + ColorLogs string + TimeFormatLogs string +} + +type envMap struct { + val string + allowEmpty bool } // ToMap creates a map of the cosmovisorEnv where the keys are the env var names. -func (c cosmovisorEnv) ToMap() map[string]string { - return map[string]string{ - EnvHome: c.Home, - EnvName: c.Name, - EnvDownloadBin: c.DownloadBin, - EnvDownloadMustHaveChecksum: c.DownloadMustHaveChecksum, - EnvRestartUpgrade: c.RestartUpgrade, - EnvRestartDelay: c.RestartDelay, - EnvSkipBackup: c.SkipBackup, - EnvDataBackupPath: c.DataBackupPath, - EnvInterval: c.Interval, - EnvPreupgradeMaxRetries: c.PreupgradeMaxRetries, - EnvDisableLogs: c.DisableLogs, +func (c cosmovisorEnv) ToMap() map[string]envMap { + return map[string]envMap{ + EnvHome: {val: c.Home, allowEmpty: false}, + EnvName: {val: c.Name, allowEmpty: false}, + EnvDownloadBin: {val: c.DownloadBin, allowEmpty: false}, + EnvDownloadMustHaveChecksum: {val: c.DownloadMustHaveChecksum, allowEmpty: false}, + EnvRestartUpgrade: {val: c.RestartUpgrade, allowEmpty: false}, + EnvRestartDelay: {val: c.RestartDelay, allowEmpty: false}, + EnvSkipBackup: {val: c.SkipBackup, allowEmpty: false}, + EnvDataBackupPath: {val: c.DataBackupPath, allowEmpty: false}, + EnvInterval: {val: c.Interval, allowEmpty: false}, + EnvPreupgradeMaxRetries: {val: c.PreupgradeMaxRetries, allowEmpty: false}, + EnvDisableLogs: {val: c.DisableLogs, allowEmpty: false}, + EnvColorLogs: {val: c.ColorLogs, allowEmpty: false}, + EnvTimeFormatLogs: {val: c.TimeFormatLogs, allowEmpty: true}, } } @@ -79,6 +88,10 @@ func (c *cosmovisorEnv) Set(envVar, envVal string) { c.PreupgradeMaxRetries = envVal case EnvDisableLogs: c.DisableLogs = envVal + case EnvColorLogs: + c.ColorLogs = envVal + case EnvTimeFormatLogs: + c.TimeFormatLogs = envVal default: panic(fmt.Errorf("Unknown environment variable [%s]. Ccannot set field to [%s]. ", envVar, envVal)) } @@ -109,9 +122,9 @@ func (s *argsTestSuite) setEnv(t *testing.T, env *cosmovisorEnv) { for envVar, envVal := range env.ToMap() { var err error var msg string - if len(envVal) != 0 { - err = os.Setenv(envVar, envVal) - msg = fmt.Sprintf("setting %s to %s", envVar, envVal) + if len(envVal.val) != 0 || envVal.allowEmpty { + err = os.Setenv(envVar, envVal.val) + msg = fmt.Sprintf("setting %s to %s", envVar, envVal.val) } else { err = os.Unsetenv(envVar) msg = fmt.Sprintf("unsetting %s", envVar) @@ -283,7 +296,7 @@ func (s *argsTestSuite) TestBooleanOption() { name := "COSMOVISOR_TEST_VAL" check := func(def, expected, isErr bool, msg string) { - v, err := booleanOption(name, def) + v, err := BooleanOption(name, def) if isErr { s.Require().Error(err) return @@ -317,6 +330,57 @@ func (s *argsTestSuite) TestBooleanOption() { check(false, true, false, "should handle true value case not sensitive") } +func (s *argsTestSuite) TestTimeFormat() { + initialEnv := s.clearEnv() + defer s.setEnv(nil, initialEnv) + + name := "COSMOVISOR_TEST_VAL" + + check := func(def, expected string, isErr bool, msg string) { + v, err := TimeFormatOptionFromEnv(name, def) + if isErr { + s.Require().Error(err) + return + } + s.Require().NoError(err) + s.Require().Equal(expected, v, msg) + } + + os.Unsetenv(name) + check(time.Kitchen, time.Kitchen, false, "should correctly set default value") + + os.Setenv(name, "") + check(time.Kitchen, "", false, "should correctly set to a none") + + os.Setenv(name, "wrong") + check(time.Kitchen, "", true, "should error on wrong value") + + os.Setenv(name, "layout") + check(time.Kitchen, time.Layout, false, "should handle layout value") + os.Setenv(name, "ansic") + check(time.Kitchen, time.ANSIC, false, "should handle ansic value") + os.Setenv(name, "unixdate") + check(time.Kitchen, time.UnixDate, false, "should handle unixdate value") + os.Setenv(name, "rubydate") + check(time.Kitchen, time.RubyDate, false, "should handle rubydate value") + os.Setenv(name, "rfc822") + check(time.Kitchen, time.RFC822, false, "should handle rfc822 value") + os.Setenv(name, "rfc822z") + check(time.Kitchen, time.RFC822Z, false, "should handle rfc822z value") + os.Setenv(name, "rfc850") + check(time.Kitchen, time.RFC850, false, "should handle rfc850 value") + os.Setenv(name, "rfc1123") + check(time.Kitchen, time.RFC1123, false, "should handle rfc1123 value") + os.Setenv(name, "rfc1123z") + check(time.Kitchen, time.RFC1123Z, false, "should handle rfc1123z value") + os.Setenv(name, "rfc3339") + check(time.Kitchen, time.RFC3339, false, "should handle rfc3339 value") + os.Setenv(name, "rfc3339nano") + check(time.Kitchen, time.RFC3339Nano, false, "should handle rfc3339nano value") + os.Setenv(name, "kitchen") + check(time.Kitchen, time.Kitchen, false, "should handle kitchen value") +} + func (s *argsTestSuite) TestDetailString() { home := "/home" name := "test-name" @@ -350,6 +414,9 @@ func (s *argsTestSuite) TestDetailString() { fmt.Sprintf("%s: %t", EnvSkipBackup, unsafeSkipBackup), fmt.Sprintf("%s: %s", EnvDataBackupPath, home), fmt.Sprintf("%s: %d", EnvPreupgradeMaxRetries, preupgradeMaxRetries), + fmt.Sprintf("%s: %t", EnvDisableLogs, cfg.DisableLogs), + fmt.Sprintf("%s: %t", EnvColorLogs, cfg.ColorLogs), + fmt.Sprintf("%s: %s", EnvTimeFormatLogs, cfg.TimeFormatLogs), "Derived Values:", fmt.Sprintf("Root Dir: %s", home), fmt.Sprintf("Upgrade Dir: %s", home), @@ -373,7 +440,18 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { absPath, perr := filepath.Abs(relPath) s.Require().NoError(perr) - newConfig := func(home, name string, downloadBin, downloadMustHaveChecksum, restartUpgrade bool, restartDelay int, skipBackup bool, dataBackupPath string, interval, preupgradeMaxRetries int, disableLogs bool) *Config { + newConfig := func( + home, name string, + downloadBin bool, + downloadMustHaveChecksum bool, + restartUpgrade bool, + restartDelay int, + skipBackup bool, + dataBackupPath string, + interval, preupgradeMaxRetries int, + disableLogs, colorLogs bool, + timeFormatLogs string, + ) *Config { return &Config{ Home: home, Name: name, @@ -386,6 +464,8 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { DataBackupPath: dataBackupPath, PreupgradeMaxRetries: preupgradeMaxRetries, DisableLogs: disableLogs, + ColorLogs: colorLogs, + TimeFormatLogs: timeFormatLogs, } } @@ -408,215 +488,248 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { DataBackupPath: "bad", Interval: "bad", PreupgradeMaxRetries: "bad", + TimeFormatLogs: "bad", }, expectedCfg: nil, - expectedErrCount: 10, + expectedErrCount: 11, }, { name: "all good", - envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen), expectedErrCount: 0, }, { name: "nothing set", - envVals: cosmovisorEnv{"", "", "", "", "", "", "", "", "", "", "false"}, + envVals: cosmovisorEnv{"", "", "", "", "", "", "", "", "", "", "false", "false", ""}, expectedCfg: nil, expectedErrCount: 3, }, // Note: Home and Name tests are done in TestValidate + // timeformat tests are done in the TestTimeFormat { name: "download bin bad", - envVals: cosmovisorEnv{absPath, "testname", "bad", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "bad", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"}, expectedCfg: nil, expectedErrCount: 1, }, { - name: "download bin not set", - envVals: cosmovisorEnv{absPath, "testname", "", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, false), + name: "download bin not set", + + envVals: cosmovisorEnv{absPath, "testname", "", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen), expectedErrCount: 0, }, { name: "download bin true", - envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen), expectedErrCount: 0, }, { name: "download bin false", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen), expectedErrCount: 0, }, { name: "download ensure checksum true", - envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen), expectedErrCount: 0, }, { name: "download ensure checksum false", - envVals: cosmovisorEnv{absPath, "testname", "true", "false", "false", "600ms", "true", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", true, false, false, 600, true, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "true", "false", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", true, false, false, 600, true, absPath, 303, 1, false, true, time.Kitchen), expectedErrCount: 0, }, { name: "restart upgrade bad", - envVals: cosmovisorEnv{absPath, "testname", "true", "true", "bad", "600ms", "true", "", "303ms", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "bad", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "restart upgrade not set", - envVals: cosmovisorEnv{absPath, "testname", "true", "true", "", "600ms", "true", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, false, true, time.Kitchen), expectedErrCount: 0, }, { name: "restart upgrade true", - envVals: cosmovisorEnv{absPath, "testname", "true", "true", "true", "600ms", "true", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "true", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, false, true, time.Kitchen), expectedErrCount: 0, }, { name: "restart upgrade true", - envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen), expectedErrCount: 0, }, { name: "skip unsafe backups bad", - envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "bad", "", "303ms", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "bad", "", "303ms", "1", "false", "true", "kitchen"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "skip unsafe backups not set", - envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "", "", "303ms", "1", "false", "true", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, false, true, time.Kitchen), expectedErrCount: 0, }, { name: "skip unsafe backups true", - envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen), expectedErrCount: 0, }, { name: "skip unsafe backups false", - envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "false", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "false", "", "303ms", "1", "false", "true", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, false, true, time.Kitchen), expectedErrCount: 0, }, { name: "poll interval bad", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "bad", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "bad", "1", "false", "true", "kitchen"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "poll interval 0", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "0", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "0", "1", "false", "true", "kitchen"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "poll interval not set", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 300, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "", "1", "false", "false", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 300, 1, false, false, time.Kitchen), expectedErrCount: 0, }, { name: "poll interval 600", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "600", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "600", "1", "false", "true", "kitchen"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "poll interval 1s", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "1s", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 1000, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "1s", "1", "false", "false", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 1000, 1, false, false, time.Kitchen), expectedErrCount: 0, }, { name: "poll interval -3m", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "-3m", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "-3m", "1", "false", "true", "kitchen"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "restart delay bad", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "bad", "false", "", "303ms", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "bad", "false", "", "303ms", "1", "false", "true", "kitchen"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "restart delay 0", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "0", "false", "", "303ms", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "0", "false", "", "303ms", "1", "false", "true", "kitchen"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "restart delay not set", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "", "false", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 0, false, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "", "false", "", "303ms", "1", "false", "false", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 0, false, absPath, 303, 1, false, false, time.Kitchen), expectedErrCount: 0, }, { name: "restart delay 600", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600", "false", "", "300ms", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600", "false", "", "300ms", "1", "false", "true", "kitchen"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "restart delay 1s", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "1s", "false", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 1000, false, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "1s", "false", "", "303ms", "1", "false", "false", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 1000, false, absPath, 303, 1, false, false, time.Kitchen), expectedErrCount: 0, }, { name: "restart delay -3m", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "-3m", "false", "", "303ms", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "-3m", "false", "", "303ms", "1", "false", "true", "kitchen"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "prepupgrade max retries bad", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "bad", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "bad", "false", "true", "kitchen"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "prepupgrade max retries 0", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "0", "false"}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, false), + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "0", "false", "false", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, false, false, time.Kitchen), expectedErrCount: 0, }, { name: "prepupgrade max retries not set", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "false"}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, false), + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "false", "false", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, false, false, time.Kitchen), expectedErrCount: 0, }, { name: "prepupgrade max retries 5", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "false"}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 5, false), + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "false", "false", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 5, false, false, time.Kitchen), expectedErrCount: 0, }, { name: "disable logs bad", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "bad"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "bad", "true", "kitchen"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "disable logs good", - envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true"}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true), + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "false", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, false, time.Kitchen), + expectedErrCount: 0, + }, + { + name: "disable logs color bad", + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "true", "bad", "kitchen"}, + expectedCfg: nil, + expectedErrCount: 1, + }, + { + name: "disable logs color good", + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "false", "kitchen"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, false, time.Kitchen), expectedErrCount: 0, }, + { + name: "disable logs timestamp", + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "false", ""}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, false, ""), + expectedErrCount: 0, + }, + { + name: "enable rf3339 logs timestamp", + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "rfc3339"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, true, time.RFC3339), + expectedErrCount: 0, + }, + { + name: "invalid logs timestamp format", + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "invalid"}, + expectedCfg: nil, + expectedErrCount: 1, + }, } for _, tc := range tests { diff --git a/tools/cosmovisor/cmd/cosmovisor/add_upgrade.go b/tools/cosmovisor/cmd/cosmovisor/add_upgrade.go index a7b90eb0dc0a..683660a72381 100644 --- a/tools/cosmovisor/cmd/cosmovisor/add_upgrade.go +++ b/tools/cosmovisor/cmd/cosmovisor/add_upgrade.go @@ -5,10 +5,9 @@ import ( "os" "path" - "cosmossdk.io/log" - "cosmossdk.io/tools/cosmovisor" - "github.com/rs/zerolog" "github.com/spf13/cobra" + + "cosmossdk.io/tools/cosmovisor" ) func NewAddUpgradeCmd() *cobra.Command { @@ -32,10 +31,7 @@ func AddUpgrade(cmd *cobra.Command, args []string) error { return err } - logger := cmd.Context().Value(log.ContextKey).(log.Logger) - if cfg.DisableLogs { - logger = log.NewCustomLogger(zerolog.Nop()) - } + logger := cfg.Logger(os.Stdout) upgradeName := args[0] if len(upgradeName) == 0 { diff --git a/tools/cosmovisor/cmd/cosmovisor/init.go b/tools/cosmovisor/cmd/cosmovisor/init.go index f085fe51543c..0848ee2bf450 100644 --- a/tools/cosmovisor/cmd/cosmovisor/init.go +++ b/tools/cosmovisor/cmd/cosmovisor/init.go @@ -6,12 +6,14 @@ import ( "io" "os" "path/filepath" + "time" "github.com/spf13/cobra" "cosmossdk.io/log" - "cosmossdk.io/tools/cosmovisor" "cosmossdk.io/x/upgrade/plan" + + "cosmossdk.io/tools/cosmovisor" ) var initCmd = &cobra.Command{ @@ -19,8 +21,7 @@ var initCmd = &cobra.Command{ Short: "Initialize a cosmovisor daemon home directory.", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - logger := cmd.Context().Value(log.ContextKey).(log.Logger) - return InitializeCosmovisor(logger, args) + return InitializeCosmovisor(nil, args) }, } @@ -43,6 +44,10 @@ func InitializeCosmovisor(logger log.Logger, args []string) error { return err } + if logger == nil { + logger = cfg.Logger(os.Stdout) + } + logger.Info("checking on the genesis/bin directory") genBinExe := cfg.GenesisBin() genBinDir, _ := filepath.Split(genBinExe) @@ -95,6 +100,15 @@ func getConfigForInitCmd() (*cosmovisor.Config, error) { Home: os.Getenv(cosmovisor.EnvHome), Name: os.Getenv(cosmovisor.EnvName), } + + var err error + if cfg.ColorLogs, err = cosmovisor.BooleanOption(cosmovisor.EnvColorLogs, true); err != nil { + errs = append(errs, err) + } + if cfg.TimeFormatLogs, err = cosmovisor.TimeFormatOptionFromEnv(cosmovisor.EnvTimeFormatLogs, time.Kitchen); err != nil { + errs = append(errs, err) + } + if len(cfg.Name) == 0 { errs = append(errs, fmt.Errorf("%s is not set", cosmovisor.EnvName)) } @@ -105,7 +119,7 @@ func getConfigForInitCmd() (*cosmovisor.Config, error) { errs = append(errs, fmt.Errorf("%s must be an absolute path", cosmovisor.EnvHome)) } if len(errs) > 0 { - return nil, errors.Join(errs...) + return cfg, errors.Join(errs...) } return cfg, nil } diff --git a/tools/cosmovisor/cmd/cosmovisor/init_test.go b/tools/cosmovisor/cmd/cosmovisor/init_test.go index be943263d41e..c45a43ad0472 100644 --- a/tools/cosmovisor/cmd/cosmovisor/init_test.go +++ b/tools/cosmovisor/cmd/cosmovisor/init_test.go @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/suite" "cosmossdk.io/log" + "cosmossdk.io/tools/cosmovisor" ) @@ -28,15 +29,24 @@ func TestInitTestSuite(t *testing.T) { // cosmovisorInitEnv are some string values of environment variables used to configure Cosmovisor, and used by the init command. type cosmovisorInitEnv struct { - Home string - Name string + Home string + Name string + ColorLogs string + TimeFormatLogs string +} + +type envMap struct { + val string + allowEmpty bool } // ToMap creates a map of the cosmovisorInitEnv where the keys are the env var names. -func (c cosmovisorInitEnv) ToMap() map[string]string { - return map[string]string{ - cosmovisor.EnvHome: c.Home, - cosmovisor.EnvName: c.Name, +func (c cosmovisorInitEnv) ToMap() map[string]envMap { + return map[string]envMap{ + cosmovisor.EnvHome: {val: c.Home, allowEmpty: false}, + cosmovisor.EnvName: {val: c.Name, allowEmpty: false}, + cosmovisor.EnvColorLogs: {val: c.ColorLogs, allowEmpty: false}, + cosmovisor.EnvTimeFormatLogs: {val: c.TimeFormatLogs, allowEmpty: true}, } } @@ -47,6 +57,10 @@ func (c *cosmovisorInitEnv) Set(envVar, envVal string) { c.Home = envVal case cosmovisor.EnvName: c.Name = envVal + case cosmovisor.EnvColorLogs: + c.Name = envVal + case cosmovisor.EnvTimeFormatLogs: + c.Name = envVal default: panic(fmt.Errorf("Unknown environment variable [%s]. Cannot set field to [%s]. ", envVar, envVal)) } @@ -77,9 +91,9 @@ func (s *InitTestSuite) setEnv(t *testing.T, env *cosmovisorInitEnv) { for envVar, envVal := range env.ToMap() { var err error var msg string - if len(envVal) != 0 { - err = os.Setenv(envVar, envVal) - msg = fmt.Sprintf("setting %s to %s", envVar, envVal) + if len(envVal.val) != 0 || envVal.allowEmpty { + err = os.Setenv(envVar, envVal.val) + msg = fmt.Sprintf("setting %s to %s", envVar, envVal.val) } else { err = os.Unsetenv(envVar) msg = fmt.Sprintf("unsetting %s", envVar) diff --git a/tools/cosmovisor/cmd/cosmovisor/main.go b/tools/cosmovisor/cmd/cosmovisor/main.go index 1b13832f4994..8ed5a2e9f9ed 100644 --- a/tools/cosmovisor/cmd/cosmovisor/main.go +++ b/tools/cosmovisor/cmd/cosmovisor/main.go @@ -3,15 +3,14 @@ package main import ( "context" "os" - - "cosmossdk.io/log" ) func main() { - logger := log.NewLogger(os.Stdout).With(log.ModuleKey, "cosmovisor") - ctx := context.WithValue(context.Background(), log.ContextKey, logger) + // error logger used only during configuration phase + cfg, _ := getConfigForInitCmd() + logger := cfg.Logger(os.Stderr) - if err := NewRootCmd().ExecuteContext(ctx); err != nil { + if err := NewRootCmd().ExecuteContext(context.Background()); err != nil { if errMulti, ok := err.(interface{ Unwrap() []error }); ok { err := errMulti.Unwrap() for _, e := range err { diff --git a/tools/cosmovisor/cmd/cosmovisor/run.go b/tools/cosmovisor/cmd/cosmovisor/run.go index fe300d58fa62..9192ecea1e7b 100644 --- a/tools/cosmovisor/cmd/cosmovisor/run.go +++ b/tools/cosmovisor/cmd/cosmovisor/run.go @@ -1,10 +1,11 @@ package main import ( - "cosmossdk.io/log" - "cosmossdk.io/tools/cosmovisor" - "github.com/rs/zerolog" + "os" + "github.com/spf13/cobra" + + "cosmossdk.io/tools/cosmovisor" ) var runCmd = &cobra.Command{ @@ -12,23 +13,19 @@ var runCmd = &cobra.Command{ Short: "Run an APP command.", SilenceUsage: true, DisableFlagParsing: true, - RunE: func(cmd *cobra.Command, args []string) error { - return Run(cmd, args) + RunE: func(_ *cobra.Command, args []string) error { + return run(args) }, } -// Run runs the configured program with the given args and monitors it for upgrades. -func Run(cmd *cobra.Command, args []string, options ...RunOption) error { +// run runs the configured program with the given args and monitors it for upgrades. +func run(args []string, options ...RunOption) error { cfg, err := cosmovisor.GetConfigFromEnv() if err != nil { return err } - logger := cmd.Context().Value(log.ContextKey).(log.Logger) - - if cfg.DisableLogs { - logger = log.NewCustomLogger(zerolog.Nop()) - } + logger := cfg.Logger(os.Stdout) runCfg := DefaultRunConfig for _, opt := range options { diff --git a/tools/cosmovisor/cmd/cosmovisor/run_config.go b/tools/cosmovisor/cmd/cosmovisor/run_config.go index 5a77e30b4186..3f865c610933 100644 --- a/tools/cosmovisor/cmd/cosmovisor/run_config.go +++ b/tools/cosmovisor/cmd/cosmovisor/run_config.go @@ -26,7 +26,7 @@ func StdOutRunOption(w io.Writer) RunOption { } } -// SdErrRunOption sets the StdErr writer for the Run command +// StdErrRunOption sets the StdErr writer for the Run command func StdErrRunOption(w io.Writer) RunOption { return func(cfg *RunConfig) { cfg.StdErr = w diff --git a/tools/cosmovisor/cmd/cosmovisor/version.go b/tools/cosmovisor/cmd/cosmovisor/version.go index a1c8e1bcb3c9..df1c3cd1520a 100644 --- a/tools/cosmovisor/cmd/cosmovisor/version.go +++ b/tools/cosmovisor/cmd/cosmovisor/version.go @@ -6,8 +6,9 @@ import ( "runtime/debug" "strings" - "cosmossdk.io/tools/cosmovisor" "github.com/spf13/cobra" + + "cosmossdk.io/tools/cosmovisor" ) func NewVersionCmd() *cobra.Command { @@ -45,7 +46,7 @@ func printVersion(cmd *cobra.Command, args []string, noAppVersion bool) error { return nil } - if err := Run(cmd, append([]string{"version"}, args...)); err != nil { + if err := run(append([]string{"version"}, args...)); err != nil { return fmt.Errorf("failed to run version command: %w", err) } @@ -59,8 +60,7 @@ func printVersionJSON(cmd *cobra.Command, args []string, noAppVersion bool) erro } buf := new(strings.Builder) - if err := Run( - cmd, + if err := run( []string{"version", "--long", "--output", "json"}, StdOutRunOption(buf), ); err != nil {