diff --git a/cmd/root.go b/cmd/root.go index 2375b414..c0e37efd 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -80,6 +80,7 @@ See ochami-config(5) for more details on configuring the ochami config file(s).` rootCmd.PersistentFlags().StringVarP(&cli.ConfigFile, "config", "c", "", "path to configuration file to use") rootCmd.PersistentFlags().StringP("log-format", "L", "", "log format (json,rfc3339,basic)") rootCmd.PersistentFlags().StringP("log-level", "l", "", "set verbosity of logs (info,warning,debug)") + rootCmd.PersistentFlags().String("log-color", "", "set coloring of logs (auto,on,off)") rootCmd.PersistentFlags().StringP("cluster", "C", "", "name of cluster whose config to use for this command") rootCmd.PersistentFlags().StringP("cluster-uri", "u", "", "base URI for OpenCHAMI services, excluding service base path (overrides cluster.uri in config file)") rootCmd.PersistentFlags().StringVar(&cli.CACertPath, "cacert", "", "path to root CA certificate in PEM format") diff --git a/doc/config.example.yaml b/doc/config.example.yaml index 1c62bdcc..f1eec20b 100644 --- a/doc/config.example.yaml +++ b/doc/config.example.yaml @@ -20,6 +20,13 @@ log: # basic - Specifies log level but no timestamp data format: json + # Color output. Specify whether or not to color output. Available values are: + # + # on - Logs are colored unconditionally + # off - Logs are uncolored unconditionally + # auto - Logs are colored if stderr is a terminal (default) + color: auto + # Specify the name of the cluster to use by default. If this is not specified, # --cluster must be used on the command line to specify the name of the cluster # to use when communicating with OpenCHAMI services. diff --git a/go.mod b/go.mod index c670cc6b..282e0e44 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/spf13/cobra v1.10.2 github.com/synackd/go-kargs v0.0.1-beta.1 github.com/vbauerster/mpb/v8 v8.10.2 + golang.org/x/term v0.44.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index e2410d90..2821fd23 100644 --- a/go.sum +++ b/go.sum @@ -227,6 +227,8 @@ golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= diff --git a/internal/cli/cli.go b/internal/cli/cli.go index f2fb46a2..50651bb8 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -186,7 +186,15 @@ func InitLogging(cmd *cobra.Command) error { config.GlobalConfig.Log.Level = ll } - if err := log.Init(config.GlobalConfig.Log.Level, config.GlobalConfig.Log.Format); err != nil { + if cmd.Flags().Changed("log-color") { + lc, err := cmd.Flags().GetString("log-color") + if err != nil { + return fmt.Errorf("failed to fetch flag log-color: %w", err) + } + config.GlobalConfig.Log.Color = lc + } + + if err := log.Init(config.GlobalConfig.Log.Level, config.GlobalConfig.Log.Format, config.GlobalConfig.Log.Color); err != nil { return fmt.Errorf("failed to Initialize logger: %w", err) } diff --git a/internal/config/config.go b/internal/config/config.go index b5e03271..5ea46662 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -55,6 +55,7 @@ var DefaultConfig = Config{ Log: ConfigLog{ Format: "rfc3339", Level: "warning", + Color: "auto", }, Timeout: 30 * time.Second, } @@ -118,7 +119,7 @@ func (c *Config) UnmarshalYAML(value *yaml.Node) error { Line: n.Content[i].Line, } } - // If key was found and is not empty, set our sentinal + // If key was found and is not empty, set our sentinel hasTimeout = true break } @@ -157,6 +158,7 @@ func (c Config) GetCluster(name string) (ConfigCluster, error) { type ConfigLog struct { Format string `yaml:"format,omitempty"` Level string `yaml:"level,omitempty"` + Color string `yaml:"color,omitempty"` } // ConfigCluster is a "wrapper" around an individual cluster configuration. It diff --git a/internal/log/log.go b/internal/log/log.go index 161bcfd6..4550f64f 100644 --- a/internal/log/log.go +++ b/internal/log/log.go @@ -16,6 +16,7 @@ import ( "time" "github.com/rs/zerolog" + "golang.org/x/term" "github.com/OpenCHAMI/ochami/internal/version" ) @@ -30,7 +31,7 @@ var ( // Init() initializes the global logging object so it can be used for logging by // any package that imports this internal log package. -func Init(ll, lf string) error { +func Init(ll, lf, lc string) error { var loggerLevel zerolog.Level switch ll { case "warning": @@ -44,6 +45,18 @@ func Init(ll, lf string) error { } cw := zerolog.ConsoleWriter{Out: os.Stderr} + + switch lc { + case "", "auto": + cw.NoColor = !term.IsTerminal(int(os.Stderr.Fd())) + case "on": + cw.NoColor = false + case "off": + cw.NoColor = true + default: + return fmt.Errorf("invalid log-color: %s", lc) + } + switch lf { case "rfc3339": cw.TimeFormat = time.RFC3339 diff --git a/internal/log/log_test.go b/internal/log/log_test.go index eacf2d2c..17e59d41 100644 --- a/internal/log/log_test.go +++ b/internal/log/log_test.go @@ -15,6 +15,7 @@ func TestInit(t *testing.T) { type args struct { ll string lf string + lc string } tests := []struct { name string @@ -26,6 +27,7 @@ func TestInit(t *testing.T) { args: args{ ll: "warning", lf: "basic", + lc: "auto", }, wantErr: false, }, @@ -34,6 +36,7 @@ func TestInit(t *testing.T) { args: args{ ll: "unsupported", lf: "basic", + lc: "auto", }, wantErr: true, }, @@ -42,6 +45,7 @@ func TestInit(t *testing.T) { args: args{ ll: "warning", lf: "unsupported", + lc: "auto", }, wantErr: true, }, @@ -50,13 +54,23 @@ func TestInit(t *testing.T) { args: args{ ll: "unsupported", lf: "unsupported", + lc: "auto", + }, + wantErr: true, + }, + { + name: "supported level and format, unsupported color", + args: args{ + ll: "warning", + lf: "basic", + lc: "unsupported", }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := Init(tt.args.ll, tt.args.lf); (err != nil) != tt.wantErr { + if err := Init(tt.args.ll, tt.args.lf, tt.args.lc); (err != nil) != tt.wantErr { t.Errorf("Init() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/man/ochami-config.5.sc b/man/ochami-config.5.sc index 96140197..e3a1e568 100644 --- a/man/ochami-config.5.sc +++ b/man/ochami-config.5.sc @@ -63,6 +63,17 @@ These configuration options are global configuration options. - _warning_ - _debug_ + *color:* _color_ + Enable/disable logging color. *auto* detects whether or not stderr is a + terminal and enables color accordingly. + + Default: *auto* + Supported: + + - _auto_ + - _on_ + - _off_ + *timeout:* _duration_ The timeout to use for HTTP requests. This is a duration string as accepted by Go's duration parser (e.g. _30s_, _5m_, _1m30s_). diff --git a/man/ochami.1.sc b/man/ochami.1.sc index ed72fe16..b3d3c844 100644 --- a/man/ochami.1.sc +++ b/man/ochami.1.sc @@ -139,6 +139,16 @@ _foobar_. - _warning_ - _debug_ +*--log-color* _color_ + Enable/disable logging color. *auto* detects whether or not stderr is a + terminal and enables color accordingly. Defaults to _auto_. + + Supported: + + - _auto_ + - _on_ + - _off_ + *--no-token* Disable reading of and checking for access token and do not include any token in the request headers. This overrides the value of *enable-auth* set