mirror of https://github.com/lightninglabs/loop
cmd/loopd: add persistent logging, debug level toggle
In this commit, we add persistent logic to the loopd daemon. Users can now designate a default logging directory, as well as log rotation options. Additionally, we've added a new `--debuglevel` command which allows users to crank up the logging for particular sub-systems on start up. Fixes #5.pull/30/head
parent
9a824d88b1
commit
6ddf0fa523
@ -1,24 +1,207 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/btcsuite/btclog"
|
"github.com/btcsuite/btclog"
|
||||||
|
"github.com/jrick/logrotate/rotator"
|
||||||
|
"github.com/lightninglabs/loop"
|
||||||
|
"github.com/lightninglabs/loop/lndclient"
|
||||||
|
"github.com/lightningnetwork/lnd/build"
|
||||||
)
|
)
|
||||||
|
|
||||||
// log is a logger that is initialized with no output filters. This means the
|
// Loggers per subsystem. A single backend logger is created and all subsystem
|
||||||
// package will not perform any logging by default until the caller requests
|
// loggers created from it will write to the backend. When adding new
|
||||||
// it.
|
// 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 (
|
var (
|
||||||
backendLog = btclog.NewBackend(logWriter{})
|
logWriter = &build.LogWriter{}
|
||||||
logger = backendLog.Logger("LOOPD")
|
|
||||||
|
// 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)
|
||||||
)
|
)
|
||||||
|
|
||||||
// logWriter implements an io.Writer that outputs to both standard output and
|
func init() {
|
||||||
// the write-end pipe of an initialized log rotator.
|
loop.UseLogger(loopLog)
|
||||||
type logWriter struct{}
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
func (logWriter) Write(p []byte) (n int, err error) {
|
// Sort the subsystems for stable display.
|
||||||
os.Stdout.Write(p)
|
sort.Strings(subsystems)
|
||||||
return len(p), nil
|
return subsystems
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue