lnd/build/sub_logger.go

264 lines
7.3 KiB
Go
Raw Normal View History

package build
import (
"fmt"
"sort"
"strings"
"sync"
"github.com/btcsuite/btclog/v2"
)
// 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(handlers ...btclog.Handler) *SubLoggerManager {
return &SubLoggerManager{
loggers: make(SubLoggers),
genLogger: newSubLogGenerator(
newHandlerSet(btclog.LevelInfo, handlers...),
),
}
}
// 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
// LeveledSubLogger provides the ability to retrieve the subsystem loggers of
// a logger and set their log levels individually or all at once.
type LeveledSubLogger interface {
// SubLoggers returns the map of all registered subsystem loggers.
SubLoggers() SubLoggers
// SupportedSubsystems returns a slice of strings containing the names
// of the supported subsystems. Should ideally correspond to the keys
// of the subsystem logger map and be sorted.
SupportedSubsystems() []string
// SetLogLevel assigns an individual subsystem logger a new log level.
SetLogLevel(subsystemID string, logLevel string)
// SetLogLevels assigns all subsystem loggers the same new log level.
SetLogLevels(logLevel string)
}
// ParseAndSetDebugLevels attempts to parse the specified debug level and set
// the levels accordingly on the given logger. An appropriate error is returned
// if anything is invalid.
func ParseAndSetDebugLevels(level string, logger LeveledSubLogger) error {
// Split at the delimiter.
levels := strings.Split(level, ",")
if len(levels) == 0 {
return fmt.Errorf("invalid log level: %v", level)
}
// If the first entry has no =, treat is as the log level for all
// subsystems.
globalLevel := levels[0]
if !strings.Contains(globalLevel, "=") {
// Validate debug log level.
if !validLogLevel(globalLevel) {
str := "the specified debug level [%v] is invalid"
return fmt.Errorf(str, globalLevel)
}
// Change the logging level for all subsystems.
logger.SetLogLevels(globalLevel)
// The rest will target specific subsystems.
levels = levels[1:]
}
// Go through the subsystem/level pairs while detecting issues and
// update the log levels accordingly.
for _, logLevelPair := range levels {
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, "=")
if len(fields) != 2 {
str := "the specified debug level has an invalid " +
"format [%v] -- use format subsystem1=level1," +
"subsystem2=level2"
return fmt.Errorf(str, logLevelPair)
}
subsysID, logLevel := fields[0], fields[1]
subLoggers := logger.SubLoggers()
// Validate subsystem.
if _, exists := subLoggers[subsysID]; !exists {
str := "the specified subsystem [%v] is invalid -- " +
"supported subsystems are %v"
return fmt.Errorf(
str, subsysID, logger.SupportedSubsystems(),
)
}
// Validate log level.
if !validLogLevel(logLevel) {
str := "the specified debug level [%v] is invalid"
return fmt.Errorf(str, logLevel)
}
logger.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
}