diff --git a/go.mod b/go.mod index 3f23f97ff..cccb2ad27 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module k8s.io/git-sync require ( - github.com/go-logr/glogr v0.1.0 - github.com/go-logr/logr v0.1.0 // indirect + github.com/go-logr/glogr v1.0.0-rc1 + github.com/go-logr/logr v1.0.0-rc1 github.com/google/go-licenses v0.0.0-20210329231322-ce1d9163b77d github.com/prometheus/client_golang v0.9.2 github.com/spf13/pflag v1.0.5 diff --git a/go.sum b/go.sum index e60a99d2f..4908f6658 100644 --- a/go.sum +++ b/go.sum @@ -22,10 +22,10 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-logr/glogr v0.1.0 h1:5W02LkUIi+DaBwtWKYGxoX9gqVMo6i9ehwkhorjcP74= -github.com/go-logr/glogr v0.1.0/go.mod h1:GDQ2+z9PAAX7+qBhL3FzAL2Nf8dxyliu0ppgJIX7YhU= -github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/glogr v1.0.0-rc1 h1:+gtb5mFlgR1b1yxqRKbUXCzFLaNDcQ677K4gYw7Chxs= +github.com/go-logr/glogr v1.0.0-rc1/go.mod h1:u16L0yJa6zS81j91BhNLTr4zFt64A/cWTXBi8bg43R8= +github.com/go-logr/logr v1.0.0-rc1 h1:+ul9F74rBkPajeP8m4o3o0tiglmzNFsPnuhYyBCQ0Sc= +github.com/go-logr/logr v1.0.0-rc1/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= diff --git a/vendor/github.com/go-logr/glogr/README.md b/vendor/github.com/go-logr/glogr/README.md index 3829b03f6..b30bba674 100644 --- a/vendor/github.com/go-logr/glogr/README.md +++ b/vendor/github.com/go-logr/glogr/README.md @@ -1,8 +1,6 @@ # Minimal Go logging using glog -This package implements the [logr interface](https://github.com/thockin/logr) +This package implements the [logr interface](https://github.com/go-logr/logr) in terms of Google's [glog](https://godoc.org/github.com/golang/glog). This provides a relatively minimalist API to logging in Go, backed by a well-proven implementation. - -This is a BETA grade implementation. diff --git a/vendor/github.com/go-logr/glogr/glogr.go b/vendor/github.com/go-logr/glogr/glogr.go index f5ad48836..be5e6d4fa 100644 --- a/vendor/github.com/go-logr/glogr/glogr.go +++ b/vendor/github.com/go-logr/glogr/glogr.go @@ -1,13 +1,32 @@ -// Package glogr implements github.com/thockin/logr.Logger in terms of +/* +Copyright 2019 The logr Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package glogr implements github.com/go-logr/logr.Logger in terms of // github.com/golang/glog. package glogr import ( "bytes" - "encoding/json" "fmt" + "path/filepath" + "reflect" "runtime" - "sort" + "strconv" + "strings" + "sync/atomic" "github.com/go-logr/logr" "github.com/golang/glog" @@ -15,138 +34,327 @@ import ( // New returns a logr.Logger which is implemented by glog. func New() logr.Logger { - return glogger{ - level: 0, - prefix: "", - values: nil, - } + return NewWithOptions(Options{}) } -type glogger struct { - level int - prefix string - values []interface{} -} +// NewWithOptions returns a logr.Logger which is implemented by glog. +func NewWithOptions(opts Options) logr.Logger { + if opts.Depth < 0 { + opts.Depth = 0 + } -func (l glogger) clone() glogger { - return glogger{ - level: l.level, - prefix: l.prefix, - values: copySlice(l.values), + gl := &glogger{ + prefix: "", + values: nil, + depth: opts.Depth, + logCaller: opts.LogCaller, } + return logr.New(gl) +} + +// Options carries parameters which influence the way logs are generated. +type Options struct { + // Depth biases the assumed number of call frames to the "true" caller. + // This is useful when the calling code calls a function which then calls + // glogr (e.g. a logging shim to another API). Values less than zero will + // be treated as zero. + Depth int + + // LogCaller tells glogr to add a "caller" key to some or all log lines. + // The glog implementation always logs this information in its per-line + // header, whether this option is set or not. + LogCaller MessageClass + + // TODO: add an option to log the date/time } -func copySlice(in []interface{}) []interface{} { - out := make([]interface{}, len(in)) - copy(out, in) - return out +// MessageClass indicates which category or categories of messages to consider. +type MessageClass int + +const ( + None MessageClass = iota + All + Info + Error +) + +type glogger struct { + prefix string + values []interface{} + depth int + logCaller MessageClass } +var _ logr.LogSink = &glogger{} +var _ logr.CallDepthLogSink = &glogger{} + // Magic string for intermediate frames that we should ignore. const autogeneratedFrameName = "" +// Cached depth of this interface's log functions. +var framesAtomic int32 // atomic + // Discover how many frames we need to climb to find the caller. This approach // was suggested by Ian Lance Taylor of the Go team, so it *should* be safe // enough (famous last words). +// +// This assumes that all logging paths are the same depth from the caller, +// which should be a reasonable assumption since they are part of the same +// interface. func framesToCaller() int { + // Figuring out the current depth is somewhat expensive. Saving the value + // amortizes most of that runtime cost. + if atomic.LoadInt32(&framesAtomic) != 0 { + return int(framesAtomic) + } // 1 is the immediate caller. 3 should be too many. for i := 1; i < 3; i++ { _, file, _, _ := runtime.Caller(i + 1) // +1 for this function's frame if file != autogeneratedFrameName { + atomic.StoreInt32(&framesAtomic, int32(i)) return i } } return 1 // something went wrong, this is safe } -type kvPair struct { - key string - val interface{} -} - func flatten(kvList ...interface{}) string { - keys := make([]string, 0, len(kvList)) - vals := make(map[string]interface{}, len(kvList)) + if len(kvList)%2 != 0 { + kvList = append(kvList, "") + } + // Empirically bytes.Buffer is faster than strings.Builder for this. + buf := bytes.NewBuffer(make([]byte, 0, 1024)) for i := 0; i < len(kvList); i += 2 { k, ok := kvList[i].(string) if !ok { - panic(fmt.Sprintf("key is not a string: %s", pretty(kvList[i]))) - } - var v interface{} - if i+1 < len(kvList) { - v = kvList[i+1] + k = fmt.Sprintf("", i/2) } - keys = append(keys, k) - vals[k] = v - } - sort.Strings(keys) - buf := bytes.Buffer{} - for i, k := range keys { - v := vals[k] + v := kvList[i+1] + if i > 0 { buf.WriteRune(' ') } - buf.WriteString(pretty(k)) - buf.WriteString("=") + buf.WriteRune('"') + buf.WriteString(k) + buf.WriteRune('"') + buf.WriteRune('=') buf.WriteString(pretty(v)) } return buf.String() } func pretty(value interface{}) string { - jb, _ := json.Marshal(value) - return string(jb) + return prettyWithFlags(value, 0) } -func (l glogger) Info(msg string, kvList ...interface{}) { - if l.Enabled() { - lvlStr := flatten("level", l.level) - msgStr := flatten("msg", msg) - fixedStr := flatten(l.values...) - userStr := flatten(kvList...) - glog.InfoDepth(framesToCaller(), l.prefix, " ", lvlStr, " ", msgStr, " ", fixedStr, " ", userStr) +const ( + flagRawString = 0x1 +) + +// TODO: This is not fast. Most of the overhead goes here. +func prettyWithFlags(value interface{}, flags uint32) string { + // Handling the most common types without reflect is a small perf win. + switch v := value.(type) { + case bool: + return strconv.FormatBool(v) + case string: + if flags&flagRawString > 0 { + return v + } + // This is empirically faster than strings.Builder. + return `"` + v + `"` + case int: + return strconv.FormatInt(int64(v), 10) + case int8: + return strconv.FormatInt(int64(v), 10) + case int16: + return strconv.FormatInt(int64(v), 10) + case int32: + return strconv.FormatInt(int64(v), 10) + case int64: + return strconv.FormatInt(int64(v), 10) + case uint: + return strconv.FormatUint(uint64(v), 10) + case uint8: + return strconv.FormatUint(uint64(v), 10) + case uint16: + return strconv.FormatUint(uint64(v), 10) + case uint32: + return strconv.FormatUint(uint64(v), 10) + case uint64: + return strconv.FormatUint(v, 10) + case uintptr: + return strconv.FormatUint(uint64(v), 10) + case float32: + return strconv.FormatFloat(float64(v), 'f', -1, 32) + case float64: + return strconv.FormatFloat(v, 'f', -1, 64) + } + + buf := bytes.NewBuffer(make([]byte, 0, 256)) + t := reflect.TypeOf(value) + if t == nil { + return "null" } + v := reflect.ValueOf(value) + switch t.Kind() { + case reflect.Bool: + return strconv.FormatBool(v.Bool()) + case reflect.String: + if flags&flagRawString > 0 { + return v.String() + } + // This is empirically faster than strings.Builder. + return `"` + v.String() + `"` + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.FormatInt(int64(v.Int()), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return strconv.FormatUint(uint64(v.Uint()), 10) + case reflect.Float32: + return strconv.FormatFloat(float64(v.Float()), 'f', -1, 32) + case reflect.Float64: + return strconv.FormatFloat(v.Float(), 'f', -1, 64) + case reflect.Struct: + buf.WriteRune('{') + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + if f.PkgPath != "" { + // reflect says this field is only defined for non-exported fields. + continue + } + if i > 0 { + buf.WriteRune(',') + } + buf.WriteRune('"') + name := f.Name + if tag, found := f.Tag.Lookup("json"); found { + if comma := strings.Index(tag, ","); comma != -1 { + name = tag[:comma] + } else { + name = tag + } + } + buf.WriteString(name) + buf.WriteRune('"') + buf.WriteRune(':') + buf.WriteString(pretty(v.Field(i).Interface())) + } + buf.WriteRune('}') + return buf.String() + case reflect.Slice, reflect.Array: + buf.WriteRune('[') + for i := 0; i < v.Len(); i++ { + if i > 0 { + buf.WriteRune(',') + } + e := v.Index(i) + buf.WriteString(pretty(e.Interface())) + } + buf.WriteRune(']') + return buf.String() + case reflect.Map: + buf.WriteRune('{') + // This does not sort the map keys, for best perf. + it := v.MapRange() + i := 0 + for it.Next() { + if i > 0 { + buf.WriteRune(',') + } + // JSON only does string keys. + buf.WriteRune('"') + buf.WriteString(prettyWithFlags(it.Key().Interface(), flagRawString)) + buf.WriteRune('"') + buf.WriteRune(':') + buf.WriteString(pretty(it.Value().Interface())) + i++ + } + buf.WriteRune('}') + return buf.String() + case reflect.Ptr, reflect.Interface: + return pretty(v.Elem().Interface()) + } + return fmt.Sprintf(`""`, t.Kind().String()) +} + +type callerID struct { + File string `json:"file"` + Line int `json:"line"` } -func (l glogger) Enabled() bool { - return bool(glog.V(glog.Level(l.level))) +func (l glogger) caller() callerID { + // +1 for this frame. + _, file, line, ok := runtime.Caller(framesToCaller() + l.depth + 1) + if !ok { + return callerID{"", 0} + } + return callerID{filepath.Base(file), line} +} + +func (l *glogger) Init(info logr.RuntimeInfo) { + l.depth += info.CallDepth +} + +func (l glogger) Enabled(level int) bool { + return bool(glog.V(glog.Level(level))) +} + +func (l glogger) Info(level int, msg string, kvList ...interface{}) { + args := make([]interface{}, 0, 64) // using a constant here impacts perf + if l.logCaller == All || l.logCaller == Info { + args = append(args, "caller", l.caller()) + } + args = append(args, "level", level, "msg", msg) + args = append(args, l.values...) + args = append(args, kvList...) + argsStr := flatten(args...) + glog.InfoDepth(framesToCaller()+l.depth, l.prefix, argsStr) } func (l glogger) Error(err error, msg string, kvList ...interface{}) { - msgStr := flatten("msg", msg) + args := make([]interface{}, 0, 64) // using a constant here impacts perf + if l.logCaller == All || l.logCaller == Error { + args = append(args, "caller", l.caller()) + } + args = append(args, "msg", msg) var loggableErr interface{} if err != nil { loggableErr = err.Error() } - errStr := flatten("error", loggableErr) - fixedStr := flatten(l.values...) - userStr := flatten(kvList...) - glog.ErrorDepth(framesToCaller(), l.prefix, " ", msgStr, " ", errStr, " ", fixedStr, " ", userStr) -} - -func (l glogger) V(level int) logr.InfoLogger { - new := l.clone() - new.level = level - return new + args = append(args, "error", loggableErr) + args = append(args, l.values...) + args = append(args, kvList...) + argsStr := flatten(args...) + glog.ErrorDepth(framesToCaller()+l.depth, l.prefix, argsStr) } // WithName returns a new logr.Logger with the specified name appended. glogr // uses '/' characters to separate name elements. Callers should not pass '/' // in the provided name string, but this library does not actually enforce that. -func (l glogger) WithName(name string) logr.Logger { - new := l.clone() - if len(l.prefix) > 0 { - new.prefix = l.prefix + "/" +func (l *glogger) WithName(name string) logr.LogSink { + l2 := &glogger{} + *l2 = *l + if len(l2.prefix) > 0 { + l.prefix = l2.prefix + "/" } - new.prefix += name - return new + l2.prefix += name + return l2 } -func (l glogger) WithValues(kvList ...interface{}) logr.Logger { - new := l.clone() - new.values = append(new.values, kvList...) - return new +func (l *glogger) WithValues(kvList ...interface{}) logr.LogSink { + l2 := &glogger{} + *l2 = *l + // Three slice args forces a copy. + n := len(l.values) + l2.values = append(l2.values[:n:n], kvList...) + return l2 + } -var _ logr.Logger = glogger{} -var _ logr.InfoLogger = glogger{} +func (l *glogger) WithCallDepth(depth int) logr.LogSink { + l2 := &glogger{} + *l2 = *l + l2.depth += depth + return l2 + +} diff --git a/vendor/github.com/go-logr/glogr/go.mod b/vendor/github.com/go-logr/glogr/go.mod new file mode 100644 index 000000000..45ebc4356 --- /dev/null +++ b/vendor/github.com/go-logr/glogr/go.mod @@ -0,0 +1,8 @@ +module github.com/go-logr/glogr + +go 1.16 + +require ( + github.com/go-logr/logr v1.0.0-rc1 + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b +) diff --git a/vendor/github.com/go-logr/glogr/go.sum b/vendor/github.com/go-logr/glogr/go.sum new file mode 100644 index 000000000..6fb0e0ae5 --- /dev/null +++ b/vendor/github.com/go-logr/glogr/go.sum @@ -0,0 +1,6 @@ +github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.0.0-rc1 h1:+ul9F74rBkPajeP8m4o3o0tiglmzNFsPnuhYyBCQ0Sc= +github.com/go-logr/logr v1.0.0-rc1/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= diff --git a/vendor/github.com/go-logr/logr/CHANGELOG.md b/vendor/github.com/go-logr/logr/CHANGELOG.md new file mode 100644 index 000000000..c35696004 --- /dev/null +++ b/vendor/github.com/go-logr/logr/CHANGELOG.md @@ -0,0 +1,6 @@ +# CHANGELOG + +## v1.0.0-rc1 + +This is the first logged release. Major changes (including breaking changes) +have occurred since earlier tags. diff --git a/vendor/github.com/go-logr/logr/CONTRIBUTING.md b/vendor/github.com/go-logr/logr/CONTRIBUTING.md new file mode 100644 index 000000000..5d37e294c --- /dev/null +++ b/vendor/github.com/go-logr/logr/CONTRIBUTING.md @@ -0,0 +1,17 @@ +# Contributing + +Logr is open to pull-requests, provided they fit within the intended scope of +the project. Specifically, this library aims to be VERY small and minimalist, +with no external dependencies. + +## Compatibility + +This project intends to follow [semantic versioning](http://semver.org) and +is very strict about compatibility. Any proposed changes MUST follow those +rules. + +## Performance + +As a logging library, logr must be as light-weight as possible. Any proposed +code change must include results of running the [benchmark](./benchmark) +before and after the change. diff --git a/vendor/github.com/go-logr/logr/README.md b/vendor/github.com/go-logr/logr/README.md index 26296d024..e6339e694 100644 --- a/vendor/github.com/go-logr/logr/README.md +++ b/vendor/github.com/go-logr/logr/README.md @@ -1,23 +1,90 @@ -# A more minimal logging API for Go +# A minimal logging API for Go -Before you consider this package, please read [this blog post by the inimitable -Dave Cheney](http://dave.cheney.net/2015/11/05/lets-talk-about-logging). I -really appreciate what he has to say, and it largely aligns with my own -experiences. Too many choices of levels means inconsistent logs. +logr offers an(other) opinion on how Go programs and libraries can do logging +without becoming coupled to a particular logging implementation, This is not +an implementation of logging - it is an API. In fact it is two APIs with twop +different sets of users. -This package offers a purely abstract interface, based on these ideas but with -a few twists. Code can depend on just this interface and have the actual -logging implementation be injected from callers. Ideally only `main()` knows -what logging implementation is being used. +The `Logger` type is intended for application and library authors. It provides +a relatively small API which can be used everywhere you want to emit logs. It +defers the actual act of writing logs (to files, to stdout, or whatever) to the +`LogSink` interface. -# Differences from Dave's ideas +The `LogSink` interface is intended for logging library implementors. It is a +pure interface which can be implemented by to provide the actual logging +functionality. + +This decoupling allows application and library developers to write code in +terms of `logr.Logger` (which has very low dependency fan-out) while the +implementation of logging is managed "up stack" (e.g. in or near `main()`). +Application developers can then switch out implementations as necessary. + +Many people assert that libraries should not be logging, and as such efforts +like this are pointless. Those people are welcome to convince the authors of +the tens-of-thousands of libraries that *DO* write logs that they are all +wrong. In the meantime, logr takes a more practical approach. + +## Typical usage + +Somewhere, early in an application's life, it will make a decision about which +logging library (implementation) it actually wants to use. Something like: + +``` + func main() { + // ... other setup code ... + + // Create the "root" logger. We have chosen the "logimpl" implementation, + // which takes some initial parameters and returns a logr.Logger. + logger := logimpl.New(param1, param2) + + // ... other setup code ... +``` + +Most apps will call into other libraries, create stuctures to govern the flow, +etc. The `logr.Logger` object can be passed to these other libraries, stored +in structs, or even used as a package-global variable, if needed. For example: + +``` + app := createTheAppObject(logger) + app.Run() +``` + +Outside of this early setup, no other packages need to know about the choice of +implementation. They write logs in terms of the `logr.Logger` that they +received: + +``` + type appObject struct { + // ... other fields ... + logger logr.Logr + // ... other fields ... + } + + func (app *appObject) Run() { + app.logger.Info("starting up", "timestamp", time.Now()) + + // ... app code ... +``` + +## Background + +If the Go standard library had defined an interface for logging, this project +probably would not be needed. Alas, here we are. + +### Inspiration + +Before you consider this package, please read [this blog post by the +inimitable Dave Cheney][warning-makes-no-sense]. We really appreciate what +he has to say, and it largely aligns with our own experiences. + +### Differences from Dave's ideas The main differences are: 1) Dave basically proposes doing away with the notion of a logging API in favor -of `fmt.Printf()`. I disagree, especially when you consider things like output -locations, timestamps, file and line decorations, and structured logging. I -restrict the API to just 2 types of logs: info and error. +of `fmt.Printf()`. We disagree, especially when you consider things like output +locations, timestamps, file and line decorations, and structured logging. This +package restricts the logging API to just 2 types of logs: info and error. Info logs are things you want to tell the user which are not errors. Error logs are, well, errors. If your code receives an `error` from a subordinate @@ -31,6 +98,168 @@ may feel very similar, but the primary difference is the lack of semantics. Because verbosity is a numerical value, it's safe to assume that an app running with higher verbosity means more (and less important) logs will be generated. -This is a BETA grade API. I have implemented it for -[glog](https://godoc.org/github.com/golang/glog). Until there is a significant -2nd implementation, I don't really know how it will change. +## Implementations (non-exhaustive) + +There are implementations for the following logging libraries: + +- **a function**: [funcr](https://github.com/go-logr/logr/funcr) +- **github.com/google/glog**: [glogr](https://github.com/go-logr/glogr) +- **k8s.io/klog**: [klogr](https://git.k8s.io/klog/klogr) +- **go.uber.org/zap**: [zapr](https://github.com/go-logr/zapr) +- **log** (the Go standard library logger): [stdr](https://github.com/go-logr/stdr) +- **github.com/sirupsen/logrus**: [logrusr](https://github.com/bombsimon/logrusr) +- **github.com/wojas/genericr**: [genericr](https://github.com/wojas/genericr) (makes it easy to implement your own backend) +- **logfmt** (Heroku style [logging](https://www.brandur.org/logfmt)): [logfmtr](https://github.com/iand/logfmtr) + +## FAQ + +### Conceptual + +#### Why structured logging? + +- **Structured logs are more easily queriable**: Since you've got + key-value pairs, it's much easier to query your structured logs for + particular values by filtering on the contents of a particular key -- + think searching request logs for error codes, Kubernetes reconcilers for + the name and namespace of the reconciled object, etc + +- **Structured logging makes it easier to have cross-referencable logs**: + Similarly to searchability, if you maintain conventions around your + keys, it becomes easy to gather all log lines related to a particular + concept. + +- **Structured logs allow better dimensions of filtering**: if you have + structure to your logs, you've got more precise control over how much + information is logged -- you might choose in a particular configuration + to log certain keys but not others, only log lines where a certain key + matches a certain value, etc, instead of just having v-levels and names + to key off of. + +- **Structured logs better represent structured data**: sometimes, the + data that you want to log is inherently structured (think tuple-link + objects). Structured logs allow you to preserve that structure when + outputting. + +#### Why V-levels? + +**V-levels give operators an easy way to control the chattiness of log +operations**. V-levels provide a way for a given package to distinguish +the relative importance or verbosity of a given log message. Then, if +a particular logger or package is logging too many messages, the user +of the package can simply change the v-levels for that library. + +#### Why not named levels, like Info/Warning/Error? + +Read [Dave Cheney's post][warning-makes-no-sense]. Then read [Differences +from Dave's ideas](#differences-from-daves-ideas). + +#### Why not allow format strings, too? + +**Format strings negate many of the benefits of structured logs**: + +- They're not easily searchable without resorting to fuzzy searching, + regular expressions, etc + +- They don't store structured data well, since contents are flattened into + a string + +- They're not cross-referenceable + +- They don't compress easily, since the message is not constant + +(unless you turn positional parameters into key-value pairs with numerical +keys, at which point you've gotten key-value logging with meaningless +keys) + +### Practical + +#### Why key-value pairs, and not a map? + +Key-value pairs are *much* easier to optimize, especially around +allocations. Zap (a structured logger that inspired logr's interface) has +[performance measurements](https://github.com/uber-go/zap#performance) +that show this quite nicely. + +While the interface ends up being a little less obvious, you get +potentially better performance, plus avoid making users type +`map[string]string{}` every time they want to log. + +#### What if my V-levels differ between libraries? + +That's fine. Control your V-levels on a per-logger basis, and use the +`WithName` method to pass different loggers to different libraries. + +Generally, you should take care to ensure that you have relatively +consistent V-levels within a given logger, however, as this makes deciding +on what verbosity of logs to request easier. + +#### But I really want to use a format string! + +That's not actually a question. Assuming your question is "how do +I convert my mental model of logging with format strings to logging with +constant messages": + +1. figure out what the error actually is, as you'd write in a TL;DR style, + and use that as a message + +2. For every place you'd write a format specifier, look to the word before + it, and add that as a key value pair + +For instance, consider the following examples (all taken from spots in the +Kubernetes codebase): + +- `klog.V(4).Infof("Client is returning errors: code %v, error %v", + responseCode, err)` becomes `logger.Error(err, "client returned an + error", "code", responseCode)` + +- `klog.V(4).Infof("Got a Retry-After %ds response for attempt %d to %v", + seconds, retries, url)` becomes `logger.V(4).Info("got a retry-after + response when requesting url", "attempt", retries, "after + seconds", seconds, "url", url)` + +If you *really* must use a format string, use it in a key's value, and +call `fmt.Sprintf` yourself. For instance: `log.Printf("unable to +reflect over type %T")` becomes `logger.Info("unable to reflect over +type", "type", fmt.Sprintf("%T"))`. In general though, the cases where +this is necessary should be few and far between. + +#### How do I choose my V-levels? + +This is basically the only hard constraint: increase V-levels to denote +more verbose or more debug-y logs. + +Otherwise, you can start out with `0` as "you always want to see this", +`1` as "common logging that you might *possibly* want to turn off", and +`10` as "I would like to performance-test your log collection stack". + +Then gradually choose levels in between as you need them, working your way +down from 10 (for debug and trace style logs) and up from 1 (for chattier +info-type logs). + +#### How do I choose my keys? + +- make your keys human-readable +- constant keys are generally a good idea +- be consistent across your codebase +- keys should naturally match parts of the message string + +While key names are mostly unrestricted (and spaces are acceptable), +it's generally a good idea to stick to printable ascii characters, or at +least match the general character set of your log lines. + +#### Why should keys be constant values? + +The point of structured logging is to make later log processing easier. Your +keys are, effectively, the schema of each log message. If you use different +keys across instances of the same log-line, you will make your structured logs +much harder to use. `Sprintf()` is for values, not for keys! + +#### Why is this not a pure interface? + +The Logger type is implemented as a struct in order to allow the Go compiler to +optimize things like high-V `Info` logs that are not triggered. Not all of +these implementations are implemented yet, but this structure was suggested as +a way to ensure they *can* be implemented. All of the real work is behind the +`LogSink` interface. + +[warning-makes-no-sense]: http://dave.cheney.net/2015/11/05/lets-talk-about-logging diff --git a/vendor/github.com/go-logr/logr/discard.go b/vendor/github.com/go-logr/logr/discard.go new file mode 100644 index 000000000..9d92a38f1 --- /dev/null +++ b/vendor/github.com/go-logr/logr/discard.go @@ -0,0 +1,54 @@ +/* +Copyright 2020 The logr Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package logr + +// Discard returns a Logger that discards all messages logged to it. It can be +// used whenever the caller is not interested in the logs. Logger instances +// produced by this function always compare as equal. +func Discard() Logger { + return Logger{ + level: 0, + sink: discardLogSink{}, + } +} + +// discardLogSink is a LogSink that discards all messages. +type discardLogSink struct{} + +// Verify that it actually implements the interface +var _ LogSink = discardLogSink{} + +func (l discardLogSink) Init(RuntimeInfo) { +} + +func (l discardLogSink) Enabled(int) bool { + return false +} + +func (l discardLogSink) Info(int, string, ...interface{}) { +} + +func (l discardLogSink) Error(error, string, ...interface{}) { +} + +func (l discardLogSink) WithValues(...interface{}) LogSink { + return l +} + +func (l discardLogSink) WithName(string) LogSink { + return l +} diff --git a/vendor/github.com/go-logr/logr/go.mod b/vendor/github.com/go-logr/logr/go.mod new file mode 100644 index 000000000..591884e91 --- /dev/null +++ b/vendor/github.com/go-logr/logr/go.mod @@ -0,0 +1,3 @@ +module github.com/go-logr/logr + +go 1.14 diff --git a/vendor/github.com/go-logr/logr/logr.go b/vendor/github.com/go-logr/logr/logr.go index ad72e7886..a2303e692 100644 --- a/vendor/github.com/go-logr/logr/logr.go +++ b/vendor/github.com/go-logr/logr/logr.go @@ -1,151 +1,374 @@ -// Package logr defines abstract interfaces for logging. Packages can depend on -// these interfaces and callers can implement logging in whatever way is -// appropriate. -// +/* +Copyright 2019 The logr Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + // This design derives from Dave Cheney's blog: // http://dave.cheney.net/2015/11/05/lets-talk-about-logging + +// Package logr defines a general-purpose logging API and abstract interfaces +// to back that API. Packages in the Go ecosystem can depend on this package, +// while callers can implement logging with whatever backend is appropriate. // -// This is a BETA grade API. Until there is a significant 2nd implementation, -// I don't really know how it will change. +// Usage +// ----- // -// The logging specifically makes it non-trivial to use format strings, to encourage -// attaching structured information instead of unstructured format strings. +// Logging is done using a Logger instance. Logger is a concrete type with +// methods, which defers the actual logging to a LogSink interface. The main +// methods of Logger are Info() and Error(). Arguments to Info() and Error() +// are key/value pairs rather than printf-style formatted strings, emphasizing +// "structured logging". // -// Usage +// With Go's standard log package, we might write: +// log.Printf("setting target value %s", targetValue) // -// Logging is done using a Logger. Loggers can have name prefixes and named values -// attached, so that all log messages logged with that Logger have some base context -// associated. +// With logr's structured logging, we'd write: +// logger.Info("setting target", "value", targetValue) // -// The term "key" is used to refer to the name associated with a particular value, to -// disambiguate it from the general Logger name. +// Errors are much the same. Instead of: +// log.Printf("failed to open the pod bay door for user %s: %v", user, err) // -// For instance, suppose we're trying to reconcile the state of an object, and we want -// to log that we've made some decision. +// We'd write: +// logger.Error(err, "failed to open the pod bay door", "user", user) // -// With the traditional log package, we might write -// log.Printf( -// "decided to set field foo to value %q for object %s/%s", -// targetValue, object.Namespace, object.Name) +// Info() and Error() are very similar, but they are separate methods so that +// LogSink implementations can choose to do things like attach additional +// information (such as stack traces) on calls to Error(). +// +// Verbosity +// --------- +// +// Often we want to log information only when the application in "verbose +// mode". To write log-lines that are more verbose, Logger has a V() method. +// The higher the V-level of a log-line, the less critical it is considered. +// Log-lines with V-levels that are not enabled (as per the LogSink) will not +// be written. Level V(0) is the default, and logger.V(0).Info() has the same +// meaning as logger.Info(). Negative V-levels have the same meaning as V(0). +// +// Where we might have written: +// if flVerbose >= 2 { +// log.Printf("an unusual thing happened") +// } // -// With logr's structured logging, we'd write -// // elsewhere in the file, set up the logger to log with the prefix of "reconcilers", -// // and the named value target-type=Foo, for extra context. -// log := mainLogger.WithName("reconcilers").WithValues("target-type", "Foo") +// We can write: +// logger.V(2).Info("an unusual thing happened") // -// // later on... -// log.Info("setting field foo on object", "value", targetValue, "object", object) +// Logger Names +// ------------ // -// Depending on our logging implementation, we could then make logging decisions based on field values -// (like only logging such events for objects in a certain namespace), or copy the structured -// information into a structured log store. +// Logger instances can have name strings so that all messages logged through +// that instance have additional context. For example, you might want to add +// a subsystem name: // -// For logging errors, Logger has a method called Error. Suppose we wanted to log an -// error while reconciling. With the traditional log package, we might write -// log.Errorf("unable to reconcile object %s/%s: %v", object.Namespace, object.Name, err) +// logger.WithName("compactor").Info("started", "time", time.Now()) // -// With logr, we'd instead write -// // assuming the above setup for log -// log.Error(err, "unable to reconcile object", "object", object) +// The WithName() method returns a new Logger, which can be passed to +// constructors or other functions for further use. Repeated use of WithName() +// will accumulate name "segments". These name segments will be joined in some +// way by the LogSink implementation. It is strongly recommended that name +// segments contain simple identifiers (letters, digits, and hyphen), and do +// not contain characters that could muddle the log output or confuse the +// joining operation (e.g. whitespace, commas, periods, slashes, brackets, +// quotes, etc). // -// This functions similarly to: -// log.Info("unable to reconcile object", "error", err, "object", object) +// Saved Values +// ------------ // -// However, it ensures that a standard key for the error value ("error") is used across all -// error logging. Furthermore, certain implementations may choose to attach additional -// information (such as stack traces) on calls to Error, so it's preferred to use Error -// to log errors. +// Logger instances can store any number of key/value pairs, which will be +// logged alongside all messages logged through that instance. For example, +// you might want to create a Logger instance per managed object: // -// Parts of a log line +// With the standard log package, we might write: +// log.Printf("decided to set field foo to value %q for object %s/%s", +// targetValue, object.Namespace, object.Name) +// +// With logr's we'd write: +// // Elsewhere: set up the logger to log the object name. +// obj.logger = mainLogger.WithValues( +// "name", obj.name, "namespace", obj.namespace) // -// Each log message from a Logger has four types of context: -// logger name, log verbosity, log message, and the named values. +// // later on... +// obj.logger.Info("setting foo", "value", targetValue) // -// The Logger name constists of a series of name "segments" added by successive calls to WithName. -// These name segments will be joined in some way by the underlying implementation. It is strongly -// reccomended that name segements contain simple identifiers (letters, digits, and hyphen), and do -// not contain characters that could muddle the log output or confuse the joining operation (e.g. -// whitespace, commas, periods, slashes, brackets, quotes, etc). +// Best Practices +// -------------- // -// Log verbosity represents how little a log matters. Level zero, the default, matters most. -// Increasing levels matter less and less. Try to avoid lots of different verbosity levels, -// and instead provide useful keys, logger names, and log messages for users to filter on. -// It's illegal to pass a log level below zero. +// Logger has very few hard rules, with the goal that LogSink implementations +// might have a lot of freedom to differentiate. There are, however, some +// things to consider. // -// The log message consists of a constant message attached to the the log line. This -// should generally be a simple description of what's occuring, and should never be a format string. +// The log message consists of a constant message attached to the log line. +// This should generally be a simple description of what's occurring, and should +// never be a format string. Variable information can then be attached using +// named values. // -// Variable information can then be attached using named values (key/value pairs). Keys are arbitrary -// strings, while values may be any Go value. +// Keys are arbitrary strings, but should generally be constant values. Values +// may be any Go value, but how the value is formatted is determined by the +// LogSink implementation. // // Key Naming Conventions +// ---------------------- +// +// Keys are not strictly required to conform to any specification or regex, but +// it is recommended that they: +// * be human-readable and meaningful (not auto-generated or simple ordinals) +// * be constant (not dependent on input data) +// * contain only printable characters +// * not contain whitespace or punctuation +// +// These guidelines help ensure that log data is processed properly regardless +// of the log implementation. For example, log implementations will try to +// output JSON data or will store data for later database (e.g. SQL) queries. // -// While users are generally free to use key names of their choice, it's generally best to avoid -// using the following keys, as they're frequently used by implementations: +// While users are generally free to use key names of their choice, it's +// generally best to avoid using the following keys, as they're frequently used +// by implementations: // -// - `"error"`: the underlying error value in the `Error` method. -// - `"stacktrace"`: the stack trace associated with a particular log line or error -// (often from the `Error` message). -// - `"caller"`: the calling information (file/line) of a particular log line. -// - `"msg"`: the log message. -// - `"level"`: the log level. -// - `"ts"`: the timestamp for a log line. +// * `"caller"`: the calling information (file/line) of a particular log line. +// * `"error"`: the underlying error value in the `Error` method. +// * `"level"`: the log level. +// * `"logger"`: the name of the associated logger. +// * `"msg"`: the log message. +// * `"stacktrace"`: the stack trace associated with a particular log line or +// error (often from the `Error` message). +// * `"ts"`: the timestamp for a log line. // -// Implementations are encouraged to make use of these keys to represent the above -// concepts, when neccessary (for example, in a pure-JSON output form, it would be -// necessary to represent at least message and timestamp as ordinary named values). +// Implementations are encouraged to make use of these keys to represent the +// above concepts, when necessary (for example, in a pure-JSON output form, it +// would be necessary to represent at least message and timestamp as ordinary +// named values). +// +// Break Glass +// ----------- +// +// Implementations may choose to give callers access to the underlying +// logging implementation. The recommended pattern for this is: +// // Underlier exposes access to the underlying logging implementation. +// // Since callers only have a logr.Logger, they have to know which +// // implementation is in use, so this interface is less of an abstraction +// // and more of way to test type conversion. +// type Underlier interface { +// GetUnderlying() +// } package logr -// TODO: consider adding back in format strings if they're really needed -// TODO: consider other bits of zap/zapcore functionality like ObjectMarshaller (for arbitrary objects) -// TODO: consider other bits of glog functionality like Flush, InfoDepth, OutputStats +import ( + "context" +) + +// New returns a new Logger instance. This is primarily used by libraries +// implementing LogSink, rather than end users. +func New(sink LogSink) Logger { + logger := Logger{ + sink: sink, + } + if withCallDepth, ok := sink.(CallDepthLogSink); ok { + logger.withCallDepth = withCallDepth + } + sink.Init(runtimeInfo) + return logger +} + +// Logger is an interface to an abstract logging implementation. This is a +// concrete type for performance reasons, but all the real work is passed on +// to a LogSink. Implementations of LogSink should provide their own +// constructors that return Logger, not LogSink. +type Logger struct { + level int + sink LogSink + withCallDepth CallDepthLogSink +} + +// Enabled tests whether this Logger is enabled. For example, commandline +// flags might be used to set the logging verbosity and disable some info +// logs. +func (l Logger) Enabled() bool { + return l.sink.Enabled(l.level) +} + +// Info logs a non-error message with the given key/value pairs as context. +// +// The msg argument should be used to add some constant description to +// the log line. The key/value pairs can then be used to add additional +// variable information. The key/value pairs must alternate string +// keys and arbitrary values. +func (l Logger) Info(msg string, keysAndValues ...interface{}) { + if l.Enabled() { + l.sink.Info(l.level, msg, keysAndValues...) + } +} + +// Error logs an error, with the given message and key/value pairs as context. +// It functions similarly to Info, but may have unique behavior, and should be +// preferred for logging errors (see the package documentations for more +// information). +// +// The msg argument should be used to add context to any underlying error, +// while the err argument should be used to attach the actual error that +// triggered this log line, if present. +func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) { + l.sink.Error(err, msg, keysAndValues...) +} + +// V returns a new Logger instance for a specific verbosity level, relative to +// this Logger. In other words, V-levels are additive. A higher verbosity +// level means a log message is less important. Negative V-levels are treated +// as 0. +func (l Logger) V(level int) Logger { + if level < 0 { + level = 0 + } + l.level += level + return l +} + +// WithValues returns a new Logger instance with additional key/value pairs. +// See Info for documentation on how key/value pairs work. +func (l Logger) WithValues(keysAndValues ...interface{}) Logger { + l.sink = l.sink.WithValues(keysAndValues...) + return l +} + +// WithName returns a new Logger instance with the specified name element added +// to the Logger's name. Successive calls with WithName append additional +// suffixes to the Logger's name. It's strongly recommended that name segments +// contain only letters, digits, and hyphens (see the package documentation for +// more information). +func (l Logger) WithName(name string) Logger { + l.sink = l.sink.WithName(name) + return l +} + +// WithCallDepth returns a Logger instance that offsets the call stack by the +// specified number of frames when logging call site information, if possible. +// This is useful for users who have helper functions between the "real" call +// site and the actual calls to Logger methods. If depth is 0 the attribution +// should be to the direct caller of this function. If depth is 1 the +// attribution should skip 1 call frame, and so on. Successive calls to this +// are additive. +// +// If the underlying log implementation supports a WithCallDepth(int) method, +// it will be called and the result returned. If the implementation does not +// support CallDepthLogSink, the original Logger will be returned. +func (l Logger) WithCallDepth(depth int) Logger { + if l.withCallDepth == nil { + return l + } + l.sink = l.withCallDepth.WithCallDepth(depth) + return l +} + +// contextKey is how we find Loggers in a context.Context. +type contextKey struct{} + +// FromContext returns a Logger from ctx or an error if no Logger is found. +func FromContext(ctx context.Context) (Logger, error) { + if v, ok := ctx.Value(contextKey{}).(Logger); ok { + return v, nil + } + + return Logger{}, notFoundError{} +} + +// notFoundError exists to carry an IsNotFound method. +type notFoundError struct{} + +func (notFoundError) Error() string { + return "no logr.Logger was present" +} + +func (notFoundError) IsNotFound() bool { + return true +} + +// FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this +// returns a Logger that discards all log messages. +func FromContextOrDiscard(ctx context.Context) Logger { + if v, ok := ctx.Value(contextKey{}).(Logger); ok { + return v + } + + return Discard() +} + +// NewContext returns a new Context, derived from ctx, which carries the +// provided Logger. +func NewContext(ctx context.Context, logger Logger) context.Context { + return context.WithValue(ctx, contextKey{}, logger) +} + +// RuntimeInfo holds information that the logr "core" library knows which +// LogSinks might want to know. +type RuntimeInfo struct { + // CallDepth is the number of call frames the logr library adds between the + // end-user and the LogSink. LogSink implementations which choose to print + // the original logging site (e.g. file & line) should climb this many + // additional frames to find it. + CallDepth int +} + +// runtimeInfo is a static global. It must not be changed at run time. +var runtimeInfo = RuntimeInfo{ + CallDepth: 1, +} + +// LogSink represents a logging implementation. End-users will generally not +// interact with this type. +type LogSink interface { + // Init receives optional information about the logr library for LogSink + // implementations that need it. + Init(info RuntimeInfo) + + // Enabled tests whether this LogSink is enabled at the specified V-level. + // For example, commandline flags might be used to set the logging + // verbosity and disable some info logs. + Enabled(level int) bool -// InfoLogger represents the ability to log non-error messages, at a particular verbosity. -type InfoLogger interface { // Info logs a non-error message with the given key/value pairs as context. - // - // The msg argument should be used to add some constant description to - // the log line. The key/value pairs can then be used to add additional - // variable information. The key/value pairs should alternate string - // keys and arbitrary values. - Info(msg string, keysAndValues ...interface{}) - - // Enabled tests whether this InfoLogger is enabled. For example, - // commandline flags might be used to set the logging verbosity and disable - // some info logs. - Enabled() bool -} - -// Logger represents the ability to log messages, both errors and not. -type Logger interface { - // All Loggers implement InfoLogger. Calling InfoLogger methods directly on - // a Logger value is equivalent to calling them on a V(0) InfoLogger. For - // example, logger.Info() produces the same result as logger.V(0).Info. - InfoLogger - - // Error logs an error, with the given message and key/value pairs as context. - // It functions similarly to calling Info with the "error" named value, but may - // have unique behavior, and should be preferred for logging errors (see the - // package documentations for more information). - // - // The msg field should be used to add context to any underlying error, - // while the err field should be used to attach the actual error that - // triggered this log line, if present. + // The level argument is provided for optional logging. This method will + // only be called when Enabled(level) is true. See Logger.Info for more + // details. + Info(level int, msg string, keysAndValues ...interface{}) + + // Error logs an error, with the given message and key/value pairs as + // context. See Logger.Error for more details. Error(err error, msg string, keysAndValues ...interface{}) - // V returns an InfoLogger value for a specific verbosity level. A higher - // verbosity level means a log message is less important. It's illegal to - // pass a log level less than zero. - V(level int) InfoLogger - - // WithValues adds some key-value pairs of context to a logger. - // See Info for documentation on how key/value pairs work. - WithValues(keysAndValues ...interface{}) Logger - - // WithName adds a new element to the logger's name. - // Successive calls with WithName continue to append - // suffixes to the logger's name. It's strongly reccomended - // that name segments contain only letters, digits, and hyphens - // (see the package documentation for more information). - WithName(name string) Logger + // WithValues returns a new LogSink with additional key/value pairs. See + // Logger.WithValues for more details. + WithValues(keysAndValues ...interface{}) LogSink + + // WithName returns a new LogSink with the specified name appended. See + // Logger.WithName for more details. + WithName(name string) LogSink +} + +// CallDepthLogSink represents a Logger that knows how to climb the call stack +// to identify the original call site and can offset the depth by a specified +// number of frames. This is useful for users who have helper functions +// between the "real" call site and the actual calls to Logger methods. +// Implementations that log information about the call site (such as file, +// function, or line) would otherwise log information about the intermediate +// helper functions. +// +// This is an optional interface and implementations are not required to +// support it. +type CallDepthLogSink interface { + // WithCallDepth returns a Logger that will offset the call stack by the + // specified number of frames when logging call site information. If depth + // is 0 the attribution should be to the direct caller of this method. If + // depth is 1 the attribution should skip 1 call frame, and so on. + // Successive calls to this are additive. + WithCallDepth(depth int) LogSink } diff --git a/vendor/modules.txt b/vendor/modules.txt index 063645461..a98ba2726 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -7,10 +7,10 @@ github.com/emirpasic/gods/lists/arraylist github.com/emirpasic/gods/trees github.com/emirpasic/gods/trees/binaryheap github.com/emirpasic/gods/utils -# github.com/go-logr/glogr v0.1.0 +# github.com/go-logr/glogr v1.0.0-rc1 ## explicit github.com/go-logr/glogr -# github.com/go-logr/logr v0.1.0 +# github.com/go-logr/logr v1.0.0-rc1 ## explicit github.com/go-logr/logr # github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b