diff --git a/cmd/cmd.go b/cmd/cmd.go index 225e71cb9..4a232e96e 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -31,7 +31,6 @@ var ( statsInterval = fs.DurationP("stats", "", time.Minute*1, "Interval between printing stats, e.g 500ms, 60s, 5m. (0 to disable)") dataRateUnit = fs.StringP("stats-unit", "", "bytes", "Show data rate in stats as either 'bits' or 'bytes'/s") version bool - logFile = fs.StringP("log-file", "", "", "Log everything to this file") retries = fs.IntP("retries", "", 3, "Retry operations this many times if they fail") ) @@ -320,20 +319,8 @@ func StartStats() chan struct{} { // initConfig is run by cobra after initialising the flags func initConfig() { - // Log file output - if *logFile != "" { - f, err := os.OpenFile(*logFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0640) - if err != nil { - log.Fatalf("Failed to open log file: %v", err) - } - _, err = f.Seek(0, os.SEEK_END) - if err != nil { - fs.Errorf(nil, "Failed to seek log file to end: %v", err) - } - log.SetOutput(f) - fs.DebugLogger.SetOutput(f) - redirectStderr(f) - } + // Start the logger + fs.InitLogging() // Load the rest of the config now we have started the logger fs.LoadConfig() diff --git a/docs/content/docs.md b/docs/content/docs.md index 92e650d7d..1affb0a9a 100644 --- a/docs/content/docs.md +++ b/docs/content/docs.md @@ -502,6 +502,18 @@ then the files will have SUFFIX added on to them. See `--backup-dir` for more info. +### --syslog ### + +On capable OSes (not Windows or Plan9) send all log output to syslog. + +This can be useful for running rclone in script or `rclone mount`. + +### -syslog-facility string ### + +If using `--syslog` this sets the syslog facility (eg `KERN`, `USER`). +See `man syslog` for a list of possible facilities. The default +facility is `DAEMON`. + ### --track-renames ### By default rclone doesn't not keep track of renamed files, so if you diff --git a/fs/flags.go b/fs/flags.go index ab8635e05..8b221ba34 100644 --- a/fs/flags.go +++ b/fs/flags.go @@ -254,7 +254,7 @@ func setDefaultFromEnv(name string) { if err != nil { log.Fatalf("Invalid value for environment variable %q: %v", key, err) } - // log.Printf("Set default for %q from %q to %q (%v)", name, key, newValue, flag.Value) + Debugf(nil, "Set default for %q from %q to %q (%v)", name, key, newValue, flag.Value) flag.DefValue = newValue } } diff --git a/fs/log.go b/fs/log.go index 539ba84da..36fa180b4 100644 --- a/fs/log.go +++ b/fs/log.go @@ -11,9 +11,17 @@ 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 +// Log levels. These are the syslog levels of which we only use a +// subset. +// +// LOG_EMERG system is unusable +// LOG_ALERT action must be taken immediately +// LOG_CRIT critical conditions +// LOG_ERR error conditions +// LOG_WARNING warning conditions +// LOG_NOTICE normal, but significant, condition +// LOG_INFO informational message +// LOG_DEBUG debug-level message const ( LogLevelEmergency LogLevel = iota LogLevelAlert @@ -25,25 +33,52 @@ const ( LogLevelDebug // Debug level, needs -vv ) -// Outside world interface +var logLevelToString = []string{ + LogLevelEmergency: "EMERGENCY", + LogLevelAlert: "ALERT", + LogLevelCritical: "CRITICAL", + LogLevelError: "ERROR", + LogLevelWarning: "WARNING", + LogLevelNotice: "NOTICE", + LogLevelInfo: "INFO", + LogLevelDebug: "DEBUG", +} -// DebugLogger - logs to Stdout -var DebugLogger = log.New(os.Stdout, "", log.LstdFlags) - -// makeLog produces a log string from the arguments passed in -func makeLog(o interface{}, text string, args ...interface{}) string { - out := fmt.Sprintf(text, args...) - if o == nil { - return out +// String turns a LogLevel into a string +func (l LogLevel) String() string { + if l >= LogLevel(len(logLevelToString)) { + return fmt.Sprintf("LogLevel(%d)", l) } - return fmt.Sprintf("%v: %s", o, out) + return logLevelToString[l] +} + +// Flags +var ( + logFile = StringP("log-file", "", "", "Log everything to this file") + useSyslog = BoolP("syslog", "", false, "Use Syslog for logging") + syslogFacility = StringP("syslog-facility", "", "DAEMON", "Facility for syslog, eg KERN,USER,...") +) + +// logPrint sends the text to the logger of level +var logPrint = func(level LogLevel, text string) { + text = fmt.Sprintf("%-6s: %s", level, text) + log.Print(text) +} + +// logPrintf produces a log string from the arguments passed in +func logPrintf(level LogLevel, o interface{}, text string, args ...interface{}) { + out := fmt.Sprintf(text, args...) + if o != nil { + out = fmt.Sprintf("%v: %s", o, out) + } + logPrint(level, out) } // Errorf writes error log output for this Object or Fs. It -// unconditionally logs a message regardless of Config.LogLevel +// should always be seen by the user. func Errorf(o interface{}, text string, args ...interface{}) { if Config.LogLevel >= LogLevelError { - log.Print(makeLog(o, text, args...)) + logPrintf(LogLevelError, o, text, args...) } } @@ -54,7 +89,7 @@ func Errorf(o interface{}, text string, args ...interface{}) { // out with the -q flag. func Logf(o interface{}, text string, args ...interface{}) { if Config.LogLevel >= LogLevelNotice { - log.Print(makeLog(o, text, args...)) + logPrintf(LogLevelNotice, o, text, args...) } } @@ -63,7 +98,7 @@ func Logf(o interface{}, text string, args ...interface{}) { // appear with the -v flag. func Infof(o interface{}, text string, args ...interface{}) { if Config.LogLevel >= LogLevelInfo { - DebugLogger.Print(makeLog(o, text, args...)) + logPrintf(LogLevelInfo, o, text, args...) } } @@ -71,6 +106,31 @@ func Infof(o interface{}, text string, args ...interface{}) { // debug only. The user must have to specify -vv to see this. func Debugf(o interface{}, text string, args ...interface{}) { if Config.LogLevel >= LogLevelDebug { - DebugLogger.Print(makeLog(o, text, args...)) + logPrintf(LogLevelDebug, o, text, args...) + } +} + +// InitLogging start the logging as per the command line flags +func InitLogging() { + // Log file output + if *logFile != "" { + f, err := os.OpenFile(*logFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0640) + if err != nil { + log.Fatalf("Failed to open log file: %v", err) + } + _, err = f.Seek(0, os.SEEK_END) + if err != nil { + Errorf(nil, "Failed to seek log file to end: %v", err) + } + log.SetOutput(f) + redirectStderr(f) + } + + // Syslog output + if *useSyslog { + if *logFile != "" { + log.Fatalf("Can't use --syslog and --log-file together") + } + startSysLog() } } diff --git a/fs/loglevel_string.go b/fs/loglevel_string.go deleted file mode 100644 index b6048146e..000000000 --- a/fs/loglevel_string.go +++ /dev/null @@ -1,16 +0,0 @@ -// 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/cmd/redirect_stderr.go b/fs/redirect_stderr.go similarity index 67% rename from cmd/redirect_stderr.go rename to fs/redirect_stderr.go index 0e5938421..3a2425c97 100644 --- a/cmd/redirect_stderr.go +++ b/fs/redirect_stderr.go @@ -2,15 +2,11 @@ // +build !windows,!darwin,!dragonfly,!freebsd,!linux,!nacl,!netbsd,!openbsd -package cmd +package fs -import ( - "os" - - "github.com/ncw/rclone/fs" -) +import "os" // redirectStderr to the file passed in func redirectStderr(f *os.File) { - fs.Errorf(nil, "Can't redirect stderr to file") + Errorf(nil, "Can't redirect stderr to file") } diff --git a/cmd/redirect_stderr_unix.go b/fs/redirect_stderr_unix.go similarity index 96% rename from cmd/redirect_stderr_unix.go rename to fs/redirect_stderr_unix.go index 86e83646c..5126f53aa 100644 --- a/cmd/redirect_stderr_unix.go +++ b/fs/redirect_stderr_unix.go @@ -2,7 +2,7 @@ // +build !windows,!solaris,!plan9 -package cmd +package fs import ( "log" diff --git a/cmd/redirect_stderr_windows.go b/fs/redirect_stderr_windows.go similarity index 98% rename from cmd/redirect_stderr_windows.go rename to fs/redirect_stderr_windows.go index 37fc80122..ef8a46359 100644 --- a/cmd/redirect_stderr_windows.go +++ b/fs/redirect_stderr_windows.go @@ -6,7 +6,7 @@ // +build windows -package cmd +package fs import ( "log" diff --git a/fs/syslog.go b/fs/syslog.go new file mode 100644 index 000000000..623ce346d --- /dev/null +++ b/fs/syslog.go @@ -0,0 +1,16 @@ +// Syslog interface for non-Unix variants only + +// +build windows nacl plan9 + +package fs + +import ( + "log" + "runtime" +) + +// Starts syslog if configured, returns true if it was started +func startSysLog() bool { + log.Fatalf("--syslog not supported on %s platform", runtime.GOOS) + return false +} diff --git a/fs/syslog_unix.go b/fs/syslog_unix.go new file mode 100644 index 000000000..13d2825a9 --- /dev/null +++ b/fs/syslog_unix.go @@ -0,0 +1,65 @@ +// Syslog interface for Unix variants only + +// +build !windows,!nacl,!plan9 + +package fs + +import ( + "log" + "log/syslog" + "os" + "path" +) + +var ( + syslogFacilityMap = map[string]syslog.Priority{ + "KERN": syslog.LOG_KERN, + "USER": syslog.LOG_USER, + "MAIL": syslog.LOG_MAIL, + "DAEMON": syslog.LOG_DAEMON, + "AUTH": syslog.LOG_AUTH, + "SYSLOG": syslog.LOG_SYSLOG, + "LPR": syslog.LOG_LPR, + "NEWS": syslog.LOG_NEWS, + "UUCP": syslog.LOG_UUCP, + "CRON": syslog.LOG_CRON, + "AUTHPRIV": syslog.LOG_AUTHPRIV, + "FTP": syslog.LOG_FTP, + } +) + +// Starts syslog +func startSysLog() bool { + facility, ok := syslogFacilityMap[*syslogFacility] + if !ok { + log.Fatalf("Unknown syslog facility %q - man syslog for list", *syslogFacility) + } + Me := path.Base(os.Args[0]) + w, err := syslog.New(syslog.LOG_NOTICE|facility, Me) + if err != nil { + log.Fatalf("Failed to start syslog: %v", err) + } + log.SetFlags(0) + log.SetOutput(w) + logPrint = func(level LogLevel, text string) { + switch level { + case LogLevelEmergency: + _ = w.Emerg(text) + case LogLevelAlert: + _ = w.Alert(text) + case LogLevelCritical: + _ = w.Crit(text) + case LogLevelError: + _ = w.Err(text) + case LogLevelWarning: + _ = w.Warning(text) + case LogLevelNotice: + _ = w.Notice(text) + case LogLevelInfo: + _ = w.Info(text) + case LogLevelDebug: + _ = w.Debug(text) + } + } + return true +}