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 }