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