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  }