github.com/decred/dcrlnd@v0.7.6/build/log.go (about) 1 package build 2 3 import ( 4 "fmt" 5 "io" 6 "strings" 7 8 "github.com/decred/slog" 9 ) 10 11 // LogType is an indicating the type of logging specified by the build flag. 12 type LogType byte 13 14 const ( 15 // LogTypeNone indicates no logging. 16 LogTypeNone LogType = iota 17 18 // LogTypeStdOut all logging is written directly to stdout. 19 LogTypeStdOut 20 21 // LogTypeDefault logs to both stdout and a given io.PipeWriter. 22 LogTypeDefault 23 ) 24 25 // String returns a human readable identifier for the logging type. 26 func (t LogType) String() string { 27 switch t { 28 case LogTypeNone: 29 return "none" 30 case LogTypeStdOut: 31 return "stdout" 32 case LogTypeDefault: 33 return "default" 34 default: 35 return "unknown" 36 } 37 } 38 39 // LogWriter is a stub type whose behavior can be changed using the build flags 40 // "stdlog" and "nolog". The default behavior is to write to both stdout and the 41 // RotatorPipe. Passing "stdlog" will cause it only to write to stdout, and 42 // "nolog" implements Write as a no-op. 43 type LogWriter struct { 44 // RotatorPipe is the write-end pipe for writing to the log rotator. It 45 // is written to by the Write method of the LogWriter type. This only 46 // needs to be set if neither the stdlog or nolog builds are set. 47 RotatorPipe *io.PipeWriter 48 } 49 50 // NewSubLogger constructs a new subsystem log from the current LogWriter 51 // implementation. This is primarily intended for use with stdlog, as the actual 52 // writer is shared amongst all instantiations. 53 func NewSubLogger(subsystem string, 54 genSubLogger func(string) slog.Logger) slog.Logger { 55 56 switch Deployment { 57 58 // For production builds, generate a new subsystem logger from the 59 // primary log backend. If no function is provided, logging will be 60 // disabled. 61 case Production: 62 if genSubLogger != nil { 63 return genSubLogger(subsystem) 64 } 65 66 // For development builds, we must handle two distinct types of logging: 67 // unit tests and running the live daemon, e.g. for integration testing. 68 case Development: 69 switch LoggingType { 70 71 // Default logging is used when running the standalone daemon. 72 // We'll use the optional sublogger constructor to mimic the 73 // production behavior. 74 case LogTypeDefault: 75 if genSubLogger != nil { 76 return genSubLogger(subsystem) 77 } 78 79 // Logging to stdout is used in unit tests. It is not important 80 // that they share the same backend, since all output is written 81 // to std out. 82 case LogTypeStdOut: 83 backend := slog.NewBackend(&LogWriter{}) 84 logger := backend.Logger(subsystem) 85 86 // Set the logging level of the stdout logger to use the 87 // configured logging level specified by build flags. 88 level, _ := slog.LevelFromString(LogLevel) 89 logger.SetLevel(level) 90 91 return logger 92 } 93 } 94 95 // For any other configurations, we'll disable logging. 96 return slog.Disabled 97 } 98 99 // SubLoggers is a type that holds a map of subsystem loggers keyed by their 100 // subsystem name. 101 type SubLoggers map[string]slog.Logger 102 103 // LeveledSubLogger provides the ability to retrieve the subsystem loggers of 104 // a logger and set their log levels individually or all at once. 105 type LeveledSubLogger interface { 106 // SubLoggers returns the map of all registered subsystem loggers. 107 SubLoggers() SubLoggers 108 109 // SupportedSubsystems returns a slice of strings containing the names 110 // of the supported subsystems. Should ideally correspond to the keys 111 // of the subsystem logger map and be sorted. 112 SupportedSubsystems() []string 113 114 // SetLogLevel assigns an individual subsystem logger a new log level. 115 SetLogLevel(subsystemID string, logLevel string) 116 117 // SetLogLevels assigns all subsystem loggers the same new log level. 118 SetLogLevels(logLevel string) 119 } 120 121 // ParseAndSetDebugLevels attempts to parse the specified debug level and set 122 // the levels accordingly on the given logger. An appropriate error is returned 123 // if anything is invalid. 124 func ParseAndSetDebugLevels(level string, logger LeveledSubLogger) error { 125 // Split at the delimiter. 126 levels := strings.Split(level, ",") 127 if len(levels) == 0 { 128 return fmt.Errorf("invalid log level: %v", level) 129 } 130 131 // If the first entry has no =, treat is as the log level for all 132 // subsystems. 133 globalLevel := levels[0] 134 if !strings.Contains(globalLevel, "=") { 135 // Validate debug log level. 136 if !validLogLevel(globalLevel) { 137 str := "the specified debug level [%v] is invalid" 138 return fmt.Errorf(str, globalLevel) 139 } 140 141 // Change the logging level for all subsystems. 142 logger.SetLogLevels(globalLevel) 143 144 // The rest will target specific subsystems. 145 levels = levels[1:] 146 } 147 148 // Go through the subsystem/level pairs while detecting issues and 149 // update the log levels accordingly. 150 for _, logLevelPair := range levels { 151 if !strings.Contains(logLevelPair, "=") { 152 str := "the specified debug level contains an " + 153 "invalid subsystem/level pair [%v]" 154 return fmt.Errorf(str, logLevelPair) 155 } 156 157 // Extract the specified subsystem and log level. 158 fields := strings.Split(logLevelPair, "=") 159 if len(fields) != 2 { 160 str := "the specified debug level has an invalid " + 161 "format [%v] -- use format subsystem1=level1," + 162 "subsystem2=level2" 163 return fmt.Errorf(str, logLevelPair) 164 } 165 subsysID, logLevel := fields[0], fields[1] 166 subLoggers := logger.SubLoggers() 167 168 // Validate subsystem. 169 if _, exists := subLoggers[subsysID]; !exists { 170 str := "the specified subsystem [%v] is invalid -- " + 171 "supported subsystems are %v" 172 return fmt.Errorf( 173 str, subsysID, logger.SupportedSubsystems(), 174 ) 175 } 176 177 // Validate log level. 178 if !validLogLevel(logLevel) { 179 str := "the specified debug level [%v] is invalid" 180 return fmt.Errorf(str, logLevel) 181 } 182 183 logger.SetLogLevel(subsysID, logLevel) 184 } 185 186 return nil 187 } 188 189 // validLogLevel returns whether or not logLevel is a valid debug log level. 190 func validLogLevel(logLevel string) bool { 191 switch logLevel { 192 case "trace": 193 fallthrough 194 case "debug": 195 fallthrough 196 case "info": 197 fallthrough 198 case "warn": 199 fallthrough 200 case "error": 201 fallthrough 202 case "critical": 203 fallthrough 204 case "off": 205 return true 206 } 207 return false 208 }