You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
208 lines
5.9 KiB
208 lines
5.9 KiB
package build |
|
|
|
import ( |
|
"fmt" |
|
"io" |
|
"strings" |
|
|
|
"github.com/btcsuite/btclog" |
|
) |
|
|
|
// LogType is an indicating the type of logging specified by the build flag. |
|
type LogType byte |
|
|
|
const ( |
|
// LogTypeNone indicates no logging. |
|
LogTypeNone LogType = iota |
|
|
|
// LogTypeStdOut all logging is written directly to stdout. |
|
LogTypeStdOut |
|
|
|
// LogTypeDefault logs to both stdout and a given io.PipeWriter. |
|
LogTypeDefault |
|
) |
|
|
|
// String returns a human readable identifier for the logging type. |
|
func (t LogType) String() string { |
|
switch t { |
|
case LogTypeNone: |
|
return "none" |
|
case LogTypeStdOut: |
|
return "stdout" |
|
case LogTypeDefault: |
|
return "default" |
|
default: |
|
return "unknown" |
|
} |
|
} |
|
|
|
// LogWriter is a stub type whose behavior can be changed using the build flags |
|
// "stdlog" and "nolog". The default behavior is to write to both stdout and the |
|
// RotatorPipe. Passing "stdlog" will cause it only to write to stdout, and |
|
// "nolog" implements Write as a no-op. |
|
type LogWriter struct { |
|
// RotatorPipe is the write-end pipe for writing to the log rotator. It |
|
// is written to by the Write method of the LogWriter type. This only |
|
// needs to be set if neither the stdlog or nolog builds are set. |
|
RotatorPipe *io.PipeWriter |
|
} |
|
|
|
// NewSubLogger constructs a new subsystem log from the current LogWriter |
|
// implementation. This is primarily intended for use with stdlog, as the actual |
|
// writer is shared amongst all instantiations. |
|
func NewSubLogger(subsystem string, |
|
genSubLogger func(string) btclog.Logger) btclog.Logger { |
|
|
|
switch Deployment { |
|
|
|
// For production builds, generate a new subsystem logger from the |
|
// primary log backend. If no function is provided, logging will be |
|
// disabled. |
|
case Production: |
|
if genSubLogger != nil { |
|
return genSubLogger(subsystem) |
|
} |
|
|
|
// For development builds, we must handle two distinct types of logging: |
|
// unit tests and running the live daemon, e.g. for integration testing. |
|
case Development: |
|
switch LoggingType { |
|
|
|
// Default logging is used when running the standalone daemon. |
|
// We'll use the optional sublogger constructor to mimic the |
|
// production behavior. |
|
case LogTypeDefault: |
|
if genSubLogger != nil { |
|
return genSubLogger(subsystem) |
|
} |
|
|
|
// Logging to stdout is used in unit tests. It is not important |
|
// that they share the same backend, since all output is written |
|
// to std out. |
|
case LogTypeStdOut: |
|
backend := btclog.NewBackend(&LogWriter{}) |
|
logger := backend.Logger(subsystem) |
|
|
|
// Set the logging level of the stdout logger to use the |
|
// configured logging level specified by build flags. |
|
level, _ := btclog.LevelFromString(LogLevel) |
|
logger.SetLevel(level) |
|
|
|
return logger |
|
} |
|
} |
|
|
|
// For any other configurations, we'll disable logging. |
|
return btclog.Disabled |
|
} |
|
|
|
// 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 |
|
}
|
|
|