github.com/decred/dcrlnd@v0.7.6/build/logrotator.go (about)

     1  package build
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  	"sort"
     9  
    10  	"github.com/decred/slog"
    11  	"github.com/jrick/logrotate/rotator"
    12  )
    13  
    14  // RotatingLogWriter is a wrapper around the LogWriter that supports log file
    15  // rotation.
    16  type RotatingLogWriter struct {
    17  	logWriter *LogWriter
    18  
    19  	backendLog *slog.Backend
    20  
    21  	logRotator *rotator.Rotator
    22  
    23  	subsystemLoggers SubLoggers
    24  }
    25  
    26  // A compile time check to ensure RotatingLogWriter implements the
    27  // LeveledSubLogger interface.
    28  var _ LeveledSubLogger = (*RotatingLogWriter)(nil)
    29  
    30  // NewRotatingLogWriter creates a new file rotating log writer.
    31  //
    32  // NOTE: `InitLogRotator` must be called to set up log rotation after creating
    33  // the writer.
    34  func NewRotatingLogWriter() *RotatingLogWriter {
    35  	logWriter := &LogWriter{}
    36  	backendLog := slog.NewBackend(logWriter)
    37  	return &RotatingLogWriter{
    38  		logWriter:        logWriter,
    39  		backendLog:       backendLog,
    40  		subsystemLoggers: SubLoggers{},
    41  	}
    42  }
    43  
    44  // GenSubLogger creates a new sublogger. A shutdown callback function
    45  // is provided to be able to shutdown in case of a critical error.
    46  func (r *RotatingLogWriter) GenSubLogger(tag string, shutdown func()) slog.Logger {
    47  	logger := r.backendLog.Logger(tag)
    48  	return NewShutdownLogger(logger, shutdown)
    49  }
    50  
    51  // RegisterSubLogger registers a new subsystem logger.
    52  func (r *RotatingLogWriter) RegisterSubLogger(subsystem string,
    53  	logger slog.Logger) {
    54  
    55  	r.subsystemLoggers[subsystem] = logger
    56  }
    57  
    58  // InitLogRotator initializes the log file rotator to write logs to logFile and
    59  // create roll files in the same directory. It should be called as early on
    60  // startup and possible and must be closed on shutdown by calling `Close`.
    61  func (r *RotatingLogWriter) InitLogRotator(logFile string, maxLogFileSize int,
    62  	maxLogFiles int) error {
    63  
    64  	logDir, _ := filepath.Split(logFile)
    65  	err := os.MkdirAll(logDir, 0700)
    66  	if err != nil {
    67  		return fmt.Errorf("failed to create log directory: %v", err)
    68  	}
    69  	r.logRotator, err = rotator.New(
    70  		logFile, int64(maxLogFileSize*1024), false, maxLogFiles,
    71  	)
    72  	if err != nil {
    73  		return fmt.Errorf("failed to create file rotator: %v", err)
    74  	}
    75  
    76  	// Run rotator as a goroutine now but make sure we catch any errors
    77  	// that happen in case something with the rotation goes wrong during
    78  	// runtime (like running out of disk space or not being allowed to
    79  	// create a new logfile for whatever reason).
    80  	pr, pw := io.Pipe()
    81  	go func() {
    82  		err := r.logRotator.Run(pr)
    83  		if err != nil {
    84  			_, _ = fmt.Fprintf(os.Stderr,
    85  				"failed to run file rotator: %v\n", err)
    86  		}
    87  	}()
    88  
    89  	r.logWriter.RotatorPipe = pw
    90  	return nil
    91  }
    92  
    93  // Close closes the underlying log rotator if it has already been created.
    94  func (r *RotatingLogWriter) Close() error {
    95  	if r.logRotator != nil {
    96  		return r.logRotator.Close()
    97  	}
    98  	return nil
    99  }
   100  
   101  // SubLoggers returns all currently registered subsystem loggers for this log
   102  // writer.
   103  //
   104  // NOTE: This is part of the LeveledSubLogger interface.
   105  func (r *RotatingLogWriter) SubLoggers() SubLoggers {
   106  	return r.subsystemLoggers
   107  }
   108  
   109  // SupportedSubsystems returns a sorted string slice of all keys in the
   110  // subsystems map, corresponding to the names of the subsystems.
   111  //
   112  // NOTE: This is part of the LeveledSubLogger interface.
   113  func (r *RotatingLogWriter) SupportedSubsystems() []string {
   114  	// Convert the subsystemLoggers map keys to a string slice.
   115  	subsystems := make([]string, 0, len(r.subsystemLoggers))
   116  	for subsysID := range r.subsystemLoggers {
   117  		subsystems = append(subsystems, subsysID)
   118  	}
   119  
   120  	// Sort the subsystems for stable display.
   121  	sort.Strings(subsystems)
   122  	return subsystems
   123  }
   124  
   125  // SetLogLevel sets the logging level for provided subsystem. Invalid
   126  // subsystems are ignored. Uninitialized subsystems are dynamically created as
   127  // needed.
   128  //
   129  // NOTE: This is part of the LeveledSubLogger interface.
   130  func (r *RotatingLogWriter) SetLogLevel(subsystemID string, logLevel string) {
   131  	// Ignore invalid subsystems.
   132  	logger, ok := r.subsystemLoggers[subsystemID]
   133  	if !ok {
   134  		return
   135  	}
   136  
   137  	// Defaults to info if the log level is invalid.
   138  	level, _ := slog.LevelFromString(logLevel)
   139  	logger.SetLevel(level)
   140  }
   141  
   142  // SetLogLevels sets the log level for all subsystem loggers to the passed
   143  // level. It also dynamically creates the subsystem loggers as needed, so it
   144  // can be used to initialize the logging system.
   145  //
   146  // NOTE: This is part of the LeveledSubLogger interface.
   147  func (r *RotatingLogWriter) SetLogLevels(logLevel string) {
   148  	// Configure all sub-systems with the new logging level. Dynamically
   149  	// create loggers as needed.
   150  	for subsystemID := range r.subsystemLoggers {
   151  		r.SetLogLevel(subsystemID, logLevel)
   152  	}
   153  }