sigs.k8s.io/cluster-api-provider-azure@v1.14.3/internal/test/record/logger.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package record
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"io"
    23  	"sync"
    24  
    25  	"github.com/go-logr/logr"
    26  	"github.com/google/uuid"
    27  	"github.com/pkg/errors"
    28  )
    29  
    30  // LogEntry defines the information that can be used for composing a log line.
    31  type LogEntry struct {
    32  	// Prefix of the log line, composed of the hierarchy of log.WithName values.
    33  	Prefix string
    34  
    35  	// LogFunc of the log entry, e.g. "Info", "Error"
    36  	LogFunc string
    37  
    38  	// Level of the LogEntry.
    39  	Level int
    40  
    41  	// Values of the log line, composed of the concatenation of log.WithValues and KeyValue pairs passed to log.Info.
    42  	Values []interface{}
    43  }
    44  
    45  // Option is a configuration option supplied to NewLogger.
    46  type Option func(*Logger)
    47  
    48  // WithThreshold implements a New Option that allows to set the threshold level for a new logger.
    49  // The logger will write only log messages with a level/V(x) equal or higher to the threshold.
    50  func WithThreshold(threshold *int) Option {
    51  	return func(c *Logger) {
    52  		c.threshold = threshold
    53  	}
    54  }
    55  
    56  // WithWriter configures the logger to write to the io.Writer.
    57  func WithWriter(wr io.Writer) Option {
    58  	return func(c *Logger) {
    59  		c.writer = wr
    60  	}
    61  }
    62  
    63  // NewLogger returns a new instance of the clusterctl.
    64  func NewLogger(options ...Option) *Logger {
    65  	l := &Logger{
    66  		listeners: map[string]*Listener{},
    67  	}
    68  	for _, o := range options {
    69  		o(l)
    70  	}
    71  	return l
    72  }
    73  
    74  var _ logr.LogSink = (*Logger)(nil)
    75  
    76  type (
    77  	// Logger defines a test-friendly logr.Logger.
    78  	Logger struct {
    79  		threshold  *int
    80  		level      int
    81  		prefix     string
    82  		values     []interface{}
    83  		listenerMu sync.Mutex
    84  		listeners  map[string]*Listener
    85  		writer     io.Writer
    86  		root       *Logger
    87  		cloneMu    sync.Mutex
    88  		info       logr.RuntimeInfo
    89  	}
    90  
    91  	// Listener defines a listener for log entries.
    92  	Listener struct {
    93  		logger    *Logger
    94  		entriesMu sync.RWMutex
    95  		entries   []LogEntry
    96  	}
    97  )
    98  
    99  // NewListener returns a new listener with the specified logger.
   100  func NewListener(logger *Logger) *Listener {
   101  	return &Listener{
   102  		logger: logger,
   103  	}
   104  }
   105  
   106  // Listen adds this listener to its logger.
   107  func (li *Listener) Listen() func() {
   108  	return li.logger.addListener(li)
   109  }
   110  
   111  // GetEntries returns a copy of the list of log entries.
   112  func (li *Listener) GetEntries() []LogEntry {
   113  	li.entriesMu.RLock()
   114  	defer li.entriesMu.RUnlock()
   115  
   116  	return copyLogEntries(li.entries)
   117  }
   118  
   119  func (li *Listener) addEntry(entry LogEntry) {
   120  	li.entriesMu.Lock()
   121  	defer li.entriesMu.Unlock()
   122  
   123  	li.entries = append(li.entries, entry)
   124  }
   125  
   126  var _ logr.LogSink = &Logger{}
   127  
   128  func (l *Logger) addListener(listener *Listener) func() {
   129  	if l.root != nil {
   130  		return l.root.addListener(listener)
   131  	}
   132  
   133  	l.listenerMu.Lock()
   134  	defer l.listenerMu.Unlock()
   135  
   136  	id := uuid.New().String()
   137  	l.listeners[id] = listener
   138  	return func() {
   139  		l.removeListener(id)
   140  	}
   141  }
   142  
   143  func (l *Logger) removeListener(id string) {
   144  	l.listenerMu.Lock()
   145  	defer l.listenerMu.Unlock()
   146  
   147  	delete(l.listeners, id)
   148  }
   149  
   150  // Init initializes the logger from runtime information.
   151  func (l *Logger) Init(info logr.RuntimeInfo) {
   152  	l.info = info
   153  }
   154  
   155  // Enabled is always enabled.
   156  func (l *Logger) Enabled(v int) bool {
   157  	return true
   158  }
   159  
   160  // Info logs a non-error message with the given key/value pairs as context.
   161  func (l *Logger) Info(level int, msg string, kvs ...interface{}) {
   162  	values := copySlice(l.values)
   163  	values = append(values, kvs...)
   164  	values = append(values, "msg", msg)
   165  	l.write("Info", values)
   166  }
   167  
   168  // Error logs an error message with the given key/value pairs as context.
   169  func (l *Logger) Error(err error, msg string, kvs ...interface{}) {
   170  	values := copySlice(l.values)
   171  	values = append(values, kvs...)
   172  	values = append(values, "msg", msg, "error", err)
   173  	l.write("Error", values)
   174  }
   175  
   176  // V returns an Logger value for a specific verbosity level.
   177  func (l *Logger) V(level int) logr.LogSink {
   178  	nl := l.clone()
   179  	nl.level = level
   180  	return nl
   181  }
   182  
   183  // WithName adds a new element to the logger's name.
   184  func (l *Logger) WithName(name string) logr.LogSink {
   185  	nl := l.clone()
   186  	if len(l.prefix) > 0 {
   187  		nl.prefix = l.prefix + "/"
   188  	}
   189  	nl.prefix += name
   190  	return nl
   191  }
   192  
   193  // WithValues adds some key-value pairs of context to a logger.
   194  func (l *Logger) WithValues(kvList ...interface{}) logr.LogSink {
   195  	nl := l.clone()
   196  	nl.values = append(nl.values, kvList...)
   197  	return nl
   198  }
   199  
   200  func (l *Logger) write(logFunc string, values []interface{}) {
   201  	entry := LogEntry{
   202  		Prefix:  l.prefix,
   203  		LogFunc: logFunc,
   204  		Level:   l.level,
   205  		Values:  copySlice(values),
   206  	}
   207  	f, err := flatten(entry)
   208  	if err != nil {
   209  		panic(err)
   210  	}
   211  
   212  	l.writeToListeners(entry)
   213  
   214  	if l.writer != nil {
   215  		str := fmt.Sprintf("%s\n", f)
   216  		if _, err = l.writer.Write([]byte(str)); err != nil {
   217  			panic(err)
   218  		}
   219  		return
   220  	}
   221  
   222  	fmt.Println(f)
   223  }
   224  
   225  func (l *Logger) writeToListeners(entry LogEntry) {
   226  	if l.root != nil {
   227  		l.root.writeToListeners(entry)
   228  		return
   229  	}
   230  
   231  	l.listenerMu.Lock()
   232  	defer l.listenerMu.Unlock()
   233  
   234  	for _, listener := range l.listeners {
   235  		listener.addEntry(entry)
   236  	}
   237  }
   238  
   239  func (l *Logger) clone() *Logger {
   240  	l.cloneMu.Lock()
   241  	defer l.cloneMu.Unlock()
   242  
   243  	root := l.root
   244  	if root == nil {
   245  		root = l
   246  	}
   247  
   248  	return &Logger{
   249  		threshold: l.threshold,
   250  		level:     l.level,
   251  		prefix:    l.prefix,
   252  		values:    copySlice(l.values),
   253  		writer:    l.writer,
   254  		root:      root,
   255  	}
   256  }
   257  
   258  func copyLogEntries(in []LogEntry) []LogEntry {
   259  	out := make([]LogEntry, len(in))
   260  	copy(out, in)
   261  	return out
   262  }
   263  
   264  func copySlice(in []interface{}) []interface{} {
   265  	out := make([]interface{}, len(in))
   266  	copy(out, in)
   267  	return out
   268  }
   269  
   270  // flatten returns a human readable/machine parsable text representing the LogEntry.
   271  // Most notable difference with the klog implementation are:
   272  //   - The message is printed at the beginning of the line, without the Msg= variable name e.g.
   273  //     "Msg"="This is a message" --> This is a message
   274  //   - Variables name are not quoted, eg.
   275  //     This is a message "Var1"="value" --> This is a message Var1="value"
   276  //   - Variables are not sorted, thus allowing full control to the developer on the output.
   277  func flatten(entry LogEntry) (string, error) {
   278  	var msgValue string
   279  	var errorValue error
   280  	if len(entry.Values)%2 == 1 {
   281  		return "", errors.New("log entry cannot have odd number off keyAndValues")
   282  	}
   283  
   284  	keys := make([]string, 0, len(entry.Values)/2)
   285  	values := make(map[string]interface{}, len(entry.Values)/2)
   286  	for i := 0; i < len(entry.Values); i += 2 {
   287  		k, ok := entry.Values[i].(string)
   288  		if !ok {
   289  			panic(fmt.Sprintf("key is not a string: %s", entry.Values[i]))
   290  		}
   291  		var v interface{}
   292  		if i+1 < len(entry.Values) {
   293  			v = entry.Values[i+1]
   294  		}
   295  		switch k {
   296  		case "msg":
   297  			msgValue, ok = v.(string)
   298  			if !ok {
   299  				panic(fmt.Sprintf("the msg value is not of type string: %s", v))
   300  			}
   301  		case "error":
   302  			errorValue, ok = v.(error)
   303  			if !ok {
   304  				panic(fmt.Sprintf("the error value is not of type error: %s", v))
   305  			}
   306  		default:
   307  			if _, ok := values[k]; !ok {
   308  				keys = append(keys, k)
   309  			}
   310  			values[k] = v
   311  		}
   312  	}
   313  	str := ""
   314  	if entry.Prefix != "" {
   315  		str += fmt.Sprintf("[%s] ", entry.Prefix)
   316  	}
   317  	str += msgValue
   318  	if errorValue != nil {
   319  		if msgValue != "" {
   320  			str += ": "
   321  		}
   322  		str += errorValue.Error()
   323  	}
   324  	for _, k := range keys {
   325  		prettyValue, err := pretty(values[k])
   326  		if err != nil {
   327  			return "", err
   328  		}
   329  		str += fmt.Sprintf(" %s=%s", k, prettyValue)
   330  	}
   331  	return str, nil
   332  }
   333  
   334  func pretty(value interface{}) (string, error) {
   335  	jb, err := json.Marshal(value)
   336  	if err != nil {
   337  		return "", errors.Wrapf(err, "Failed to marshal %s", value)
   338  	}
   339  	return string(jb), nil
   340  }