aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/go-logr
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/go-logr')
-rw-r--r--vendor/github.com/go-logr/logr/.golangci.yaml29
-rw-r--r--vendor/github.com/go-logr/logr/CHANGELOG.md6
-rw-r--r--vendor/github.com/go-logr/logr/CONTRIBUTING.md17
-rw-r--r--vendor/github.com/go-logr/logr/README.md209
-rw-r--r--vendor/github.com/go-logr/logr/discard.go35
-rw-r--r--vendor/github.com/go-logr/logr/go.mod2
-rw-r--r--vendor/github.com/go-logr/logr/logr.go533
7 files changed, 608 insertions, 223 deletions
diff --git a/vendor/github.com/go-logr/logr/.golangci.yaml b/vendor/github.com/go-logr/logr/.golangci.yaml
new file mode 100644
index 000000000..94ff801df
--- /dev/null
+++ b/vendor/github.com/go-logr/logr/.golangci.yaml
@@ -0,0 +1,29 @@
+run:
+ timeout: 1m
+ tests: true
+
+linters:
+ disable-all: true
+ enable:
+ - asciicheck
+ - deadcode
+ - errcheck
+ - forcetypeassert
+ - gocritic
+ - gofmt
+ - goimports
+ - gosimple
+ - govet
+ - ineffassign
+ - misspell
+ - revive
+ - staticcheck
+ - structcheck
+ - typecheck
+ - unused
+ - varcheck
+
+issues:
+ exclude-use-default: false
+ max-issues-per-linter: 0
+ max-same-issues: 10
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 e9b5520a1..ad825f5f0 100644
--- a/vendor/github.com/go-logr/logr/README.md
+++ b/vendor/github.com/go-logr/logr/README.md
@@ -1,112 +1,182 @@
-# 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][warning-makes-no-sense]. I really appreciate what
-he has to say, and it largely aligns with my own experiences. Too many
-choices of levels means inconsistent logs.
+[![Go Reference](https://pkg.go.dev/badge/github.com/go-logr/logr.svg)](https://pkg.go.dev/github.com/go-logr/logr)
+
+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 two
+different sets of users.
+
+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.
+
+The `LogSink` interface is intended for logging library implementers. It is a
+pure interface which can be implemented by logging frameworks 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 structures 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:
-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.
+```
+ type appObject struct {
+ // ... other fields ...
+ logger logr.Logger
+ // ... other fields ...
+ }
-# Differences from Dave's ideas
+ 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.
+1. Dave basically proposes doing away with the notion of a logging API in favor
+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
function call and is logging that `error` *and not returning it*, use error
logs.
-2) Verbosity-levels on info logs. This gives developers a chance to indicate
+2. Verbosity-levels on info logs. This gives developers a chance to indicate
arbitrary grades of importance for info logs, without assigning names with
-semantic meaning such as "warning", "trace", and "debug". Superficially this
+semantic meaning such as "warning", "trace", and "debug." Superficially this
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.
+## Implementations (non-exhaustive)
There are implementations for the following logging libraries:
+- **a function** (can bridge to non-structured libraries): [funcr](https://github.com/go-logr/logr/tree/master/funcr)
- **github.com/google/glog**: [glogr](https://github.com/go-logr/glogr)
-- **k8s.io/klog**: [klogr](https://git.k8s.io/klog/klogr)
+- **k8s.io/klog** (for Kubernetes): [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)
+- **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)
+- **github.com/rs/zerolog**: [zerologr](https://github.com/go-logr/zerologr)
-# FAQ
+## FAQ
-## Conceptual
+### Conceptual
-## Why structured logging?
+#### Why structured logging?
-- **Structured logs are more easily queriable**: Since you've got
+- **Structured logs are more easily queryable**: 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
+ the name and namespace of the reconciled object, etc.
-- **Structured logging makes it easier to have cross-referencable logs**:
+- **Structured logging makes it easier to have cross-referenceable 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
+ 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
+ objects.) Structured logs allow you to preserve that structure when
outputting.
-## Why V-levels?
+#### 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.
+of the package can simply change the v-levels for that library.
-## Why not more named levels, like Warning?
+#### 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?
+#### 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
+ regular expressions, etc.
- They don't store structured data well, since contents are flattened into
- a string
+ a string.
-- They're not cross-referencable
+- They're not cross-referenceable.
-- They don't compress easily, since the message is not constant
+- They don't compress easily, since the message is not constant.
-(unless you turn positional parameters into key-value pairs with numerical
+(Unless you turn positional parameters into key-value pairs with numerical
keys, at which point you've gotten key-value logging with meaningless
-keys)
+keys.)
-## Practical
+### Practical
-## Why key-value pairs, and not a map?
+#### 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
@@ -117,26 +187,26 @@ 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?
+#### What if my V-levels differ between libraries?
That's fine. Control your V-levels on a per-logger basis, and use the
-`WithName` function to pass different loggers to different libraries.
+`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!
+#### 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
+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
+ it, and add that as a key value pair.
For instance, consider the following examples (all taken from spots in the
Kubernetes codebase):
@@ -150,34 +220,59 @@ Kubernetes codebase):
response when requesting url", "attempt", retries, "after
seconds", seconds, "url", url)`
-If you *really* must use a format string, place it as a key value, and
-call `fmt.Sprintf` yourself -- for instance, `log.Printf("unable to
+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?
+#### 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".
+`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).
+info-type logs.)
+
+#### How do I choose my keys?
-## How do I choose my keys
+Keys are fairly flexible, and can hold more or less any string
+value. For best compatibility with implementations and consistency
+with existing code in other projects, there are a few conventions you
+should consider.
-- 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
+- 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.
+- Use lower case for simple keys and
+ [lowerCamelCase](https://en.wiktionary.org/wiki/lowerCamelCase) for
+ more complex ones. Kubernetes is one example of a project that has
+ [adopted that
+ convention](https://github.com/kubernetes/community/blob/HEAD/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments).
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
index 2bafb13d1..9d92a38f1 100644
--- a/vendor/github.com/go-logr/logr/discard.go
+++ b/vendor/github.com/go-logr/logr/discard.go
@@ -16,36 +16,39 @@ limitations under the License.
package logr
-// Discard returns a valid Logger that discards all messages logged to it.
-// It can be used whenever the caller is not interested in the logs.
+// 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 DiscardLogger{}
+ return Logger{
+ level: 0,
+ sink: discardLogSink{},
+ }
}
-// DiscardLogger is a Logger that discards all messages.
-type DiscardLogger struct{}
+// discardLogSink is a LogSink that discards all messages.
+type discardLogSink struct{}
-func (l DiscardLogger) Enabled() bool {
- return false
+// Verify that it actually implements the interface
+var _ LogSink = discardLogSink{}
+
+func (l discardLogSink) Init(RuntimeInfo) {
}
-func (l DiscardLogger) Info(msg string, keysAndValues ...interface{}) {
+func (l discardLogSink) Enabled(int) bool {
+ return false
}
-func (l DiscardLogger) Error(err error, msg string, keysAndValues ...interface{}) {
+func (l discardLogSink) Info(int, string, ...interface{}) {
}
-func (l DiscardLogger) V(level int) Logger {
- return l
+func (l discardLogSink) Error(error, string, ...interface{}) {
}
-func (l DiscardLogger) WithValues(keysAndValues ...interface{}) Logger {
+func (l discardLogSink) WithValues(...interface{}) LogSink {
return l
}
-func (l DiscardLogger) WithName(name string) Logger {
+func (l discardLogSink) WithName(string) LogSink {
return l
}
-
-// Verify that it actually implements the interface
-var _ Logger = DiscardLogger{}
diff --git a/vendor/github.com/go-logr/logr/go.mod b/vendor/github.com/go-logr/logr/go.mod
index 591884e91..7baec9b57 100644
--- a/vendor/github.com/go-logr/logr/go.mod
+++ b/vendor/github.com/go-logr/logr/go.mod
@@ -1,3 +1,3 @@
module github.com/go-logr/logr
-go 1.14
+go 1.16
diff --git a/vendor/github.com/go-logr/logr/logr.go b/vendor/github.com/go-logr/logr/logr.go
index 842428bd3..c05482a20 100644
--- a/vendor/github.com/go-logr/logr/logr.go
+++ b/vendor/github.com/go-logr/logr/logr.go
@@ -16,83 +16,104 @@ limitations under the License.
// This design derives from Dave Cheney's blog:
// http://dave.cheney.net/2015/11/05/lets-talk-about-logging
-//
-// This is a BETA grade API. Until there is a significant 2nd implementation,
-// I don't really know how it will change.
-// Package logr defines abstract interfaces for logging. Packages can depend on
-// these interfaces and callers can implement logging in whatever way is
-// appropriate.
+// 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.
//
// Usage
//
-// 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.
+// 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".
//
-// The term "key" is used to refer to the name associated with a particular
-// value, to disambiguate it from the general Logger name.
+// With Go's standard log package, we might write:
+// log.Printf("setting target value %s", targetValue)
//
-// For instance, suppose we're trying to reconcile the state of an object, and
-// we want to log that we've made some decision.
+// With logr's structured logging, we'd write:
+// logger.Info("setting target", "value", targetValue)
//
-// 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)
+// Errors are much the same. Instead of:
+// log.Printf("failed to open the pod bay door for user %s: %v", user, err)
//
-// 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'd write:
+// logger.Error(err, "failed to open the pod bay door", "user", user)
//
-// // later on...
-// log.Info("setting foo on object", "value", targetValue, "object", object)
+// 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(). Error() messages are
+// always logged, regardless of the current verbosity. If there is no error
+// instance available, passing nil is valid.
+//
+// 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).
+// Error messages do not have a verbosity level and are always logged.
+//
+// Where we might have written:
+// if flVerbose >= 2 {
+// log.Printf("an unusual thing happened")
+// }
+//
+// We can write:
+// logger.V(2).Info("an unusual thing happened")
+//
+// Logger Names
+//
+// 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:
//
-// 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.WithName("compactor").Info("started", "time", time.Now())
//
-// 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)
+// 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).
//
-// With logr, we'd instead write:
-// // assuming the above setup for log
-// log.Error(err, "unable to reconcile object", "object", object)
+// Saved Values
//
-// This functions similarly to:
-// log.Info("unable to reconcile object", "error", err, "object", object)
+// 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:
//
-// 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.
+// 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)
//
-// Parts of a log line
+// With logr 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 consists 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 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).
+// 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 log line.
// This should generally be a simple description of what's occurring, and should
-// never be a format string.
+// 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
//
@@ -102,6 +123,7 @@ limitations under the License.
// * be constant (not dependent on input data)
// * contain only printable characters
// * not contain whitespace or punctuation
+// * use lower case for simple keys and lowerCamelCase for more complex ones
//
// These guidelines help ensure that log data is processed properly regardless
// of the log implementation. For example, log implementations will try to
@@ -110,21 +132,22 @@ limitations under the License.
// 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:
-//
-// * `"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.
+// * "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 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.
@@ -134,81 +157,222 @@ limitations under the License.
// type Underlier interface {
// GetUnderlying() <underlying-type>
// }
+//
+// Logger grants access to the sink to enable type assertions like this:
+// func DoSomethingWithImpl(log logr.Logger) {
+// if underlier, ok := log.GetSink()(impl.Underlier) {
+// implLogger := underlier.GetUnderlying()
+// ...
+// }
+// }
+//
+// Custom `With*` functions can be implemented by copying the complete
+// Logger struct and replacing the sink in the copy:
+// // WithFooBar changes the foobar parameter in the log sink and returns a
+// // new logger with that modified sink. It does nothing for loggers where
+// // the sink doesn't support that parameter.
+// func WithFoobar(log logr.Logger, foobar int) logr.Logger {
+// if foobarLogSink, ok := log.GetSink()(FoobarSink); ok {
+// log = log.WithSink(foobarLogSink.WithFooBar(foobar))
+// }
+// return log
+// }
+//
+// Don't use New to construct a new Logger with a LogSink retrieved from an
+// existing Logger. Source code attribution might not work correctly and
+// unexported fields in Logger get lost.
+//
+// Beware that the same LogSink instance may be shared by different logger
+// instances. Calling functions that modify the LogSink will affect all of
+// those.
package logr
import (
"context"
)
-// 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, OutputStats
+// 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{}
+ logger.setSink(sink)
+ sink.Init(runtimeInfo)
+ return logger
+}
-// Logger represents the ability to log messages, both errors and not.
-type Logger interface {
- // Enabled tests whether this Logger is enabled. For example, commandline
- // flags might be used to set the logging verbosity and disable some info
- // logs.
- Enabled() bool
+// setSink stores the sink and updates any related fields. It mutates the
+// logger and thus is only safe to use for loggers that are not currently being
+// used concurrently.
+func (l *Logger) setSink(sink LogSink) {
+ l.sink = sink
+}
- // 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{})
-
- // 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.
- Error(err error, msg string, keysAndValues ...interface{})
+// GetSink returns the stored sink.
+func (l Logger) GetSink() LogSink {
+ return l.sink
+}
+
+// WithSink returns a copy of the logger with the new sink.
+func (l Logger) WithSink(sink LogSink) Logger {
+ l.setSink(sink)
+ return l
+}
+
+// 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.
+//
+// The underlying sink can be accessed through GetSink and be modified through
+// WithSink. This enables the implementation of custom extensions (see "Break
+// Glass" in the package documentation). Normally the sink should be used only
+// indirectly.
+type Logger struct {
+ sink LogSink
+ level int
+}
+
+// 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() {
+ if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
+ withHelper.GetCallStackHelper()()
+ }
+ 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 log message will always be emitted, regardless of
+// verbosity level.
+//
+// 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. The err parameter is optional
+// and nil may be passed instead of an error instance.
+func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) {
+ if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
+ withHelper.GetCallStackHelper()()
+ }
+ 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.setSink(l.sink.WithValues(keysAndValues...))
+ return l
+}
- // V returns an Logger value for a specific verbosity level, relative to
- // this Logger. In other words, V values are additive. V higher verbosity
- // level means a log message is less important. It's illegal to pass a log
- // level less than zero.
- V(level int) Logger
-
- // 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 recommended
- // that name segments contain only letters, digits, and hyphens
- // (see the package documentation for more information).
- WithName(name string) Logger
+// 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.setSink(l.sink.WithName(name))
+ return l
}
-// InfoLogger provides compatibility with code that relies on the v0.1.0
-// interface.
+// 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.
+//
+// To skip one level, WithCallStackHelper() should be used instead of
+// WithCallDepth(1) because it works with implementions that support the
+// CallDepthLogSink and/or CallStackHelperLogSink interfaces.
+func (l Logger) WithCallDepth(depth int) Logger {
+ if withCallDepth, ok := l.sink.(CallDepthLogSink); ok {
+ l.setSink(withCallDepth.WithCallDepth(depth))
+ }
+ return l
+}
+
+// WithCallStackHelper returns a new Logger instance that skips the direct
+// caller 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 and want to support loggers which depend on marking
+// each individual helper function, like loggers based on testing.T.
+//
+// In addition to using that new logger instance, callers also must call the
+// returned function.
//
-// Deprecated: InfoLogger is an artifact of early versions of this API. New
-// users should never use it and existing users should use Logger instead. This
-// will be removed in a future release.
-type InfoLogger = Logger
+// If the underlying log implementation supports a WithCallDepth(int) method,
+// WithCallDepth(1) will be called to produce a new logger. If it supports a
+// WithCallStackHelper() method, that will be also called. If the
+// implementation does not support either of these, the original Logger will be
+// returned.
+func (l Logger) WithCallStackHelper() (func(), Logger) {
+ var helper func()
+ if withCallDepth, ok := l.sink.(CallDepthLogSink); ok {
+ l.setSink(withCallDepth.WithCallDepth(1))
+ }
+ if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
+ helper = withHelper.GetCallStackHelper()
+ } else {
+ helper = func() {}
+ }
+ return helper, l
+}
+// contextKey is how we find Loggers in a context.Context.
type contextKey struct{}
-// FromContext returns a Logger constructed from ctx or nil if no
-// logger details are found.
-func FromContext(ctx context.Context) Logger {
+// 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
+ return v, nil
}
- return nil
+ return Logger{}, notFoundError{}
}
-// FromContextOrDiscard returns a Logger constructed from ctx or a Logger
-// that discards all messages if no logger details are found.
+// 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
@@ -217,12 +381,59 @@ func FromContextOrDiscard(ctx context.Context) Logger {
return Discard()
}
-// NewContext returns a new context derived from ctx that embeds the Logger.
-func NewContext(ctx context.Context, l Logger) context.Context {
- return context.WithValue(ctx, contextKey{}, l)
+// 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)
}
-// CallDepthLogger represents a Logger that knows how to climb the call stack
+// 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
+
+ // Info logs a non-error message with the given key/value pairs as context.
+ // 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{})
+
+ // 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.
@@ -232,35 +443,59 @@ func NewContext(ctx context.Context, l Logger) context.Context {
//
// This is an optional interface and implementations are not required to
// support it.
-type CallDepthLogger interface {
- Logger
-
- // 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.
+type CallDepthLogSink interface {
+ // WithCallDepth returns a LogSink that will offset the call
+ // stack by the specified number of frames when logging call
+ // site information.
+ //
+ // If depth is 0, the LogSink should skip exactly the number
+ // of call frames defined in RuntimeInfo.CallDepth when Info
+ // or Error are called, i.e. the attribution should be to the
+ // direct caller of Logger.Info or Logger.Error.
+ //
+ // If depth is 1 the attribution should skip 1 call frame, and so on.
// Successive calls to this are additive.
- WithCallDepth(depth int) Logger
+ WithCallDepth(depth int) LogSink
}
-// WithCallDepth returns a Logger that will offset 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.
+// CallStackHelperLogSink represents a Logger that knows how to climb
+// the call stack to identify the original call site and can skip
+// intermediate helper functions if they mark themselves as
+// helper. Go's testing package uses that approach.
//
-// If the underlying log implementation supports the CallDepthLogger interface,
-// the WithCallDepth method will be called and the result returned. If the
-// implementation does not support CallDepthLogger, the original Logger will be
-// returned.
+// 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.
//
-// Callers which care about whether this was supported or not should test for
-// CallDepthLogger support themselves.
-func WithCallDepth(logger Logger, depth int) Logger {
- if decorator, ok := logger.(CallDepthLogger); ok {
- return decorator.WithCallDepth(depth)
- }
- return logger
+// This is an optional interface and implementations are not required
+// to support it. Implementations that choose to support this must not
+// simply implement it as WithCallDepth(1), because
+// Logger.WithCallStackHelper will call both methods if they are
+// present. This should only be implemented for LogSinks that actually
+// need it, as with testing.T.
+type CallStackHelperLogSink interface {
+ // GetCallStackHelper returns a function that must be called
+ // to mark the direct caller as helper function when logging
+ // call site information.
+ GetCallStackHelper() func()
+}
+
+// Marshaler is an optional interface that logged values may choose to
+// implement. Loggers with structured output, such as JSON, should
+// log the object return by the MarshalLog method instead of the
+// original value.
+type Marshaler interface {
+ // MarshalLog can be used to:
+ // - ensure that structs are not logged as strings when the original
+ // value has a String method: return a different type without a
+ // String method
+ // - select which fields of a complex type should get logged:
+ // return a simpler struct with fewer fields
+ // - log unexported fields: return a different struct
+ // with exported fields
+ //
+ // It may return any value of any type.
+ MarshalLog() interface{}
}