2024-09-10 17:25:40 +02:00
|
|
|
package build
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2024-09-10 18:21:35 +02:00
|
|
|
"sort"
|
2024-09-10 17:25:40 +02:00
|
|
|
"strings"
|
2024-09-10 18:21:35 +02:00
|
|
|
"sync"
|
2024-09-10 17:25:40 +02:00
|
|
|
|
2024-10-15 14:34:24 +02:00
|
|
|
"github.com/btcsuite/btclog/v2"
|
2024-09-10 17:25:40 +02:00
|
|
|
)
|
|
|
|
|
2024-09-10 18:21:35 +02:00
|
|
|
// 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.
|
2024-10-15 14:34:24 +02:00
|
|
|
func NewSubLoggerManager(handlers ...btclog.Handler) *SubLoggerManager {
|
2024-09-10 18:21:35 +02:00
|
|
|
return &SubLoggerManager{
|
2024-10-15 14:34:24 +02:00
|
|
|
loggers: make(SubLoggers),
|
|
|
|
genLogger: newSubLogGenerator(
|
|
|
|
newHandlerSet(btclog.LevelInfo, handlers...),
|
|
|
|
),
|
2024-09-10 18:21:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-10 17:25:40 +02:00
|
|
|
// 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
|
|
|
|
}
|