From 6b0c37f3d3fb1fccb70db306f9e6a49f8a150aac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= Date: Sat, 12 Aug 2023 17:02:24 +0200 Subject: [PATCH] switch to `log-format` flag --- cmd/root.go | 52 +++++++++++------------------------- docs/arguments.md | 14 +++++----- internal/flags/flags.go | 51 +++++++++++++++++++++++++++++++---- internal/flags/flags_test.go | 51 +++++++++++++++++++++++++++-------- 4 files changed, 109 insertions(+), 59 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 55221cc..8ca2630 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,6 +1,7 @@ package cmd import ( + "errors" "math" "net/http" "os" @@ -28,18 +29,17 @@ import ( ) var ( - client container.Client - scheduleSpec string - cleanup bool - noRestart bool - monitorOnly bool - enableLabel bool - enableJsonFormatter bool - notifier t.Notifier - timeout time.Duration - lifecycleHooks bool - rollingRestart bool - scope string + client container.Client + scheduleSpec string + cleanup bool + noRestart bool + monitorOnly bool + enableLabel bool + notifier t.Notifier + timeout time.Duration + lifecycleHooks bool + rollingRestart bool + scope string ) var rootCmd = NewRootCommand() @@ -77,31 +77,11 @@ func Execute() { // PreRun is a lifecycle hook that runs before the command is executed. func PreRun(cmd *cobra.Command, _ []string) { f := cmd.PersistentFlags() + if err := flags.SetupLogging(f); err != nil { + log.Fatalf("Failed to initialize logging: %s", err.Error()) + } flags.ProcessFlagAliases(f) - if enabled, _ := f.GetBool("no-color"); enabled { - log.SetFormatter(&log.TextFormatter{ - DisableColors: true, - }) - } else { - // enable logrus built-in support for https://bixense.com/clicolors/ - log.SetFormatter(&log.TextFormatter{ - EnvironmentOverrideColors: true, - }) - } - - rawLogLevel, _ := f.GetString(`log-level`) - if logLevel, err := log.ParseLevel(rawLogLevel); err != nil { - log.Fatalf("Invalid log level: %s", err.Error()) - } else { - log.SetLevel(logLevel) - } - - enableJsonFormatter, _ = f.GetBool("json-logging") - if enableJsonFormatter { - log.SetFormatter(&log.JSONFormatter{}) - } - scheduleSpec, _ = f.GetString("schedule") flags.GetSecretsFromFiles(cmd) @@ -205,7 +185,7 @@ func Run(c *cobra.Command, names []string) { httpAPI.RegisterHandler(metricsHandler.Path, metricsHandler.Handle) } - if err := httpAPI.Start(enableUpdateAPI && !unblockHTTPAPI); err != nil && err != http.ErrServerClosed { + if err := httpAPI.Start(enableUpdateAPI && !unblockHTTPAPI); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Error("failed to start API", err) } diff --git a/docs/arguments.md b/docs/arguments.md index 0b24c36..515c2c0 100644 --- a/docs/arguments.md +++ b/docs/arguments.md @@ -107,15 +107,15 @@ Environment Variable: WATCHTOWER_LOG_LEVEL Default: info ``` -## JSON Logging +## Logging format -Enables the JSON log formatter for logging, changing the log output to JSON format. By default, the log output follows the standard text-based format. +Sets what logging format to use for console output. ```text - Argument: --json-logging -Environment Variable: WATCHTOWER_JSON_LOGGING - Type: Boolean - Default: false + Argument: --log-format, -l +Environment Variable: WATCHTOWER_LOG_FORMAT + Possible values: Auto, LogFmt, Pretty or JSON + Default: Auto ``` ## ANSI colors @@ -374,4 +374,4 @@ requests and may rate limit pull requests (mainly docker.io). Environment Variable: WATCHTOWER_WARN_ON_HEAD_FAILURE Possible values: always, auto, never Default: auto -``` \ No newline at end of file +``` diff --git a/internal/flags/flags.go b/internal/flags/flags.go index 12665fe..f43d2d7 100644 --- a/internal/flags/flags.go +++ b/internal/flags/flags.go @@ -86,11 +86,11 @@ func RegisterSystemFlags(rootCmd *cobra.Command) { viper.GetBool("WATCHTOWER_LABEL_ENABLE"), "Watch containers where the com.centurylinklabs.watchtower.enable label is true") - flags.BoolP( - "json-logging", - "j", - viper.GetBool("WATCHTOWER_JSON_LOGGING"), - "Enables the JSON log formatter for logging") + flags.StringP( + "log-format", + "l", + viper.GetString("WATCHTOWER_LOG_FORMAT"), + "Sets what logging format to use for console output. Possible values: Auto, LogFmt, Pretty, JSON") flags.BoolP( "debug", @@ -385,6 +385,7 @@ func SetDefaults() { viper.SetDefault("WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG", "") viper.SetDefault("WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER", "watchtower") viper.SetDefault("WATCHTOWER_LOG_LEVEL", "info") + viper.SetDefault("WATCHTOWER_LOG_FORMAT", "auto") } // EnvConfig translates the command-line options into environment variables @@ -583,6 +584,46 @@ func ProcessFlagAliases(flags *pflag.FlagSet) { } +// SetupLogging reads only the flags that is needed to set up logging and applies them to the global logger +func SetupLogging(f *pflag.FlagSet) error { + logFormat, _ := f.GetString(`log-format`) + noColor, _ := f.GetBool("no-color") + + switch strings.ToLower(logFormat) { + case "auto": + // This will either use the "pretty" or "logfmt" format, based on whether the standard out is connected to a TTY + log.SetFormatter(&log.TextFormatter{ + DisableColors: noColor, + // enable logrus built-in support for https://bixense.com/clicolors/ + EnvironmentOverrideColors: true, + }) + case "json": + log.SetFormatter(&log.JSONFormatter{}) + case "logfmt": + log.SetFormatter(&log.TextFormatter{ + DisableColors: true, + FullTimestamp: true, + }) + case "pretty": + log.SetFormatter(&log.TextFormatter{ + // "Pretty" format combined with `--no-color` will only change the timestamp to the time since start + ForceColors: !noColor, + FullTimestamp: false, + }) + default: + return fmt.Errorf("invalid log format: %s", logFormat) + } + + rawLogLevel, _ := f.GetString(`log-level`) + if logLevel, err := log.ParseLevel(rawLogLevel); err != nil { + return fmt.Errorf("invalid log level: %e", err) + } else { + log.SetLevel(logLevel) + } + + return nil +} + func flagIsEnabled(flags *pflag.FlagSet, name string) bool { value, err := flags.GetBool(name) if err != nil { diff --git a/internal/flags/flags_test.go b/internal/flags/flags_test.go index d102e07..ba676da 100644 --- a/internal/flags/flags_test.go +++ b/internal/flags/flags_test.go @@ -183,26 +183,55 @@ func TestProcessFlagAliasesLogLevelFromEnvironment(t *testing.T) { assert.Equal(t, `debug`, logLevel) } -func TestJSONLoggingFlag(t *testing.T) { +func TestLogFormatFlag(t *testing.T) { cmd := new(cobra.Command) SetDefaults() RegisterDockerFlags(cmd) RegisterSystemFlags(cmd) - // Ensure the default value is false - enableJsonFormatter, err := cmd.PersistentFlags().GetBool("json-logging") - require.NoError(t, err) + // Ensure the default value is Auto + require.NoError(t, cmd.ParseFlags([]string{})) + require.NoError(t, SetupLogging(cmd.Flags())) + assert.IsType(t, &logrus.TextFormatter{}, logrus.StandardLogger().Formatter) - assert.Equal(t, false, enableJsonFormatter) + // Test JSON format + require.NoError(t, cmd.ParseFlags([]string{`--log-format`, `JSON`})) + require.NoError(t, SetupLogging(cmd.Flags())) + assert.IsType(t, &logrus.JSONFormatter{}, logrus.StandardLogger().Formatter) - // Test with the argument - require.NoError(t, cmd.ParseFlags([]string{`--json-logging`})) - flags := cmd.Flags() - enableJsonFormatter, _ = flags.GetBool("json-logging") - require.NoError(t, err) - assert.Equal(t, true, enableJsonFormatter) + // Test Pretty format + require.NoError(t, cmd.ParseFlags([]string{`--log-format`, `pretty`})) + require.NoError(t, SetupLogging(cmd.Flags())) + assert.IsType(t, &logrus.TextFormatter{}, logrus.StandardLogger().Formatter) + textFormatter, ok := (logrus.StandardLogger().Formatter).(*logrus.TextFormatter) + assert.True(t, ok) + assert.True(t, textFormatter.ForceColors) + assert.False(t, textFormatter.FullTimestamp) + // Test LogFmt format + require.NoError(t, cmd.ParseFlags([]string{`--log-format`, `logfmt`})) + require.NoError(t, SetupLogging(cmd.Flags())) + textFormatter, ok = (logrus.StandardLogger().Formatter).(*logrus.TextFormatter) + assert.True(t, ok) + assert.True(t, textFormatter.DisableColors) + assert.True(t, textFormatter.FullTimestamp) + + // Test invalid format + require.NoError(t, cmd.ParseFlags([]string{`--log-format`, `cowsay`})) + require.Error(t, SetupLogging(cmd.Flags())) +} + +func TestLogLevelFlag(t *testing.T) { + cmd := new(cobra.Command) + + SetDefaults() + RegisterDockerFlags(cmd) + RegisterSystemFlags(cmd) + + // Test invalid format + require.NoError(t, cmd.ParseFlags([]string{`--log-level`, `gossip`})) + require.Error(t, SetupLogging(cmd.Flags())) } func TestProcessFlagAliasesSchedAndInterval(t *testing.T) {