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 }