1// Package log provides logging for rclone 2package log 3 4import ( 5 "context" 6 "io" 7 "log" 8 "os" 9 "reflect" 10 "runtime" 11 "strings" 12 13 systemd "github.com/iguanesolutions/go-systemd/v5" 14 "github.com/rclone/rclone/fs" 15 "github.com/sirupsen/logrus" 16) 17 18// Options contains options for controlling the logging 19type Options struct { 20 File string // Log everything to this file 21 Format string // Comma separated list of log format options 22 UseSyslog bool // Use Syslog for logging 23 SyslogFacility string // Facility for syslog, e.g. KERN,USER,... 24 LogSystemdSupport bool // set if using systemd logging 25} 26 27// DefaultOpt is the default values used for Opt 28var DefaultOpt = Options{ 29 Format: "date,time", 30 SyslogFacility: "DAEMON", 31} 32 33// Opt is the options for the logger 34var Opt = DefaultOpt 35 36// fnName returns the name of the calling +2 function 37func fnName() string { 38 pc, _, _, ok := runtime.Caller(2) 39 name := "*Unknown*" 40 if ok { 41 name = runtime.FuncForPC(pc).Name() 42 dot := strings.LastIndex(name, ".") 43 if dot >= 0 { 44 name = name[dot+1:] 45 } 46 } 47 return name 48} 49 50// Trace debugs the entry and exit of the calling function 51// 52// It is designed to be used in a defer statement so it returns a 53// function that logs the exit parameters. 54// 55// Any pointers in the exit function will be dereferenced 56func Trace(o interface{}, format string, a ...interface{}) func(string, ...interface{}) { 57 if fs.GetConfig(context.Background()).LogLevel < fs.LogLevelDebug { 58 return func(format string, a ...interface{}) {} 59 } 60 name := fnName() 61 fs.LogPrintf(fs.LogLevelDebug, o, name+": "+format, a...) 62 return func(format string, a ...interface{}) { 63 for i := range a { 64 // read the values of the pointed to items 65 typ := reflect.TypeOf(a[i]) 66 if typ.Kind() == reflect.Ptr { 67 value := reflect.ValueOf(a[i]) 68 if value.IsNil() { 69 a[i] = nil 70 } else { 71 pointedToValue := reflect.Indirect(value) 72 a[i] = pointedToValue.Interface() 73 } 74 } 75 } 76 fs.LogPrintf(fs.LogLevelDebug, o, ">"+name+": "+format, a...) 77 } 78} 79 80// Stack logs a stack trace of callers with the o and info passed in 81func Stack(o interface{}, info string) { 82 if fs.GetConfig(context.Background()).LogLevel < fs.LogLevelDebug { 83 return 84 } 85 arr := [16 * 1024]byte{} 86 buf := arr[:] 87 n := runtime.Stack(buf, false) 88 buf = buf[:n] 89 fs.LogPrintf(fs.LogLevelDebug, o, "%s\nStack trace:\n%s", info, buf) 90} 91 92// InitLogging start the logging as per the command line flags 93func InitLogging() { 94 flagsStr := "," + Opt.Format + "," 95 var flags int 96 if strings.Contains(flagsStr, ",date,") { 97 flags |= log.Ldate 98 } 99 if strings.Contains(flagsStr, ",time,") { 100 flags |= log.Ltime 101 } 102 if strings.Contains(flagsStr, ",microseconds,") { 103 flags |= log.Lmicroseconds 104 } 105 if strings.Contains(flagsStr, ",UTC,") { 106 flags |= log.LUTC 107 } 108 if strings.Contains(flagsStr, ",longfile,") { 109 flags |= log.Llongfile 110 } 111 if strings.Contains(flagsStr, ",shortfile,") { 112 flags |= log.Lshortfile 113 } 114 log.SetFlags(flags) 115 116 fs.LogPrintPid = strings.Contains(flagsStr, ",pid,") 117 118 // Log file output 119 if Opt.File != "" { 120 f, err := os.OpenFile(Opt.File, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0640) 121 if err != nil { 122 log.Fatalf("Failed to open log file: %v", err) 123 } 124 _, err = f.Seek(0, io.SeekEnd) 125 if err != nil { 126 fs.Errorf(nil, "Failed to seek log file to end: %v", err) 127 } 128 log.SetOutput(f) 129 logrus.SetOutput(f) 130 redirectStderr(f) 131 } 132 133 // Syslog output 134 if Opt.UseSyslog { 135 if Opt.File != "" { 136 log.Fatalf("Can't use --syslog and --log-file together") 137 } 138 startSysLog() 139 } 140 141 // Activate systemd logger support if systemd invocation ID is 142 // detected and output is going to stderr (not logging to a file or syslog) 143 if !Redirected() { 144 if _, usingSystemd := systemd.GetInvocationID(); usingSystemd { 145 Opt.LogSystemdSupport = true 146 } 147 } 148 149 // Systemd logging output 150 if Opt.LogSystemdSupport { 151 startSystemdLog() 152 } 153} 154 155// Redirected returns true if the log has been redirected from stdout 156func Redirected() bool { 157 return Opt.UseSyslog || Opt.File != "" 158} 159