1package log
2
3import (
4	"io"
5	"log"
6	"os"
7	"os/signal"
8	"syscall"
9
10	"github.com/client9/reopen"
11	"github.com/sirupsen/logrus"
12)
13
14type nopCloser struct{}
15
16func (nopCloser) Close() error { return nil }
17
18// Initialize will configure the logger based on the options passed. It will
19// validate the options and if validation fails drop to the defaults while
20// logging a message to STDERR.
21func Initialize(opts ...LoggerOption) (io.Closer, error) {
22	conf := applyLoggerOptions(opts)
23
24	// Being unable to open the output file will cause an error
25	writer, closer, err := getOutputWriter(conf)
26	if err != nil {
27		return closer, err
28	}
29
30	conf.logger.SetFormatter(conf.buildFormatter())
31	conf.logger.SetLevel(conf.level)
32	conf.logger.SetOutput(writer)
33
34	// Only output the warnings _after_ the logger has been configured
35	for _, warning := range conf.warnings {
36		conf.logger.Warn(warning)
37	}
38
39	return closer, nil
40}
41
42func getOutputWriter(conf *loggerConfig) (io.Writer, io.Closer, error) {
43	if conf.writer != nil {
44		return conf.writer, nopCloser{}, nil
45	}
46
47	// When writing to a file, use `reopen` so that we can
48	// reopen the file on SIGHUP signals
49	f, err := reopen.NewFileWriterMode(conf.outputPath, 0644)
50	if err != nil {
51		return f, nopCloser{}, err
52	}
53
54	isMainLogger := conf.logger == logger
55
56	sighup := make(chan os.Signal, 1)
57	signal.Notify(sighup, syscall.SIGHUP)
58	go listenForSignalHangup(f, isMainLogger, conf.outputPath, sighup)
59
60	return f, f, nil
61}
62
63// Will listen for SIGHUP signals and reopen the underlying file.
64func listenForSignalHangup(l reopen.Reopener, isMainLogger bool, logFilePath string, sighup chan os.Signal) {
65	for v := range sighup {
66		// Specifically, do _not_ write to the log that is being reopened,
67		// but log this to the _main_ log file instead as the actual log
68		// might be specialised, eg: an access logger leading to an incorrect entry
69		logger.WithFields(logrus.Fields{"signal": v, "path": logFilePath}).Print("Reopening log file on signal")
70
71		err := l.Reopen()
72		if err != nil {
73			if isMainLogger {
74				// Main logger failed to reopen, last ditch effort to notify the user, but don't
75				// do this for auxiliary loggers, since we may get double-logs
76				log.Printf("Unable to reopen log file '%s' after %v. Error %v", logFilePath, v, err)
77			} else {
78				logger.WithError(err).WithFields(logrus.Fields{"signal": v, "path": logFilePath}).Print("Failed to reopen log file")
79			}
80		}
81	}
82}
83