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

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