| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- /*
- 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
- //
- // 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.
- //
- // 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.
- //
- // The term "key" is used to refer to the name associated with a particular
- // value, to disambiguate it from the general Logger name.
- //
- // 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 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)
- //
- // 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")
- //
- // // later on...
- // log.Info("setting foo on object", "value", targetValue, "object", object)
- //
- // 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.
- //
- // 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)
- //
- // With logr, we'd instead write:
- // // assuming the above setup for log
- // log.Error(err, "unable to reconcile object", "object", object)
- //
- // This functions similarly to:
- // log.Info("unable to reconcile object", "error", err, "object", 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.
- //
- // Parts of a log line
- //
- // Each log message from a Logger has four types of context:
- // logger name, log verbosity, log message, and the named values.
- //
- // 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).
- //
- // 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.
- //
- // 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 (key/value
- // pairs). Keys are arbitrary strings, while values may be any Go value.
- //
- // 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:
- //
- // * `"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).
- //
- // 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() <underlying-type>
- // }
- 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
- // 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
- // 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{})
- // 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
- }
- // InfoLogger provides compatibility with code that relies on the v0.1.0
- // interface.
- //
- // 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
- type contextKey struct{}
- // FromContext returns a Logger constructed from ctx or nil if no
- // logger details are found.
- func FromContext(ctx context.Context) Logger {
- if v, ok := ctx.Value(contextKey{}).(Logger); ok {
- return v
- }
- return nil
- }
- // FromContextOrDiscard returns a Logger constructed from ctx or a Logger
- // that discards all messages if no logger details are found.
- 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 that embeds the Logger.
- func NewContext(ctx context.Context, l Logger) context.Context {
- return context.WithValue(ctx, contextKey{}, l)
- }
- // CallDepthLogger 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 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.
- // Successive calls to this are additive.
- WithCallDepth(depth int) Logger
- }
- // 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.
- //
- // 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.
- //
- // 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
- }
|