sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/log/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 log
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"os"
    23  
    24  	"github.com/go-logr/logr"
    25  	"github.com/pkg/errors"
    26  )
    27  
    28  // logEntry defines the information that can be used for composing a log line.
    29  type logEntry struct {
    30  	// Prefix of the log line, composed of the hierarchy of log.WithName values.
    31  	Prefix string
    32  
    33  	// Level of the LogEntry.
    34  	Level int
    35  
    36  	// Values of the log line, composed of the concatenation of log.WithValues and KeyValue pairs passed to log.Info.
    37  	Values []interface{}
    38  }
    39  
    40  // Option is a configuration option supplied to NewLogger.
    41  type Option func(*logger)
    42  
    43  // WithThreshold implements a New Option that allows to set the threshold level for a new logger.
    44  // The logger will write only log messages with a level/V(x) equal or higher to the threshold.
    45  func WithThreshold(threshold *int) Option {
    46  	return func(c *logger) {
    47  		c.threshold = threshold
    48  	}
    49  }
    50  
    51  // NewLogger returns a new instance of the clusterctl.
    52  func NewLogger(options ...Option) logr.Logger {
    53  	l := &logger{}
    54  	for _, o := range options {
    55  		o(l)
    56  	}
    57  	return logr.New(l)
    58  }
    59  
    60  // logger defines a clusterctl friendly logr.Logger.
    61  type logger struct {
    62  	threshold *int
    63  	level     int
    64  	prefix    string
    65  	values    []interface{}
    66  }
    67  
    68  var _ logr.LogSink = &logger{}
    69  
    70  func (l *logger) Init(_ logr.RuntimeInfo) {
    71  }
    72  
    73  // Enabled tests whether this Logger is enabled.
    74  func (l *logger) Enabled(level int) bool {
    75  	if l.threshold == nil {
    76  		return true
    77  	}
    78  	return level <= *l.threshold
    79  }
    80  
    81  // Info logs a non-error message with the given key/value pairs as context.
    82  func (l *logger) Info(level int, msg string, kvs ...interface{}) {
    83  	if l.Enabled(level) {
    84  		values := copySlice(l.values)
    85  		values = append(values, kvs...)
    86  		values = append(values, "msg", msg)
    87  		l.write(values)
    88  	}
    89  }
    90  
    91  // Error logs an error message with the given key/value pairs as context.
    92  func (l *logger) Error(err error, msg string, kvs ...interface{}) {
    93  	values := copySlice(l.values)
    94  	values = append(values, kvs...)
    95  	values = append(values, "msg", msg, "error", err)
    96  	l.write(values)
    97  }
    98  
    99  // V returns an InfoLogger value for a specific verbosity level.
   100  func (l *logger) V(level int) logr.LogSink {
   101  	nl := l.clone()
   102  	nl.level = level
   103  	return nl
   104  }
   105  
   106  // WithName adds a new element to the logger's name.
   107  func (l *logger) WithName(name string) logr.LogSink {
   108  	nl := l.clone()
   109  	if l.prefix != "" {
   110  		nl.prefix = l.prefix + "/"
   111  	}
   112  	nl.prefix += name
   113  	return nl
   114  }
   115  
   116  // WithValues adds some key-value pairs of context to a logger.
   117  func (l *logger) WithValues(kvList ...interface{}) logr.LogSink {
   118  	nl := l.clone()
   119  	nl.values = append(nl.values, kvList...)
   120  	return nl
   121  }
   122  
   123  func (l *logger) write(values []interface{}) {
   124  	entry := logEntry{
   125  		Prefix: l.prefix,
   126  		Level:  l.level,
   127  		Values: values,
   128  	}
   129  	f, err := flatten(entry)
   130  	if err != nil {
   131  		panic(err)
   132  	}
   133  	fmt.Fprintln(os.Stderr, f)
   134  }
   135  
   136  func (l *logger) clone() *logger {
   137  	return &logger{
   138  		threshold: l.threshold,
   139  		level:     l.level,
   140  		prefix:    l.prefix,
   141  		values:    copySlice(l.values),
   142  	}
   143  }
   144  
   145  func copySlice(in []interface{}) []interface{} {
   146  	out := make([]interface{}, len(in))
   147  	copy(out, in)
   148  	return out
   149  }
   150  
   151  // flatten returns a human readable/machine parsable text representing the LogEntry.
   152  // Most notable difference with the klog implementation are:
   153  //   - The message is printed at the beginning of the line, without the Msg= variable name e.g.
   154  //     "Msg"="This is a message" --> This is a message
   155  //   - Variables name are not quoted, eg.
   156  //     This is a message "Var1"="value" --> This is a message Var1="value"
   157  //   - Variables are not sorted, thus allowing full control to the developer on the output.
   158  func flatten(entry logEntry) (string, error) {
   159  	var msgValue string
   160  	var errorValue error
   161  	if len(entry.Values)%2 == 1 {
   162  		return "", errors.New("log entry cannot have odd number off keyAndValues")
   163  	}
   164  
   165  	keys := make([]string, 0, len(entry.Values)/2)
   166  	values := make(map[string]interface{}, len(entry.Values)/2)
   167  	for i := 0; i < len(entry.Values); i += 2 {
   168  		k, ok := entry.Values[i].(string)
   169  		if !ok {
   170  			panic(fmt.Sprintf("key is not a string: %s", entry.Values[i]))
   171  		}
   172  		var v interface{}
   173  		if i+1 < len(entry.Values) {
   174  			v = entry.Values[i+1]
   175  		}
   176  		switch k {
   177  		case "msg":
   178  			msgValue, ok = v.(string)
   179  			if !ok {
   180  				panic(fmt.Sprintf("the msg value is not of type string: %s", v))
   181  			}
   182  		case "error":
   183  			errorValue, ok = v.(error)
   184  			if !ok {
   185  				panic(fmt.Sprintf("the error value is not of type error: %s", v))
   186  			}
   187  		default:
   188  			if _, ok := values[k]; !ok {
   189  				keys = append(keys, k)
   190  			}
   191  			values[k] = v
   192  		}
   193  	}
   194  	str := ""
   195  	if entry.Prefix != "" {
   196  		str += fmt.Sprintf("[%s] ", entry.Prefix)
   197  	}
   198  	str += msgValue
   199  	if errorValue != nil {
   200  		if msgValue != "" {
   201  			str += ": "
   202  		}
   203  		str += errorValue.Error()
   204  	}
   205  	for _, k := range keys {
   206  		prettyValue, err := pretty(values[k])
   207  		if err != nil {
   208  			return "", err
   209  		}
   210  		str += fmt.Sprintf(" %s=%s", k, prettyValue)
   211  	}
   212  	return str, nil
   213  }
   214  
   215  func pretty(value interface{}) (string, error) {
   216  	jb, err := json.Marshal(value)
   217  	if err != nil {
   218  		return "", errors.Wrapf(err, "Failed to marshal %s", value)
   219  	}
   220  	return string(jb), nil
   221  }