package log import ( "fmt" "runtime" "strings" "github.com/rclone/rclone/fs" "github.com/sirupsen/logrus" ) var loggerInstalled = false // InstallJSONLogger installs the JSON logger at the specified log level func InstallJSONLogger(logLevel fs.LogLevel) { if !loggerInstalled { logrus.AddHook(NewCallerHook()) loggerInstalled = true } logrus.SetFormatter(&logrus.JSONFormatter{ TimestampFormat: "2006-01-02T15:04:05.999999-07:00", }) logrus.SetLevel(logrus.DebugLevel) switch logLevel { case fs.LogLevelEmergency, fs.LogLevelAlert: logrus.SetLevel(logrus.PanicLevel) case fs.LogLevelCritical: logrus.SetLevel(logrus.FatalLevel) case fs.LogLevelError: logrus.SetLevel(logrus.ErrorLevel) case fs.LogLevelWarning, fs.LogLevelNotice: logrus.SetLevel(logrus.WarnLevel) case fs.LogLevelInfo: logrus.SetLevel(logrus.InfoLevel) case fs.LogLevelDebug: logrus.SetLevel(logrus.DebugLevel) } } // install hook in fs to call to avoid circular dependency func init() { fs.InstallJSONLogger = InstallJSONLogger } // CallerHook for log the calling file and line of the fine type CallerHook struct { Field string Skip int levels []logrus.Level } // NewCallerHook use to make a hook func NewCallerHook(levels ...logrus.Level) logrus.Hook { hook := CallerHook{ Field: "source", Skip: 7, levels: levels, } if len(hook.levels) == 0 { hook.levels = logrus.AllLevels } return &hook } // Levels implement applied hook to which levels func (h *CallerHook) Levels() []logrus.Level { return logrus.AllLevels } // Fire logs the information of context (filename and line) func (h *CallerHook) Fire(entry *logrus.Entry) error { entry.Data[h.Field] = findCaller(h.Skip) return nil } // findCaller ignores the caller relevant to logrus or fslog then find out the exact caller func findCaller(skip int) string { file := "" line := 0 for i := 0; i < 10; i++ { file, line = getCaller(skip + i) if !strings.HasPrefix(file, "logrus") && !strings.Contains(file, "log.go") { break } } return fmt.Sprintf("%s:%d", file, line) } func getCaller(skip int) (string, int) { _, file, line, ok := runtime.Caller(skip) // fmt.Println(file,":",line) if !ok { return "", 0 } n := 0 for i := len(file) - 1; i > 0; i-- { if file[i] == '/' { n++ if n >= 2 { file = file[i+1:] break } } } return file, line }