Skip to content

Commit 79ebad0

Browse files
GridexXpiksel
andauthored
feat: allow logging output to use JSON formatter (#1705)
Co-authored-by: nils måsén <[email protected]>
1 parent 897b171 commit 79ebad0

File tree

4 files changed

+114
-18
lines changed

4 files changed

+114
-18
lines changed

cmd/root.go

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmd
22

33
import (
4+
"errors"
45
"math"
56
"net/http"
67
"os"
@@ -78,23 +79,8 @@ func Execute() {
7879
func PreRun(cmd *cobra.Command, _ []string) {
7980
f := cmd.PersistentFlags()
8081
flags.ProcessFlagAliases(f)
81-
82-
if enabled, _ := f.GetBool("no-color"); enabled {
83-
log.SetFormatter(&log.TextFormatter{
84-
DisableColors: true,
85-
})
86-
} else {
87-
// enable logrus built-in support for https://bixense.com/clicolors/
88-
log.SetFormatter(&log.TextFormatter{
89-
EnvironmentOverrideColors: true,
90-
})
91-
}
92-
93-
rawLogLevel, _ := f.GetString(`log-level`)
94-
if logLevel, err := log.ParseLevel(rawLogLevel); err != nil {
95-
log.Fatalf("Invalid log level: %s", err.Error())
96-
} else {
97-
log.SetLevel(logLevel)
82+
if err := flags.SetupLogging(f); err != nil {
83+
log.Fatalf("Failed to initialize logging: %s", err.Error())
9884
}
9985

10086
scheduleSpec, _ = f.GetString("schedule")
@@ -201,7 +187,7 @@ func Run(c *cobra.Command, names []string) {
201187
httpAPI.RegisterHandler(metricsHandler.Path, metricsHandler.Handle)
202188
}
203189

204-
if err := httpAPI.Start(enableUpdateAPI && !unblockHTTPAPI); err != nil && err != http.ErrServerClosed {
190+
if err := httpAPI.Start(enableUpdateAPI && !unblockHTTPAPI); err != nil && !errors.Is(err, http.ErrServerClosed) {
205191
log.Error("failed to start API", err)
206192
}
207193

docs/arguments.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,17 @@ Environment Variable: WATCHTOWER_LOG_LEVEL
134134
Default: info
135135
```
136136

137+
## Logging format
138+
139+
Sets what logging format to use for console output.
140+
141+
```text
142+
Argument: --log-format, -l
143+
Environment Variable: WATCHTOWER_LOG_FORMAT
144+
Possible values: Auto, LogFmt, Pretty or JSON
145+
Default: Auto
146+
```
147+
137148
## ANSI colors
138149
Disable ANSI color escape codes in log output.
139150

@@ -407,6 +418,7 @@ Environment Variable: WATCHTOWER_WARN_ON_HEAD_FAILURE
407418
Possible values: always, auto, never
408419
Default: auto
409420
```
421+
410422
## Programatic Output (porcelain)
411423

412424
Writes the session results to STDOUT using a stable, machine-readable format (indicated by the argument VERSION).

internal/flags/flags.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
8585
envBool("WATCHTOWER_LABEL_ENABLE"),
8686
"Watch containers where the com.centurylinklabs.watchtower.enable label is true")
8787

88+
flags.StringP(
89+
"log-format",
90+
"l",
91+
viper.GetString("WATCHTOWER_LOG_FORMAT"),
92+
"Sets what logging format to use for console output. Possible values: Auto, LogFmt, Pretty, JSON")
93+
8894
flags.BoolP(
8995
"debug",
9096
"d",
@@ -409,6 +415,7 @@ func SetDefaults() {
409415
viper.SetDefault("WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG", "")
410416
viper.SetDefault("WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER", "watchtower")
411417
viper.SetDefault("WATCHTOWER_LOG_LEVEL", "info")
418+
viper.SetDefault("WATCHTOWER_LOG_FORMAT", "auto")
412419
}
413420

414421
// EnvConfig translates the command-line options into environment variables
@@ -611,6 +618,46 @@ func ProcessFlagAliases(flags *pflag.FlagSet) {
611618

612619
}
613620

621+
// SetupLogging reads only the flags that is needed to set up logging and applies them to the global logger
622+
func SetupLogging(f *pflag.FlagSet) error {
623+
logFormat, _ := f.GetString(`log-format`)
624+
noColor, _ := f.GetBool("no-color")
625+
626+
switch strings.ToLower(logFormat) {
627+
case "auto":
628+
// This will either use the "pretty" or "logfmt" format, based on whether the standard out is connected to a TTY
629+
log.SetFormatter(&log.TextFormatter{
630+
DisableColors: noColor,
631+
// enable logrus built-in support for https://bixense.com/clicolors/
632+
EnvironmentOverrideColors: true,
633+
})
634+
case "json":
635+
log.SetFormatter(&log.JSONFormatter{})
636+
case "logfmt":
637+
log.SetFormatter(&log.TextFormatter{
638+
DisableColors: true,
639+
FullTimestamp: true,
640+
})
641+
case "pretty":
642+
log.SetFormatter(&log.TextFormatter{
643+
// "Pretty" format combined with `--no-color` will only change the timestamp to the time since start
644+
ForceColors: !noColor,
645+
FullTimestamp: false,
646+
})
647+
default:
648+
return fmt.Errorf("invalid log format: %s", logFormat)
649+
}
650+
651+
rawLogLevel, _ := f.GetString(`log-level`)
652+
if logLevel, err := log.ParseLevel(rawLogLevel); err != nil {
653+
return fmt.Errorf("invalid log level: %e", err)
654+
} else {
655+
log.SetLevel(logLevel)
656+
}
657+
658+
return nil
659+
}
660+
614661
func flagIsEnabled(flags *pflag.FlagSet, name string) bool {
615662
value, err := flags.GetBool(name)
616663
if err != nil {

internal/flags/flags_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,57 @@ func TestProcessFlagAliasesLogLevelFromEnvironment(t *testing.T) {
177177
assert.Equal(t, `debug`, logLevel)
178178
}
179179

180+
func TestLogFormatFlag(t *testing.T) {
181+
cmd := new(cobra.Command)
182+
183+
SetDefaults()
184+
RegisterDockerFlags(cmd)
185+
RegisterSystemFlags(cmd)
186+
187+
// Ensure the default value is Auto
188+
require.NoError(t, cmd.ParseFlags([]string{}))
189+
require.NoError(t, SetupLogging(cmd.Flags()))
190+
assert.IsType(t, &logrus.TextFormatter{}, logrus.StandardLogger().Formatter)
191+
192+
// Test JSON format
193+
require.NoError(t, cmd.ParseFlags([]string{`--log-format`, `JSON`}))
194+
require.NoError(t, SetupLogging(cmd.Flags()))
195+
assert.IsType(t, &logrus.JSONFormatter{}, logrus.StandardLogger().Formatter)
196+
197+
// Test Pretty format
198+
require.NoError(t, cmd.ParseFlags([]string{`--log-format`, `pretty`}))
199+
require.NoError(t, SetupLogging(cmd.Flags()))
200+
assert.IsType(t, &logrus.TextFormatter{}, logrus.StandardLogger().Formatter)
201+
textFormatter, ok := (logrus.StandardLogger().Formatter).(*logrus.TextFormatter)
202+
assert.True(t, ok)
203+
assert.True(t, textFormatter.ForceColors)
204+
assert.False(t, textFormatter.FullTimestamp)
205+
206+
// Test LogFmt format
207+
require.NoError(t, cmd.ParseFlags([]string{`--log-format`, `logfmt`}))
208+
require.NoError(t, SetupLogging(cmd.Flags()))
209+
textFormatter, ok = (logrus.StandardLogger().Formatter).(*logrus.TextFormatter)
210+
assert.True(t, ok)
211+
assert.True(t, textFormatter.DisableColors)
212+
assert.True(t, textFormatter.FullTimestamp)
213+
214+
// Test invalid format
215+
require.NoError(t, cmd.ParseFlags([]string{`--log-format`, `cowsay`}))
216+
require.Error(t, SetupLogging(cmd.Flags()))
217+
}
218+
219+
func TestLogLevelFlag(t *testing.T) {
220+
cmd := new(cobra.Command)
221+
222+
SetDefaults()
223+
RegisterDockerFlags(cmd)
224+
RegisterSystemFlags(cmd)
225+
226+
// Test invalid format
227+
require.NoError(t, cmd.ParseFlags([]string{`--log-level`, `gossip`}))
228+
require.Error(t, SetupLogging(cmd.Flags()))
229+
}
230+
180231
func TestProcessFlagAliasesSchedAndInterval(t *testing.T) {
181232
logrus.StandardLogger().ExitFunc = func(_ int) { panic(`FATAL`) }
182233
cmd := new(cobra.Command)

0 commit comments

Comments
 (0)