github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/log.go (about)

     1  package fs
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"log"
     8  	"os"
     9  
    10  	"github.com/sirupsen/logrus"
    11  )
    12  
    13  // LogLevel describes rclone's logs.  These are a subset of the syslog log levels.
    14  type LogLevel = Enum[logLevelChoices]
    15  
    16  // Log levels.  These are the syslog levels of which we only use a
    17  // subset.
    18  //
    19  //	LOG_EMERG      system is unusable
    20  //	LOG_ALERT      action must be taken immediately
    21  //	LOG_CRIT       critical conditions
    22  //	LOG_ERR        error conditions
    23  //	LOG_WARNING    warning conditions
    24  //	LOG_NOTICE     normal, but significant, condition
    25  //	LOG_INFO       informational message
    26  //	LOG_DEBUG      debug-level message
    27  const (
    28  	LogLevelEmergency LogLevel = iota
    29  	LogLevelAlert
    30  	LogLevelCritical
    31  	LogLevelError // Error - can't be suppressed
    32  	LogLevelWarning
    33  	LogLevelNotice // Normal logging, -q suppresses
    34  	LogLevelInfo   // Transfers, needs -v
    35  	LogLevelDebug  // Debug level, needs -vv
    36  )
    37  
    38  type logLevelChoices struct{}
    39  
    40  func (logLevelChoices) Choices() []string {
    41  	return []string{
    42  		LogLevelEmergency: "EMERGENCY",
    43  		LogLevelAlert:     "ALERT",
    44  		LogLevelCritical:  "CRITICAL",
    45  		LogLevelError:     "ERROR",
    46  		LogLevelWarning:   "WARNING",
    47  		LogLevelNotice:    "NOTICE",
    48  		LogLevelInfo:      "INFO",
    49  		LogLevelDebug:     "DEBUG",
    50  	}
    51  }
    52  
    53  func (logLevelChoices) Type() string {
    54  	return "LogLevel"
    55  }
    56  
    57  // LogPrintPid enables process pid in log
    58  var LogPrintPid = false
    59  
    60  // LogPrint sends the text to the logger of level
    61  var LogPrint = func(level LogLevel, text string) {
    62  	text = fmt.Sprintf("%-6s: %s", level, text)
    63  	if LogPrintPid {
    64  		text = fmt.Sprintf("[%d] %s", os.Getpid(), text)
    65  	}
    66  	_ = log.Output(4, text)
    67  }
    68  
    69  // LogValueItem describes keyed item for a JSON log entry
    70  type LogValueItem struct {
    71  	key    string
    72  	value  interface{}
    73  	render bool
    74  }
    75  
    76  // LogValue should be used as an argument to any logging calls to
    77  // augment the JSON output with more structured information.
    78  //
    79  // key is the dictionary parameter used to store value.
    80  func LogValue(key string, value interface{}) LogValueItem {
    81  	return LogValueItem{key: key, value: value, render: true}
    82  }
    83  
    84  // LogValueHide should be used as an argument to any logging calls to
    85  // augment the JSON output with more structured information.
    86  //
    87  // key is the dictionary parameter used to store value.
    88  //
    89  // String() will return a blank string - this is useful to put items
    90  // in which don't print into the log.
    91  func LogValueHide(key string, value interface{}) LogValueItem {
    92  	return LogValueItem{key: key, value: value, render: false}
    93  }
    94  
    95  // String returns the representation of value. If render is fals this
    96  // is an empty string so LogValueItem entries won't show in the
    97  // textual representation of logs.
    98  func (j LogValueItem) String() string {
    99  	if !j.render {
   100  		return ""
   101  	}
   102  	if do, ok := j.value.(fmt.Stringer); ok {
   103  		return do.String()
   104  	}
   105  	return fmt.Sprint(j.value)
   106  }
   107  
   108  // LogPrintf produces a log string from the arguments passed in
   109  func LogPrintf(level LogLevel, o interface{}, text string, args ...interface{}) {
   110  	out := fmt.Sprintf(text, args...)
   111  
   112  	if GetConfig(context.TODO()).UseJSONLog {
   113  		fields := logrus.Fields{}
   114  		if o != nil {
   115  			fields = logrus.Fields{
   116  				"object":     fmt.Sprintf("%+v", o),
   117  				"objectType": fmt.Sprintf("%T", o),
   118  			}
   119  		}
   120  		for _, arg := range args {
   121  			if item, ok := arg.(LogValueItem); ok {
   122  				fields[item.key] = item.value
   123  			}
   124  		}
   125  		switch level {
   126  		case LogLevelDebug:
   127  			logrus.WithFields(fields).Debug(out)
   128  		case LogLevelInfo:
   129  			logrus.WithFields(fields).Info(out)
   130  		case LogLevelNotice, LogLevelWarning:
   131  			logrus.WithFields(fields).Warn(out)
   132  		case LogLevelError:
   133  			logrus.WithFields(fields).Error(out)
   134  		case LogLevelCritical:
   135  			logrus.WithFields(fields).Fatal(out)
   136  		case LogLevelEmergency, LogLevelAlert:
   137  			logrus.WithFields(fields).Panic(out)
   138  		}
   139  	} else {
   140  		if o != nil {
   141  			out = fmt.Sprintf("%v: %s", o, out)
   142  		}
   143  		LogPrint(level, out)
   144  	}
   145  }
   146  
   147  // LogLevelPrintf writes logs at the given level
   148  func LogLevelPrintf(level LogLevel, o interface{}, text string, args ...interface{}) {
   149  	if GetConfig(context.TODO()).LogLevel >= level {
   150  		LogPrintf(level, o, text, args...)
   151  	}
   152  }
   153  
   154  // Errorf writes error log output for this Object or Fs.  It
   155  // should always be seen by the user.
   156  func Errorf(o interface{}, text string, args ...interface{}) {
   157  	if GetConfig(context.TODO()).LogLevel >= LogLevelError {
   158  		LogPrintf(LogLevelError, o, text, args...)
   159  	}
   160  }
   161  
   162  // Logf writes log output for this Object or Fs.  This should be
   163  // considered to be Notice level logging.  It is the default level.
   164  // By default rclone should not log very much so only use this for
   165  // important things the user should see.  The user can filter these
   166  // out with the -q flag.
   167  func Logf(o interface{}, text string, args ...interface{}) {
   168  	if GetConfig(context.TODO()).LogLevel >= LogLevelNotice {
   169  		LogPrintf(LogLevelNotice, o, text, args...)
   170  	}
   171  }
   172  
   173  // Infof writes info on transfers for this Object or Fs.  Use this
   174  // level for logging transfers, deletions and things which should
   175  // appear with the -v flag.
   176  func Infof(o interface{}, text string, args ...interface{}) {
   177  	if GetConfig(context.TODO()).LogLevel >= LogLevelInfo {
   178  		LogPrintf(LogLevelInfo, o, text, args...)
   179  	}
   180  }
   181  
   182  // Debugf writes debugging output for this Object or Fs.  Use this for
   183  // debug only.  The user must have to specify -vv to see this.
   184  func Debugf(o interface{}, text string, args ...interface{}) {
   185  	if GetConfig(context.TODO()).LogLevel >= LogLevelDebug {
   186  		LogPrintf(LogLevelDebug, o, text, args...)
   187  	}
   188  }
   189  
   190  // LogDirName returns an object for the logger, logging a root
   191  // directory which would normally be "" as the Fs
   192  func LogDirName(f Fs, dir string) interface{} {
   193  	if dir != "" {
   194  		return dir
   195  	}
   196  	return f
   197  }
   198  
   199  // PrettyPrint formats JSON for improved readability in debug logs.
   200  // If it can't Marshal JSON, it falls back to fmt.
   201  func PrettyPrint(in any, label string, level LogLevel) {
   202  	if GetConfig(context.TODO()).LogLevel < level {
   203  		return
   204  	}
   205  	inBytes, err := json.MarshalIndent(in, "", "\t")
   206  	if err != nil || string(inBytes) == "{}" || string(inBytes) == "[]" {
   207  		LogPrintf(level, label, "\n%+v\n", in)
   208  		return
   209  	}
   210  	LogPrintf(level, label, "\n%s\n", string(inBytes))
   211  }