diff --git a/cli/cli.go b/cli/cli.go index 87909759..d259e446 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -22,6 +22,7 @@ package cli import ( "encoding/json" "fmt" + "io/ioutil" "log" "os" "strings" @@ -33,9 +34,12 @@ import ( "github.com/arduino/FirmwareUploader/modules/winc" "github.com/arduino/FirmwareUploader/utils" "github.com/arduino/FirmwareUploader/utils/context" + v "github.com/arduino/FirmwareUploader/version" "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/go-paths-helper" + "github.com/mattn/go-colorable" + "github.com/rifflock/lfshook" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -43,6 +47,10 @@ import ( var ( ctx = &context.Context{} outputFormat string + verbose bool + logFile string + logFormat string + logLevel string ) func NewCommand() *cobra.Command { @@ -70,8 +78,14 @@ func NewCommand() *cobra.Command { firmwareUploaderCli.Flags().StringVar(&ctx.Model, "model", "", "module model (winc, nina or sara)") firmwareUploaderCli.Flags().StringVar(&ctx.BoardName, "get_available_for", "", "Ask for available firmwares matching a given board") firmwareUploaderCli.Flags().IntVar(&ctx.Retries, "retries", 9, "Number of retries in case of upload failure") + firmwareUploaderCli.PersistentFlags().StringVar(&outputFormat, "format", "text", "The output format, can be {text|json}.") + firmwareUploaderCli.PersistentFlags().StringVar(&logFile, "log-file", "", "Path to the file where logs will be written") + firmwareUploaderCli.PersistentFlags().StringVar(&logFormat, "log-format", "", "The output format for the logs, can be {text|json}.") + firmwareUploaderCli.PersistentFlags().StringVar(&logLevel, "log-level", "info", "Messages with this level and above will be logged. Valid levels are: trace, debug, info, warn, error, fatal, panic") + firmwareUploaderCli.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Print the logs on the standard output.") + return firmwareUploaderCli } @@ -129,6 +143,22 @@ func run(cmd *cobra.Command, args []string) { } } +// Convert the string passed to the `--log-level` option to the corresponding +// logrus formal level. +func toLogLevel(s string) (t logrus.Level, found bool) { + t, found = map[string]logrus.Level{ + "trace": logrus.TraceLevel, + "debug": logrus.DebugLevel, + "info": logrus.InfoLevel, + "warn": logrus.WarnLevel, + "error": logrus.ErrorLevel, + "fatal": logrus.FatalLevel, + "panic": logrus.PanicLevel, + }[s] + + return +} + func parseFormatString(arg string) (feedback.OutputFormat, bool) { f, found := map[string]feedback.OutputFormat{ "json": feedback.JSON, @@ -139,6 +169,47 @@ func parseFormatString(arg string) (feedback.OutputFormat, bool) { } func preRun(cmd *cobra.Command, args []string) { + // Prepare logging + if verbose { + // if we print on stdout, do it in full colors + logrus.SetOutput(colorable.NewColorableStdout()) + logrus.SetFormatter(&logrus.TextFormatter{ + ForceColors: true, + }) + } else { + logrus.SetOutput(ioutil.Discard) + } + + // Normalize the format strings + logFormat = strings.ToLower(logFormat) + if logFormat == "json" { + logrus.SetFormatter(&logrus.JSONFormatter{}) + } + + logFile := "" + if logFile != "" { + file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + fmt.Printf("Unable to open file for logging: %s", logFile) + os.Exit(errorcodes.ErrBadCall) + } + + // Use a hook so we don't get color codes in the log file + if outputFormat == "json" { + logrus.AddHook(lfshook.NewHook(file, &logrus.JSONFormatter{})) + } else { + logrus.AddHook(lfshook.NewHook(file, &logrus.TextFormatter{})) + } + } + + // Configure logging filter + if lvl, found := toLogLevel(logLevel); !found { + feedback.Errorf("Invalid option for --log-level: %s", logLevel) + os.Exit(errorcodes.ErrBadArgument) + } else { + logrus.SetLevel(lvl) + } + // // Prepare the Feedback system // @@ -148,13 +219,15 @@ func preRun(cmd *cobra.Command, args []string) { // check the right output format was passed format, found := parseFormatString(outputFormat) if !found { - feedback.Error("Invalid output format: " + outputFormat) + feedback.Errorf("Invalid output format: %s", outputFormat) os.Exit(errorcodes.ErrBadCall) } // use the output format to configure the Feedback feedback.SetFormat(format) + logrus.Info(v.VersionInfo) + if outputFormat != "text" { cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { logrus.Warn("Calling help on JSON format") diff --git a/go.mod b/go.mod index 3f3e129a..38a64f0c 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,9 @@ replace go.bug.st/serial => github.com/cmaglie/go-serial v0.0.0-20200923162623-b require ( github.com/arduino/arduino-cli v0.0.0-20210422154105-5aa424818026 github.com/arduino/go-paths-helper v1.4.0 + github.com/mattn/go-colorable v0.1.8 // indirect github.com/pkg/errors v0.9.1 + github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 // indirect github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.1.3 github.com/stretchr/testify v1.6.1 diff --git a/go.sum b/go.sum index 2210f05e..b54b049a 100644 --- a/go.sum +++ b/go.sum @@ -179,8 +179,12 @@ github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/marcinbor85/gohex v0.0.0-20210308104911-55fb1c624d84/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mdlayher/genetlink v0.0.0-20190313224034-60417448a851/go.mod h1:EsbsAEUEs15qC1cosAwxgCWV0Qhd8TmkxnA9Kw1Vhl4= @@ -225,6 +229,7 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo= github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -359,6 +364,8 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=