github.com/ethersphere/bee/v2@v2.2.0/pkg/log/log.go (about) 1 // Copyright 2022 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package log 6 7 import ( 8 "io" 9 "strconv" 10 "sync" 11 "sync/atomic" 12 ) 13 14 // Level specifies a level of verbosity for logger. 15 // Level should be modified only through its set method. 16 // Level is treated as a sync/atomic int32. 17 type Level int32 18 19 // get returns the value of the Level. 20 func (l *Level) get() Level { 21 return Level(atomic.LoadInt32((*int32)(l))) 22 } 23 24 // set updates the value of the Level. 25 func (l *Level) set(v Level) { 26 atomic.StoreInt32((*int32)(l), int32(v)) 27 } 28 29 // String implements the fmt.Stringer interface. 30 func (l Level) String() string { 31 switch l.get() { 32 case VerbosityNone: 33 return "none" 34 case VerbosityError: 35 return "error" 36 case VerbosityWarning: 37 return "warning" 38 case VerbosityInfo: 39 return "info" 40 case VerbosityDebug: 41 return "debug" 42 case VerbosityAll: 43 return "all" 44 } 45 return strconv.FormatInt(int64(l), 10) // Covers all in the range [VerbosityDebug ... VerbosityAll>. 46 } 47 48 // ParseVerbosityLevel returns a verbosity Level parsed from the given s. 49 func ParseVerbosityLevel(s string) (Level, error) { 50 switch s { 51 case "none": 52 return VerbosityNone, nil 53 case "error": 54 return VerbosityError, nil 55 case "warning": 56 return VerbosityWarning, nil 57 case "info": 58 return VerbosityInfo, nil 59 case "debug": 60 return VerbosityDebug, nil 61 case "all": 62 return VerbosityAll, nil 63 } 64 i, err := strconv.ParseInt(s, 10, 32) 65 return Level(i), err 66 } 67 68 // MustParseVerbosityLevel returns a verbosity Level parsed from the given s. 69 // It panics if the given s is not a valid verbosity level. 70 func MustParseVerbosityLevel(s string) Level { 71 l, err := ParseVerbosityLevel(s) 72 if err != nil { 73 panic(err) 74 } 75 return l 76 } 77 78 const ( 79 // VerbosityNone will silence the logger. 80 VerbosityNone = Level(iota - 4) 81 // VerbosityError allows only error messages to be printed. 82 VerbosityError 83 // VerbosityWarning allows only error and warning messages to be printed. 84 VerbosityWarning 85 // VerbosityInfo allows only error, warning and info messages to be printed. 86 VerbosityInfo 87 // VerbosityDebug allows only error, warning, info and debug messages to be printed. 88 VerbosityDebug 89 // VerbosityAll allows to print all messages up to and including level V. 90 // It is a placeholder for the maximum possible V level verbosity within the logger. 91 VerbosityAll = Level(1<<31 - 1) 92 ) 93 94 // Hook that is fired when logging 95 // on the associated severity log level. 96 // Note, the call must be non-blocking. 97 type Hook interface { 98 Fire(Level) error 99 } 100 101 // Builder specifies a set of methods that can be used to 102 // modify the behavior of the logger before it is created. 103 type Builder interface { 104 // V specifies verbosity level added to the debug verbosity, relative to 105 // the parent Logger. In other words, V-levels are additive. A higher 106 // verbosity level means a log message is less important. 107 V(v uint) Builder 108 109 // WithName specifies a name element added to the Logger's name. 110 // Successive calls with WithName append additional suffixes to the 111 // Logger's name. It's strongly recommended that name segments contain 112 // only letters, digits, and hyphens (see the package documentation for 113 // more information). Formatter uses '/' characters to separate name 114 // elements. Callers should not pass '/' in the provided name string. 115 WithName(name string) Builder 116 117 // WithValues specifies additional key/value pairs 118 // to be logged with each log line. 119 WithValues(keysAndValues ...interface{}) Builder 120 121 // Build returns a new or existing Logger 122 // instance, if such instance already exists. 123 // The new instance is not registered in the 124 // logger registry. 125 Build() Logger 126 127 // Register returns new or existing Logger 128 // instance, if such instance already exists. 129 // If a new instance is created, it is also 130 // registered in the logger registry. 131 Register() Logger 132 } 133 134 // Logger provides a set of methods that define the behavior of the logger. 135 type Logger interface { 136 Builder 137 138 // Verbosity returns the current verbosity level of this logger. 139 Verbosity() Level 140 141 // Debug logs a debug message with the given key/value pairs as context. 142 // The msg argument should be used to add some constant description to 143 // the log line. The key/value pairs can then be used to add additional 144 // variable information. The key/value pairs must alternate string keys 145 // and arbitrary values. 146 Debug(msg string, keysAndValues ...interface{}) 147 148 // Info logs an info message with the given key/value pairs as context. 149 // The msg argument should be used to add some constant description to 150 // the log line. The key/value pairs can then be used to add additional 151 // variable information. The key/value pairs must alternate string keys 152 // and arbitrary values. 153 Info(msg string, keysAndValues ...interface{}) 154 155 // Warning logs a warning message with the given key/value pairs as context. 156 // The msg argument should be used to add some constant description to 157 // the log line. The key/value pairs can then be used to add additional 158 // variable information. The key/value pairs must alternate string keys 159 // and arbitrary values. 160 Warning(msg string, keysAndValues ...interface{}) 161 162 // Error logs an error, with the given message and key/value pairs as context. 163 // The msg argument should be used to add context to any underlying error, 164 // while the err argument should be used to attach the actual error that 165 // triggered this log line, if present. The err parameter is optional 166 // and nil may be passed instead of an error instance. 167 Error(err error, msg string, keysAndValues ...interface{}) 168 } 169 170 // Lock wraps io.Writer in a mutex to make it safe for concurrent use. 171 // In particular, *os.Files must be locked before use. 172 func Lock(w io.Writer) io.Writer { 173 if _, ok := w.(*lockWriter); ok { 174 return w // No need to layer on another lock. 175 } 176 return &lockWriter{w: w} 177 } 178 179 // lockWriter attaches mutex to io.Writer for convince of usage. 180 type lockWriter struct { 181 sync.Mutex 182 w io.Writer 183 } 184 185 // Write implements the io.Writer interface. 186 func (ls *lockWriter) Write(bs []byte) (int, error) { 187 ls.Lock() 188 n, err := ls.w.Write(bs) 189 ls.Unlock() 190 return n, err 191 } 192 193 // Options specifies parameters that affect logger behavior. 194 type Options struct { 195 sink io.Writer 196 verbosity Level 197 levelHooks levelHooks 198 fmtOptions fmtOptions 199 logMetrics *metrics 200 } 201 202 // Option represent Options parameters modifier. 203 type Option func(*Options) 204 205 // WithSink tells the logger to log to the given sync. 206 // The provided sync should be safe for concurrent use, 207 // if it is not then it should be wrapped with Lock helper. 208 func WithSink(sink io.Writer) Option { 209 return func(opts *Options) { opts.sink = sink } 210 } 211 212 // WithVerbosity tells the logger which verbosity level should be logged by default. 213 func WithVerbosity(verbosity Level) Option { 214 return func(opts *Options) { opts.verbosity = verbosity } 215 } 216 217 // WithCaller tells the logger to add a "caller" key to some or all log lines. 218 // This has some overhead, so some users might not want it. 219 func WithCaller(category MessageCategory) Option { 220 return func(opts *Options) { opts.fmtOptions.caller = category } 221 } 222 223 // WithCallerFunc tells the logger to also log the calling function name. 224 // This has no effect if caller logging is not enabled (see WithCaller). 225 func WithCallerFunc() Option { 226 return func(opts *Options) { opts.fmtOptions.logCallerFunc = true } 227 } 228 229 // WithTimestamp tells the logger to add a "timestamp" key to log lines. 230 // This has some overhead, so some users might not want it. 231 func WithTimestamp() Option { 232 return func(opts *Options) { opts.fmtOptions.logTimestamp = true } 233 } 234 235 // WithTimestampLayout tells the logger how to render timestamps when 236 // WithTimestamp is enabled. If not specified, a default format will 237 // be used. For more details, see docs for Go's time.Layout. 238 func WithTimestampLayout(layout string) Option { 239 return func(opts *Options) { opts.fmtOptions.timestampLayout = layout } 240 } 241 242 // WithMaxDepth tells the logger how many levels of nested fields 243 // (e.g. a struct that contains a struct, etc.) it may log. Every time 244 // it finds a struct, slice, array, or map the depth is increased by one. 245 // When the maximum is reached, the value will be converted to a string 246 // indicating that the max depth has been exceeded. If this field is not 247 // specified, a default value will be used. 248 func WithMaxDepth(depth int) Option { 249 return func(opts *Options) { opts.fmtOptions.maxLogDepth = depth } 250 } 251 252 // WithJSONOutput tells the logger if the output should be formatted as JSON. 253 func WithJSONOutput() Option { 254 return func(opts *Options) { opts.fmtOptions.jsonOutput = true } 255 } 256 257 // WithCallerDepth tells the logger the number of stack-frames 258 // to skip when attributing the log line to a file and line. 259 func WithCallerDepth(depth int) Option { 260 return func(opts *Options) { opts.fmtOptions.callerDepth = depth } 261 } 262 263 // WithLevelHooks tells the logger to register and execute hooks at related 264 // severity log levels. If VerbosityAll is given, then the given hooks will 265 // be registered with each severity log level, including the debug V levels. 266 // On the other hand, if VerbosityNone is given, hooks will 267 // not be registered with any severity log level. 268 func WithLevelHooks(l Level, hooks ...Hook) Option { 269 return func(opts *Options) { 270 if opts.levelHooks == nil { 271 opts.levelHooks = make(map[Level][]Hook) 272 } 273 switch l { 274 case VerbosityNone: 275 return 276 case VerbosityAll: 277 for _, ml := range []Level{ 278 VerbosityError, 279 VerbosityWarning, 280 VerbosityInfo, 281 VerbosityDebug, 282 VerbosityAll, // V levels. 283 } { 284 opts.levelHooks[ml] = append(opts.levelHooks[ml], hooks...) 285 } 286 default: 287 opts.levelHooks[l] = append(opts.levelHooks[l], hooks...) 288 } 289 } 290 } 291 292 // WithLogMetrics tells the logger to collect metrics about log messages. 293 func WithLogMetrics() Option { 294 return func(opts *Options) { 295 if opts.logMetrics != nil { 296 return 297 } 298 opts.logMetrics = newLogMetrics() 299 WithLevelHooks(VerbosityAll, opts.logMetrics)(opts) 300 } 301 }