github.com/ethersphere/bee/v2@v2.2.0/pkg/log/logger.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  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"reflect"
    13  	"strings"
    14  	"time"
    15  
    16  	m "github.com/ethersphere/bee/v2/pkg/metrics"
    17  	"github.com/hashicorp/go-multierror"
    18  	"github.com/prometheus/client_golang/prometheus"
    19  )
    20  
    21  var _ Logger = (*logger)(nil)
    22  
    23  // levelHooks is a helper type for storing and
    24  // help triggering the hooks on a logger instance.
    25  type levelHooks map[Level][]Hook
    26  
    27  // fire triggers all the hooks for the given level.
    28  // If level V is enabled in debug verbosity, then
    29  // the VerbosityAll hooks are triggered.
    30  func (lh levelHooks) fire(level Level) error {
    31  	if level > VerbosityDebug {
    32  		level = VerbosityAll
    33  	}
    34  	for _, hook := range lh[level] {
    35  		if err := hook.Fire(level); err != nil {
    36  			return err
    37  		}
    38  	}
    39  	return nil
    40  }
    41  
    42  type builder struct {
    43  	l *logger
    44  
    45  	// clone indicates whether this builder was cloned.
    46  	cloned bool
    47  
    48  	// v level represents the granularity of debug calls.
    49  	v uint
    50  
    51  	// names represents a path in the tree,
    52  	// element 0 is the root of the tree.
    53  	names []string
    54  
    55  	// namesStr is a cache of render names slice, so
    56  	// we don't have to render them on each Build call.
    57  	namesStr string
    58  
    59  	// values holds additional key/value pairs
    60  	// that are included on every log call.
    61  	values []interface{}
    62  
    63  	// valuesStr is a cache of render values slice, so
    64  	// we don't have to render them on each Build call.
    65  	valuesStr string
    66  }
    67  
    68  // V implements the Builder interface V method.
    69  func (b *builder) V(level uint) Builder {
    70  	if level > 0 {
    71  		c := b.clone()
    72  		c.v += level
    73  		return c
    74  	}
    75  	return b
    76  }
    77  
    78  // WithName implements the Builder interface WithName method.
    79  func (b *builder) WithName(name string) Builder {
    80  	c := b.clone()
    81  	c.names = append(c.names, name)
    82  	return c
    83  }
    84  
    85  // WithValues implements the Builder interface WithValues method.
    86  func (b *builder) WithValues(keysAndValues ...interface{}) Builder {
    87  	c := b.clone()
    88  	c.values = append(c.values, keysAndValues...)
    89  	return c
    90  }
    91  
    92  // Build implements the Builder interface Build method.
    93  func (b *builder) Build() Logger {
    94  	if !b.cloned && b.l.id != "" {
    95  		return b.l
    96  	}
    97  
    98  	b.namesStr = strings.Join(b.names, "/")
    99  	// ~5 is the average length of an English word; 4 is the rune size.
   100  	bufCap := nextPowOf2(uint64(5 * 4 * len(b.values)))
   101  	buf := bytes.NewBuffer(make([]byte, 0, bufCap))
   102  	b.l.formatter.flatten(buf, b.values, false, false)
   103  	b.valuesStr = buf.String()
   104  
   105  	key := hash(b.namesStr, b.v, b.valuesStr, b.l.sink)
   106  	if i, ok := loggers.Load(key); ok {
   107  		// Nothing to build, the instance exists.
   108  		return i.(*logger)
   109  	}
   110  	// A new child instance.
   111  	c := *b.l
   112  	b.l = &c
   113  	c.builder = b
   114  	c.cloned = false
   115  	c.id = key
   116  
   117  	return &c
   118  }
   119  
   120  // Register implements the Builder interface Register method.
   121  func (b *builder) Register() Logger {
   122  	val := b.Build()
   123  	key := hash(b.namesStr, b.v, b.valuesStr, b.l.sink)
   124  	res, _ := loggers.LoadOrStore(key, val)
   125  	return res.(*logger)
   126  }
   127  
   128  func (b *builder) clone() *builder {
   129  	if b.cloned {
   130  		return b
   131  	}
   132  
   133  	c := *b
   134  	c.cloned = true
   135  	c.names = append(make([]string, 0, len(c.names)), c.names...)
   136  	c.values = append(make([]interface{}, 0, len(c.values)), c.values...)
   137  	return &c
   138  }
   139  
   140  // logger implements the Logger interface.
   141  type logger struct {
   142  	*builder
   143  
   144  	// id is the unique identifier of a logger.
   145  	// It identifies the instance of a logger in the logger registry.
   146  	id string
   147  
   148  	// formatter formats log messages before they are written to the sink.
   149  	formatter *formatter
   150  
   151  	// verbosity represents the current verbosity level.
   152  	// This variable is used to modify the verbosity of the logger instance.
   153  	// Higher values enable more logs. Logs at or below this level
   154  	// will be written, while logs above this level will be discarded.
   155  	verbosity Level
   156  
   157  	// sink represents the stream where the logs are written.
   158  	sink io.Writer
   159  
   160  	// levelHooks allow triggering of registered hooks
   161  	// on their associated severity log levels.
   162  	levelHooks levelHooks
   163  
   164  	// metrics collects basic statistics about logged messages.
   165  	metrics *metrics
   166  }
   167  
   168  // Metrics implements metrics.Collector interface.
   169  func (l *logger) Metrics() []prometheus.Collector {
   170  	return m.PrometheusCollectorsFromFields(l.metrics)
   171  }
   172  
   173  // Verbosity implements the Logger interface Verbosity method.
   174  func (l *logger) Verbosity() Level {
   175  	return l.verbosity.get()
   176  }
   177  
   178  // Debug implements the Logger interface Debug method.
   179  func (l *logger) Debug(msg string, keysAndValues ...interface{}) {
   180  	if int(l.verbosity.get()) >= int(l.v) {
   181  		if err := l.log(VerbosityDebug, CategoryDebug, nil, msg, keysAndValues...); err != nil {
   182  			fmt.Fprintln(os.Stderr, err)
   183  		}
   184  	}
   185  }
   186  
   187  // Info implements the Logger interface Info method.
   188  func (l *logger) Info(msg string, keysAndValues ...interface{}) {
   189  	if l.verbosity.get() >= VerbosityInfo {
   190  		if err := l.log(VerbosityInfo, CategoryInfo, nil, msg, keysAndValues...); err != nil {
   191  			fmt.Fprintln(os.Stderr, err)
   192  		}
   193  	}
   194  }
   195  
   196  // Warning implements the Logger interface Warning method.
   197  func (l *logger) Warning(msg string, keysAndValues ...interface{}) {
   198  	if l.verbosity.get() >= VerbosityWarning {
   199  		if err := l.log(VerbosityWarning, CategoryWarning, nil, msg, keysAndValues...); err != nil {
   200  			fmt.Fprintln(os.Stderr, err)
   201  		}
   202  	}
   203  }
   204  
   205  // Error implements the Logger interface Error method.
   206  func (l *logger) Error(err error, msg string, keysAndValues ...interface{}) {
   207  	if l.verbosity.get() >= VerbosityError {
   208  		if err := l.log(VerbosityError, CategoryError, err, msg, keysAndValues...); err != nil {
   209  			fmt.Fprintln(os.Stderr, err)
   210  		}
   211  	}
   212  }
   213  
   214  // setVerbosity changes the verbosity level or the logger.
   215  func (l *logger) setVerbosity(v Level) {
   216  	l.verbosity.set(v)
   217  }
   218  
   219  // log logs the given msg and key-value pairs with the given level
   220  // and the given message category caller (if enabled) to the sink.
   221  func (l *logger) log(vl Level, mc MessageCategory, err error, msg string, keysAndValues ...interface{}) error {
   222  	base := make([]interface{}, 0, 14+len(keysAndValues))
   223  	if l.formatter.opts.logTimestamp {
   224  		base = append(base, "time", time.Now().Format(l.formatter.opts.timestampLayout))
   225  	}
   226  	base = append(base, "level", vl.String(), "logger", l.namesStr)
   227  	if vl == VerbosityDebug && l.v > 0 {
   228  		base = append(base, "v", l.v)
   229  	}
   230  	if policy := l.formatter.opts.caller; policy == CategoryAll || policy == mc {
   231  		base = append(base, "caller", l.formatter.caller())
   232  	}
   233  	base = append(base, "msg", msg)
   234  	if vl == VerbosityError {
   235  		if err != nil {
   236  			base = append(base, "error", err.Error())
   237  		}
   238  	}
   239  	if len(l.values) > 0 {
   240  		base = append(base, l.values...)
   241  	}
   242  	buf := l.formatter.render(base, keysAndValues)
   243  
   244  	var merr *multierror.Error
   245  	if _, err = l.sink.Write(buf); err != nil {
   246  		merr = multierror.Append(
   247  			merr,
   248  			fmt.Errorf("log %s: failed to write message: %w", vl, err),
   249  		)
   250  	}
   251  	if err := l.levelHooks.fire(vl + Level(l.v)); err != nil {
   252  		merr = multierror.Append(
   253  			merr,
   254  			fmt.Errorf("log %s: failed to fire hooks: %w", vl, err),
   255  		)
   256  	}
   257  	return merr.ErrorOrNil()
   258  }
   259  
   260  // hash is a hashing function for creating unique identifiers.
   261  func hash(prefix string, v uint, values string, w io.Writer) string {
   262  	var sink uintptr
   263  	if reflect.ValueOf(w).Kind() == reflect.Ptr {
   264  		sink = reflect.ValueOf(w).Pointer()
   265  	} else {
   266  		sink = reflect.ValueOf(&w).Pointer()
   267  	}
   268  	return fmt.Sprintf("%s[%d][%s]>>%d", prefix, v, values, sink)
   269  }
   270  
   271  // nextPowOf2 rounds up n to the next highest power of 2.
   272  // See: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
   273  func nextPowOf2(n uint64) uint64 {
   274  	n--
   275  	n |= n >> 1
   276  	n |= n >> 2
   277  	n |= n >> 4
   278  	n |= n >> 8
   279  	n |= n >> 16
   280  	n |= n >> 32
   281  	n++
   282  	return n
   283  }
   284  
   285  // TODO:
   286  // - Implement the HTTP log middleware
   287  // - Write benchmarks and do optimizations; consider `func (l *VLogger) getBuffer() *buffer` from glog