diff --git a/build/logrotator.go b/build/logrotator.go new file mode 100644 index 00000000..734a3bd7 --- /dev/null +++ b/build/logrotator.go @@ -0,0 +1,151 @@ +package build + +import ( + "fmt" + "io" + "os" + "path/filepath" + "sort" + + "github.com/btcsuite/btclog" + "github.com/jrick/logrotate/rotator" +) + +// RotatingLogWriter is a wrapper around the LogWriter that supports log file +// rotation. +type RotatingLogWriter struct { + // GenSubLogger is a function that returns a new logger for a subsystem + // belonging to the current RotatingLogWriter. + GenSubLogger func(string) btclog.Logger + + logWriter *LogWriter + + backendLog *btclog.Backend + + logRotator *rotator.Rotator + + subsystemLoggers SubLoggers +} + +// A compile time check to ensure RotatingLogWriter implements the +// LeveledSubLogger interface. +var _ LeveledSubLogger = (*RotatingLogWriter)(nil) + +// NewRotatingLogWriter creates a new file rotating log writer. +// +// NOTE: `InitLogRotator` must be called to set up log rotation after creating +// the writer. +func NewRotatingLogWriter() *RotatingLogWriter { + logWriter := &LogWriter{} + backendLog := btclog.NewBackend(logWriter) + return &RotatingLogWriter{ + GenSubLogger: backendLog.Logger, + logWriter: logWriter, + backendLog: backendLog, + subsystemLoggers: SubLoggers{}, + } +} + +// RegisterSubLogger registers a new subsystem logger. +func (r *RotatingLogWriter) RegisterSubLogger(subsystem string, + logger btclog.Logger) { + + r.subsystemLoggers[subsystem] = logger +} + +// InitLogRotator initializes the log file rotator to write logs to logFile and +// create roll files in the same directory. It should be called as early on +// startup and possible and must be closed on shutdown by calling `Close`. +func (r *RotatingLogWriter) InitLogRotator(logFile string, maxLogFileSize int, + maxLogFiles int) error { + + logDir, _ := filepath.Split(logFile) + err := os.MkdirAll(logDir, 0700) + if err != nil { + return fmt.Errorf("failed to create log directory: %v", err) + } + r.logRotator, err = rotator.New( + logFile, int64(maxLogFileSize*1024), false, maxLogFiles, + ) + if err != nil { + return fmt.Errorf("failed to create file rotator: %v", err) + } + + // Run rotator as a goroutine now but make sure we catch any errors + // that happen in case something with the rotation goes wrong during + // runtime (like running out of disk space or not being allowed to + // create a new logfile for whatever reason). + pr, pw := io.Pipe() + go func() { + err := r.logRotator.Run(pr) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, + "failed to run file rotator: %v\n", err) + } + }() + + r.logWriter.RotatorPipe = pw + return nil +} + +// Close closes the underlying log rotator if it has already been created. +func (r *RotatingLogWriter) Close() error { + if r.logRotator != nil { + return r.logRotator.Close() + } + return nil +} + +// SubLoggers returns all currently registered subsystem loggers for this log +// writer. +// +// NOTE: This is part of the LeveledSubLogger interface. +func (r *RotatingLogWriter) SubLoggers() SubLoggers { + return r.subsystemLoggers +} + +// SupportedSubsystems returns a sorted string slice of all keys in the +// subsystems map, corresponding to the names of the subsystems. +// +// NOTE: This is part of the LeveledSubLogger interface. +func (r *RotatingLogWriter) SupportedSubsystems() []string { + // Convert the subsystemLoggers map keys to a string slice. + subsystems := make([]string, 0, len(r.subsystemLoggers)) + for subsysID := range r.subsystemLoggers { + subsystems = append(subsystems, subsysID) + } + + // Sort the subsystems for stable display. + sort.Strings(subsystems) + return subsystems +} + +// SetLogLevel sets the logging level for provided subsystem. Invalid +// subsystems are ignored. Uninitialized subsystems are dynamically created as +// needed. +// +// NOTE: This is part of the LeveledSubLogger interface. +func (r *RotatingLogWriter) SetLogLevel(subsystemID string, logLevel string) { + // Ignore invalid subsystems. + logger, ok := r.subsystemLoggers[subsystemID] + if !ok { + return + } + + // Defaults to info if the log level is invalid. + level, _ := btclog.LevelFromString(logLevel) + logger.SetLevel(level) +} + +// SetLogLevels sets the log level for all subsystem loggers to the passed +// level. It also dynamically creates the subsystem loggers as needed, so it +// can be used to initialize the logging system. +// +// NOTE: This is part of the LeveledSubLogger interface. +func (r *RotatingLogWriter) SetLogLevels(logLevel string) { + // Configure all sub-systems with the new logging level. Dynamically + // create loggers as needed. + for subsystemID := range r.subsystemLoggers { + r.SetLogLevel(subsystemID, logLevel) + } +} diff --git a/config.go b/config.go index c1420d17..88467807 100644 --- a/config.go +++ b/config.go @@ -958,10 +958,11 @@ func loadConfig() (*config, error) { }*/ // Initialize logging at the default logging level. + /*TODO(guggero) fix initLogRotator( filepath.Join(cfg.LogDir, defaultLogFilename), cfg.MaxLogFileSize, cfg.MaxLogFiles, - ) + )*/ // Parse, validate, and set debug log level(s). /*TODO(guggero) fix diff --git a/log.go b/log.go index a59e96ad..2d65e094 100644 --- a/log.go +++ b/log.go @@ -2,10 +2,6 @@ package lnd import ( "context" - "fmt" - "io" - "os" - "path/filepath" "github.com/btcsuite/btcd/connmgr" "github.com/btcsuite/btclog" @@ -173,55 +169,6 @@ var subsystemLoggers = map[string]btclog.Logger{ "PRNF": prnfLog, } -// initLogRotator initializes the logging rotator to write logs to logFile and -// create roll files in the same directory. It must be called before the -// package-global log rotator variables are used. -func initLogRotator(logFile string, MaxLogFileSize int, MaxLogFiles int) { - logDir, _ := filepath.Split(logFile) - err := os.MkdirAll(logDir, 0700) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to create log directory: %v\n", err) - os.Exit(1) - } - r, err := rotator.New(logFile, int64(MaxLogFileSize*1024), false, MaxLogFiles) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to create file rotator: %v\n", err) - os.Exit(1) - } - - pr, pw := io.Pipe() - go r.Run(pr) - - logWriter.RotatorPipe = pw - logRotator = r -} - -// setLogLevel sets the logging level for provided subsystem. Invalid -// subsystems are ignored. Uninitialized subsystems are dynamically created as -// needed. -func setLogLevel(subsystemID string, logLevel string) { - // Ignore invalid subsystems. - logger, ok := subsystemLoggers[subsystemID] - if !ok { - return - } - - // Defaults to info if the log level is invalid. - level, _ := btclog.LevelFromString(logLevel) - logger.SetLevel(level) -} - -// setLogLevels sets the log level for all subsystem loggers to the passed -// level. It also dynamically creates the subsystem loggers as needed, so it -// can be used to initialize the logging system. -func setLogLevels(logLevel string) { - // Configure all sub-systems with the new logging level. Dynamically - // create loggers as needed. - for subsystemID := range subsystemLoggers { - setLogLevel(subsystemID, logLevel) - } -} - // logClosure is used to provide a closure over expensive logging operations so // don't have to be performed when the logging level doesn't warrant it. type logClosure func() string