diff --git a/build/logrotator.go b/build/logrotator.go index aa4683bba..e517a0d03 100644 --- a/build/logrotator.go +++ b/build/logrotator.go @@ -6,9 +6,7 @@ import ( "io" "os" "path/filepath" - "sort" - "github.com/btcsuite/btclog" "github.com/jrick/logrotate/rotator" "github.com/klauspost/compress/zstd" ) @@ -18,43 +16,15 @@ import ( type RotatingLogWriter struct { logWriter *LogWriter - backendLog *btclog.Backend - - logRotator *rotator.Rotator - - subsystemLoggers SubLoggers + rotator *rotator.Rotator } -// 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{ - logWriter: logWriter, - backendLog: backendLog, - subsystemLoggers: SubLoggers{}, - } -} - -// GenSubLogger creates a new sublogger. A shutdown callback function -// is provided to be able to shutdown in case of a critical error. -func (r *RotatingLogWriter) GenSubLogger(tag string, shutdown func()) btclog.Logger { - logger := r.backendLog.Logger(tag) - return NewShutdownLogger(logger, shutdown) -} - -// RegisterSubLogger registers a new subsystem logger. -func (r *RotatingLogWriter) RegisterSubLogger(subsystem string, - logger btclog.Logger) { - - r.subsystemLoggers[subsystem] = logger +func NewRotatingLogWriter(w *LogWriter) *RotatingLogWriter { + return &RotatingLogWriter{logWriter: w} } // InitLogRotator initializes the log file rotator to write logs to logFile and @@ -68,7 +38,8 @@ func (r *RotatingLogWriter) InitLogRotator(logFile, logCompressor string, if err != nil { return fmt.Errorf("failed to create log directory: %w", err) } - r.logRotator, err = rotator.New( + + r.rotator, err = rotator.New( logFile, int64(maxLogFileSize*1024), false, maxLogFiles, ) if err != nil { @@ -94,7 +65,7 @@ func (r *RotatingLogWriter) InitLogRotator(logFile, logCompressor string, } // Apply the compressor and its file suffix to the log rotator. - r.logRotator.SetCompressor(c, logCompressors[logCompressor]) + r.rotator.SetCompressor(c, logCompressors[logCompressor]) // Run rotator as a goroutine now but make sure we catch any errors // that happen in case something with the rotation goes wrong during @@ -102,7 +73,7 @@ func (r *RotatingLogWriter) InitLogRotator(logFile, logCompressor string, // create a new logfile for whatever reason). pr, pw := io.Pipe() go func() { - err := r.logRotator.Run(pr) + err := r.rotator.Run(pr) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "failed to run file rotator: %v\n", err) @@ -110,67 +81,15 @@ func (r *RotatingLogWriter) InitLogRotator(logFile, logCompressor string, }() 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() + if r.rotator != nil { + return r.rotator.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/build/sub_logger.go b/build/sub_logger.go index 76c4d4f9f..c1d28fe60 100644 --- a/build/sub_logger.go +++ b/build/sub_logger.go @@ -2,11 +2,149 @@ package build import ( "fmt" + "io" + "sort" "strings" + "sync" "github.com/btcsuite/btclog" ) +// SubLogCreator can be used to create a new logger for a particular subsystem. +type SubLogCreator interface { + // Logger returns a new logger for a particular subsytem. + Logger(subsystemTag string) btclog.Logger +} + +// SubLoggerManager manages a set of subsystem loggers. Level updates will be +// applied to all the loggers managed by the manager. +type SubLoggerManager struct { + genLogger SubLogCreator + + loggers SubLoggers + mu sync.Mutex +} + +// A compile time check to ensure SubLoggerManager implements the +// LeveledSubLogger interface. +var _ LeveledSubLogger = (*SubLoggerManager)(nil) + +// NewSubLoggerManager constructs a new SubLoggerManager. +func NewSubLoggerManager(w io.Writer) *SubLoggerManager { + return &SubLoggerManager{ + loggers: SubLoggers{}, + genLogger: btclog.NewBackend(w), + } +} + +// GenSubLogger creates a new sub-logger and adds it to the set managed by the +// SubLoggerManager. A shutdown callback function is provided to be able to shut +// down in case of a critical error. +func (r *SubLoggerManager) GenSubLogger(subsystem string, + shutdown func()) btclog.Logger { + + // Create a new logger with the given subsystem tag. + logger := r.genLogger.Logger(subsystem) + + // Wrap the new logger in a Shutdown logger so that the shutdown + // call back is called if a critical log is ever written via this new + // logger. + l := NewShutdownLogger(logger, shutdown) + + r.RegisterSubLogger(subsystem, l) + + return l +} + +// RegisterSubLogger registers the given logger under the given subsystem name. +func (r *SubLoggerManager) RegisterSubLogger(subsystem string, + logger btclog.Logger) { + + // Add the new logger to the set of loggers managed by the manager. + r.mu.Lock() + r.loggers[subsystem] = logger + r.mu.Unlock() +} + +// SubLoggers returns all currently registered subsystem loggers for this log +// writer. +// +// NOTE: This is part of the LeveledSubLogger interface. +func (r *SubLoggerManager) SubLoggers() SubLoggers { + r.mu.Lock() + defer r.mu.Unlock() + + return r.loggers +} + +// 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 *SubLoggerManager) SupportedSubsystems() []string { + r.mu.Lock() + defer r.mu.Unlock() + + // Convert the subsystemLoggers map keys to a string slice. + subsystems := make([]string, 0, len(r.loggers)) + for subsysID := range r.loggers { + 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 *SubLoggerManager) SetLogLevel(subsystemID string, logLevel string) { + r.mu.Lock() + defer r.mu.Unlock() + + r.setLogLevelUnsafe(subsystemID, logLevel) +} + +// setLogLevelUnsafe sets the logging level for provided subsystem. Invalid +// subsystems are ignored. Uninitialized subsystems are dynamically created as +// needed. +// +// NOTE: the SubLoggerManager mutex must be held before calling this method. +func (r *SubLoggerManager) setLogLevelUnsafe(subsystemID string, + logLevel string) { + + // Ignore invalid subsystems. + logger, ok := r.loggers[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 *SubLoggerManager) SetLogLevels(logLevel string) { + r.mu.Lock() + defer r.mu.Unlock() + + // Configure all sub-systems with the new logging level. Dynamically + // create loggers as needed. + for subsystemID := range r.loggers { + r.setLogLevelUnsafe(subsystemID, logLevel) + } +} + // SubLoggers is a type that holds a map of subsystem loggers keyed by their // subsystem name. type SubLoggers map[string]btclog.Logger diff --git a/config.go b/config.go index 2ad83c126..6cfd4941f 100644 --- a/config.go +++ b/config.go @@ -494,9 +494,11 @@ type Config struct { GRPC *GRPCConfig `group:"grpc" namespace:"grpc"` - // LogWriter is the root logger that all of the daemon's subloggers are + // SubLogMgr is the root logger that all the daemon's subloggers are // hooked up to. - LogWriter *build.RotatingLogWriter + SubLogMgr *build.SubLoggerManager + LogWriter *build.LogWriter + LogRotator *build.RotatingLogWriter // networkDir is the path to the directory of the currently active // network. This path will hold the files related to each different @@ -714,7 +716,7 @@ func DefaultConfig() Config { MaxChannelFeeAllocation: htlcswitch.DefaultMaxLinkFeeAllocation, MaxCommitFeeRateAnchors: lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte, MaxFeeExposure: uint64(htlcswitch.DefaultMaxFeeExposure.ToSatoshis()), - LogWriter: build.NewRotatingLogWriter(), + LogWriter: &build.LogWriter{}, DB: lncfg.DefaultDB(), Cluster: lncfg.DefaultCluster(), RPCMiddleware: lncfg.DefaultRPCMiddleware(), @@ -1411,16 +1413,19 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser, cfg.LogCompressor) } + cfg.LogRotator = build.NewRotatingLogWriter(cfg.LogWriter) + cfg.SubLogMgr = build.NewSubLoggerManager(cfg.LogWriter) + // Initialize logging at the default logging level. - SetupLoggers(cfg.LogWriter, interceptor) + SetupLoggers(cfg.SubLogMgr, interceptor) // Special show command to list supported subsystems and exit. if cfg.DebugLevel == "show" { fmt.Println("Supported subsystems", - cfg.LogWriter.SupportedSubsystems()) + cfg.SubLogMgr.SupportedSubsystems()) os.Exit(0) } - err = cfg.LogWriter.InitLogRotator( + err = cfg.LogRotator.InitLogRotator( filepath.Join(cfg.LogDir, defaultLogFilename), cfg.LogCompressor, cfg.MaxLogFileSize, cfg.MaxLogFiles, ) @@ -1430,7 +1435,7 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser, } // Parse, validate, and set debug log level(s). - err = build.ParseAndSetDebugLevels(cfg.DebugLevel, cfg.LogWriter) + err = build.ParseAndSetDebugLevels(cfg.DebugLevel, cfg.SubLogMgr) if err != nil { str := "error parsing debug level: %v" return nil, &lncfg.UsageError{Err: mkErr(str, err)} diff --git a/lnd.go b/lnd.go index 8fb38b8f3..f51181195 100644 --- a/lnd.go +++ b/lnd.go @@ -149,7 +149,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, defer func() { ltndLog.Info("Shutdown complete\n") - err := cfg.LogWriter.Close() + err := cfg.LogRotator.Close() if err != nil { ltndLog.Errorf("Could not close log rotator: %v", err) } diff --git a/log.go b/log.go index a24c39fd4..748880447 100644 --- a/log.go +++ b/log.go @@ -95,7 +95,7 @@ var ( // genSubLogger creates a logger for a subsystem. We provide an instance of // a signal.Interceptor to be able to shutdown in the case of a critical error. -func genSubLogger(root *build.RotatingLogWriter, +func genSubLogger(root *build.SubLoggerManager, interceptor signal.Interceptor) func(string) btclog.Logger { // Create a shutdown function which will request shutdown from our @@ -116,7 +116,7 @@ func genSubLogger(root *build.RotatingLogWriter, } // SetupLoggers initializes all package-global logger variables. -func SetupLoggers(root *build.RotatingLogWriter, interceptor signal.Interceptor) { +func SetupLoggers(root *build.SubLoggerManager, interceptor signal.Interceptor) { genLogger := genSubLogger(root, interceptor) // Now that we have the proper root logger, we can replace the @@ -193,7 +193,7 @@ func SetupLoggers(root *build.RotatingLogWriter, interceptor signal.Interceptor) // AddSubLogger is a helper method to conveniently create and register the // logger of one or more sub systems. -func AddSubLogger(root *build.RotatingLogWriter, subsystem string, +func AddSubLogger(root *build.SubLoggerManager, subsystem string, interceptor signal.Interceptor, useLoggers ...func(btclog.Logger)) { // genSubLogger will return a callback for creating a logger instance, @@ -208,7 +208,7 @@ func AddSubLogger(root *build.RotatingLogWriter, subsystem string, // SetSubLogger is a helper method to conveniently register the logger of a sub // system. -func SetSubLogger(root *build.RotatingLogWriter, subsystem string, +func SetSubLogger(root *build.SubLoggerManager, subsystem string, logger btclog.Logger, useLoggers ...func(btclog.Logger)) { root.RegisterSubLogger(subsystem, logger) diff --git a/rpcserver.go b/rpcserver.go index 6f43adf7f..76aa057bf 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -7341,7 +7341,7 @@ func (r *rpcServer) DebugLevel(ctx context.Context, if req.Show { return &lnrpc.DebugLevelResponse{ SubSystems: strings.Join( - r.cfg.LogWriter.SupportedSubsystems(), " ", + r.cfg.SubLogMgr.SupportedSubsystems(), " ", ), }, nil } @@ -7350,12 +7350,12 @@ func (r *rpcServer) DebugLevel(ctx context.Context, // Otherwise, we'll attempt to set the logging level using the // specified level spec. - err := build.ParseAndSetDebugLevels(req.LevelSpec, r.cfg.LogWriter) + err := build.ParseAndSetDebugLevels(req.LevelSpec, r.cfg.SubLogMgr) if err != nil { return nil, err } - subLoggers := r.cfg.LogWriter.SubLoggers() + subLoggers := r.cfg.SubLogMgr.SubLoggers() // Sort alphabetically by subsystem name. var tags []string for t := range subLoggers {