diff --git a/cmd/cmd.go b/cmd/cmd.go index 782aedbfd..225e71cb9 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -274,12 +274,10 @@ func Run(Retry bool, showStats bool, cmd *cobra.Command, f func() error) { if err != nil { log.Fatalf("Failed to %s: %v", cmd.Name(), err) } - if showStats && (!fs.Config.Quiet || fs.Stats.Errored() || *statsInterval > 0) { + if showStats && (fs.Stats.Errored() || *statsInterval > 0) { fs.Infof(nil, "%s", fs.Stats) } - if fs.Config.Verbose { - fs.Debugf(nil, "Go routines at exit %d\n", runtime.NumGoroutine()) - } + fs.Debugf(nil, "Go routines at exit %d\n", runtime.NumGoroutine()) if fs.Stats.Errored() { os.Exit(1) } diff --git a/cmd/mount/fs_test.go b/cmd/mount/fs_test.go index 4c8b3bdea..a58d4d7b3 100644 --- a/cmd/mount/fs_test.go +++ b/cmd/mount/fs_test.go @@ -71,8 +71,9 @@ func newRun() *Run { // "RCLONE_CONFIG_PASS=hunter2" (or your password) *fs.AskPassword = false fs.LoadConfig() - fs.Config.Verbose = *Verbose - fs.Config.Quiet = !*Verbose + if *Verbose { + fs.Config.LogLevel = fs.LogLevelDebug + } fs.Config.DumpHeaders = *DumpHeaders fs.Config.DumpBodies = *DumpBodies fs.Config.LowLevelRetries = *LowLevelRetries diff --git a/docs/content/docs.md b/docs/content/docs.md index 57436eb74..92e650d7d 100644 --- a/docs/content/docs.md +++ b/docs/content/docs.md @@ -362,6 +362,22 @@ This can be useful for tracking down problems with syncs in combination with the `-v` flag. See the Logging section for more info. +### --log-level LEVEL ### + +This sets the log level for rclone. The default log level is `INFO`. + +`DEBUG` is equivalent to `-vv`. It outputs lots of debug info - useful +for bug reports and really finding out what rclone is doing. + +`INFO` is equivalent to `-v`. It outputs information about each transfer +and prints stats once a minute by default. + +`NOTICE` is the default log level if no logging flags are supplied. It +outputs very little when things are working normally. It outputs +warnings and significant events. + +`ERROR` is equivalent to `-q`. It only output error messages. + ### --low-level-retries NUMBER ### This controls the number of low level retries rclone does. @@ -568,12 +584,14 @@ This can be useful when transferring to a remote which doesn't support mod times directly as it is more accurate than a `--size-only` check and faster than using `--checksum`. -### -v, --verbose ### +### -v, -vv, --verbose ### -If you set this flag, rclone will become very verbose telling you -about every file it considers and transfers. +With `-v` rclone will tell you about each file that is transferred and +a small number of significant events. -Very useful for debugging. +With `-vv` rclone will become very verbose telling you about every +file it considers and transfers. Please send bug reports with a log +with this setting. ### -V, --version ### @@ -762,22 +780,34 @@ See the [filtering section](/filtering/). Logging ------- -rclone has 3 levels of logging, `Error`, `Info` and `Debug`. +rclone has 4 levels of logging, `Error`, `Notice`, `Info` and `Debug`. -By default rclone logs `Error` and `Info` to standard error and `Debug` -to standard output. This means you can redirect standard output and -standard error to different places. +By default rclone logs to standard error. This means you can redirect +standard error and still see the normal output of rclone commands (eg +`rclone ls`). -By default rclone will produce `Error` and `Info` level messages. +By default rclone will produce `Error` and `Notice` level messages. If you use the `-q` flag, rclone will only produce `Error` messages. -If you use the `-v` flag, rclone will produce `Error`, `Info` and -`Debug` messages. +If you use the `-v` flag, rclone will produce `Error`, `Notice` and +`Info` messages. + +If you use the `-vv` flag, rclone will produce `Error`, `Notice`, +`Info` and `Debug` messages. + +You can also control the log levels with the `--log-level` flag. If you use the `--log-file=FILE` option, rclone will redirect `Error`, `Info` and `Debug` messages along with standard error to FILE. +If you use the `--syslog` flag then rclone will log to syslog and the +`--syslog-facility` control which facility it uses. + +Rclone prefixes all log messages with their level in capitals, eg INFO +which makes it easy to grep the log file for different kinds of +information. + Exit Code --------- diff --git a/fs/config.go b/fs/config.go index ee12490d1..78695aa66 100644 --- a/fs/config.go +++ b/fs/config.go @@ -26,6 +26,7 @@ import ( "github.com/Unknwon/goconfig" "github.com/pkg/errors" + "github.com/spf13/pflag" "golang.org/x/crypto/nacl/secretbox" "golang.org/x/text/unicode/norm" ) @@ -56,8 +57,9 @@ var ( // Config is the global config Config = &ConfigInfo{} // Flags - verbose = BoolP("verbose", "v", false, "Print lots more stuff") + verbose = CountP("verbose", "v", "Print lots more stuff (repeat for more)") quiet = BoolP("quiet", "q", false, "Print as little stuff as possible") + logLevel = StringP("log-level", "", "INFO", "Log level DEBUG|INFO|NOTICE|ERROR") modifyWindow = DurationP("modify-window", "", time.Nanosecond, "Max time diff to be considered the same") checkers = IntP("checkers", "", 8, "Number of checkers to run in parallel.") transfers = IntP("transfers", "", 4, "Number of file transfers to run in parallel.") @@ -182,8 +184,7 @@ func MustReveal(x string) string { // ConfigInfo is filesystem config options type ConfigInfo struct { - Verbose bool - Quiet bool + LogLevel LogLevel DryRun bool CheckSum bool SizeOnly bool @@ -300,8 +301,39 @@ func LoadConfig() { // Read some flags if set // // FIXME read these from the config file too - Config.Verbose = *verbose - Config.Quiet = *quiet + Config.LogLevel = LogLevelNotice + if *verbose >= 2 { + Config.LogLevel = LogLevelDebug + } else if *verbose >= 1 { + Config.LogLevel = LogLevelInfo + } + if *quiet { + if *verbose > 0 { + log.Fatalf("Can't set -v and -q") + } + Config.LogLevel = LogLevelError + } + logLevelFlag := pflag.Lookup("log-level") + if logLevelFlag != nil && logLevelFlag.Changed { + if *verbose > 0 { + log.Fatalf("Can't set -v and --log-level") + } + if *quiet { + log.Fatalf("Can't set -q and --log-level") + } + switch strings.ToUpper(*logLevel) { + case "ERROR": + Config.LogLevel = LogLevelError + case "NOTICE": + Config.LogLevel = LogLevelNotice + case "INFO": + Config.LogLevel = LogLevelInfo + case "DEBUG": + Config.LogLevel = LogLevelDebug + default: + log.Fatalf("Unknown --log-level %q", *logLevel) + } + } Config.ModifyWindow = *modifyWindow Config.Checkers = *checkers Config.Transfers = *transfers diff --git a/fs/flags.go b/fs/flags.go index b530b0615..ab8635e05 100644 --- a/fs/flags.go +++ b/fs/flags.go @@ -313,3 +313,12 @@ func StringArrayP(name, shorthand string, value []string, usage string) (out *[] setDefaultFromEnv(name) return out } + +// CountP defines a flag which can be overridden by an environment variable +// +// It is a thin wrapper around pflag.CountP +func CountP(name, shorthand string, usage string) (out *int) { + out = pflag.CountP(name, shorthand, usage) + setDefaultFromEnv(name) + return out +} diff --git a/fs/log.go b/fs/log.go index ffd5286f5..539ba84da 100644 --- a/fs/log.go +++ b/fs/log.go @@ -11,9 +11,11 @@ import ( // LogLevel describes rclone's logs. These are a subset of the syslog log levels. type LogLevel byte +//go:generate stringer -type=LogLevel + // Log levels - a subset of the syslog logs const ( - LogLevelEmergency = iota + LogLevelEmergency LogLevel = iota LogLevelAlert LogLevelCritical LogLevelError // Error - can't be suppressed @@ -38,30 +40,37 @@ func makeLog(o interface{}, text string, args ...interface{}) string { } // Errorf writes error log output for this Object or Fs. It -// unconditionally logs a message regardless of Config.Quiet or -// Config.Verbose. +// unconditionally logs a message regardless of Config.LogLevel func Errorf(o interface{}, text string, args ...interface{}) { - log.Print(makeLog(o, text, args...)) -} - -// Logf writes log output for this Object or Fs. This should be -// considered to be Info level logging. -func Logf(o interface{}, text string, args ...interface{}) { - if !Config.Quiet { + if Config.LogLevel >= LogLevelError { log.Print(makeLog(o, text, args...)) } } -// Infof writes info on transfers for this Object or Fs +// Logf writes log output for this Object or Fs. This should be +// considered to be Info level logging. It is the default level. By +// default rclone should not log very much so only use this for +// important things the user should see. The user can filter these +// out with the -q flag. +func Logf(o interface{}, text string, args ...interface{}) { + if Config.LogLevel >= LogLevelNotice { + log.Print(makeLog(o, text, args...)) + } +} + +// Infof writes info on transfers for this Object or Fs. Use this +// level for logging transfers, deletions and things which should +// appear with the -v flag. func Infof(o interface{}, text string, args ...interface{}) { - if Config.Verbose { + if Config.LogLevel >= LogLevelInfo { DebugLogger.Print(makeLog(o, text, args...)) } } -// Debugf writes debugging output for this Object or Fs +// Debugf writes debugging output for this Object or Fs. Use this for +// debug only. The user must have to specify -vv to see this. func Debugf(o interface{}, text string, args ...interface{}) { - if Config.Verbose { + if Config.LogLevel >= LogLevelDebug { DebugLogger.Print(makeLog(o, text, args...)) } } diff --git a/fs/loglevel_string.go b/fs/loglevel_string.go new file mode 100644 index 000000000..b6048146e --- /dev/null +++ b/fs/loglevel_string.go @@ -0,0 +1,16 @@ +// Code generated by "stringer -type=LogLevel"; DO NOT EDIT + +package fs + +import "fmt" + +const _LogLevel_name = "LogLevelEmergencyLogLevelAlertLogLevelCriticalLogLevelErrorLogLevelWarningLogLevelNoticeLogLevelInfoLogLevelDebug" + +var _LogLevel_index = [...]uint8{0, 17, 30, 46, 59, 74, 88, 100, 113} + +func (i LogLevel) String() string { + if i >= LogLevel(len(_LogLevel_index)-1) { + return fmt.Sprintf("LogLevel(%d)", i) + } + return _LogLevel_name[_LogLevel_index[i]:_LogLevel_index[i+1]] +} diff --git a/fs/operations_test.go b/fs/operations_test.go index e5842801f..df98843e7 100644 --- a/fs/operations_test.go +++ b/fs/operations_test.go @@ -105,8 +105,9 @@ func newRun() *Run { // "RCLONE_CONFIG_PASS=hunter2" (or your password) *fs.AskPassword = false fs.LoadConfig() - fs.Config.Verbose = *Verbose - fs.Config.Quiet = !*Verbose + if *Verbose { + fs.Config.LogLevel = fs.LogLevelDebug + } fs.Config.DumpHeaders = *DumpHeaders fs.Config.DumpBodies = *DumpBodies fs.Config.LowLevelRetries = *LowLevelRetries diff --git a/fstest/fstests/fstests.go b/fstest/fstests/fstests.go index 4c584df1e..c8a64a6ec 100644 --- a/fstest/fstests/fstests.go +++ b/fstest/fstests/fstests.go @@ -73,8 +73,9 @@ func TestInit(t *testing.T) { for _, item := range ExtraConfig { fs.ConfigFileSet(item.Name, item.Key, item.Value) } - fs.Config.Verbose = *verbose - fs.Config.Quiet = !*verbose + if *verbose { + fs.Config.LogLevel = fs.LogLevelDebug + } fs.Config.DumpHeaders = *dumpHeaders fs.Config.DumpBodies = *dumpBodies t.Logf("Using remote %q", RemoteName)