package main import ( "fmt" "io" "os" "path/filepath" "sort" "strings" "github.com/btcsuite/btclog" "github.com/jrick/logrotate/rotator" "github.com/lightninglabs/loop" "github.com/lightninglabs/loop/lndclient" "github.com/lightningnetwork/lnd/build" ) // Loggers per subsystem. A single backend logger is created and all subsystem // loggers created from it will write to the backend. When adding new // subsystems, add the subsystem logger variable here and to the // subsystemLoggers map. // // Loggers can not be used before the log rotator has been initialized with a // log file. This must be performed early during application startup by // calling initLogRotator. var ( logWriter = &build.LogWriter{} // backendLog is the logging backend used to create all subsystem // loggers. The backend must not be used before the log rotator has // been initialized, or data races and/or nil pointer dereferences will // occur. backendLog = btclog.NewBackend(logWriter) // logRotator is one of the logging outputs. It should be closed on // application shutdown. logRotator *rotator.Rotator loopdLog = build.NewSubLogger("LOOPD", backendLog.Logger) loopLog = build.NewSubLogger("LOOP", backendLog.Logger) lndClientLog = build.NewSubLogger("LNDC", backendLog.Logger) ) func init() { loop.UseLogger(loopLog) lndclient.UseLogger(lndClientLog) } // 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 } // subsystemLoggers maps each subsystem identifier to its associated logger. var subsystemLoggers = map[string]btclog.Logger{ "LOOPD": loopdLog, "LOOP": loopLog, "LNDC": lndClientLog, } // 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 // String invokes the underlying function and returns the result. func (c logClosure) String() string { return c() } // newLogClosure returns a new closure over a function that returns a string // which itself provides a Stringer interface so that it can be used with the // logging system. func newLogClosure(c func() string) logClosure { return logClosure(c) } // parseAndSetDebugLevels attempts to parse the specified debug level and set // the levels accordingly. An appropriate error is returned if anything is // invalid. func parseAndSetDebugLevels(debugLevel string) error { // When the specified string doesn't have any delimiters, treat it as // the log level for all subsystems. if !strings.Contains(debugLevel, ",") && !strings.Contains(debugLevel, "=") { // Validate debug log level. if !validLogLevel(debugLevel) { str := "The specified debug level [%v] is invalid" return fmt.Errorf(str, debugLevel) } // Change the logging level for all subsystems. setLogLevels(debugLevel) return nil } // Split the specified string into subsystem/level pairs while detecting // issues and update the log levels accordingly. for _, logLevelPair := range strings.Split(debugLevel, ",") { if !strings.Contains(logLevelPair, "=") { str := "The specified debug level contains an invalid " + "subsystem/level pair [%v]" return fmt.Errorf(str, logLevelPair) } // Extract the specified subsystem and log level. fields := strings.Split(logLevelPair, "=") subsysID, logLevel := fields[0], fields[1] // Validate subsystem. if _, exists := subsystemLoggers[subsysID]; !exists { str := "The specified subsystem [%v] is invalid -- " + "supported subsystems %v" return fmt.Errorf(str, subsysID, supportedSubsystems()) } // Validate log level. if !validLogLevel(logLevel) { str := "The specified debug level [%v] is invalid" return fmt.Errorf(str, logLevel) } setLogLevel(subsysID, logLevel) } return nil } // validLogLevel returns whether or not logLevel is a valid debug log level. func validLogLevel(logLevel string) bool { switch logLevel { case "trace": fallthrough case "debug": fallthrough case "info": fallthrough case "warn": fallthrough case "error": fallthrough case "critical": fallthrough case "off": return true } return false } // supportedSubsystems returns a sorted slice of the supported subsystems for // logging purposes. func supportedSubsystems() []string { // Convert the subsystemLoggers map keys to a slice. subsystems := make([]string, 0, len(subsystemLoggers)) for subsysID := range subsystemLoggers { subsystems = append(subsystems, subsysID) } // Sort the subsystems for stable display. sort.Strings(subsystems) return subsystems }