13 | 13 |
limitations under the License.
|
14 | 14 |
*/
|
15 | 15 |
|
|
16 |
// This design derives from Dave Cheney's blog:
|
|
17 |
// http://dave.cheney.net/2015/11/05/lets-talk-about-logging
|
|
18 |
//
|
|
19 |
// This is a BETA grade API. Until there is a significant 2nd implementation,
|
|
20 |
// I don't really know how it will change.
|
|
21 |
|
16 | 22 |
// Package logr defines abstract interfaces for logging. Packages can depend on
|
17 | 23 |
// these interfaces and callers can implement logging in whatever way is
|
18 | 24 |
// appropriate.
|
19 | 25 |
//
|
20 | |
// This design derives from Dave Cheney's blog:
|
21 | |
// http://dave.cheney.net/2015/11/05/lets-talk-about-logging
|
22 | |
//
|
23 | |
// This is a BETA grade API. Until there is a significant 2nd implementation,
|
24 | |
// I don't really know how it will change.
|
25 | |
//
|
26 | |
// The logging specifically makes it non-trivial to use format strings, to encourage
|
27 | |
// attaching structured information instead of unstructured format strings.
|
28 | |
//
|
29 | 26 |
// Usage
|
30 | 27 |
//
|
31 | 28 |
// Logging is done using a Logger. Loggers can have name prefixes and named
|
|
39 | 36 |
// we want to log that we've made some decision.
|
40 | 37 |
//
|
41 | 38 |
// With the traditional log package, we might write:
|
42 | |
// log.Printf(
|
43 | |
// "decided to set field foo to value %q for object %s/%s",
|
|
39 |
// log.Printf("decided to set field foo to value %q for object %s/%s",
|
44 | 40 |
// targetValue, object.Namespace, object.Name)
|
45 | 41 |
//
|
46 | 42 |
// With logr's structured logging, we'd write:
|
47 | |
// // elsewhere in the file, set up the logger to log with the prefix of "reconcilers",
|
48 | |
// // and the named value target-type=Foo, for extra context.
|
49 | |
// log := mainLogger.WithName("reconcilers").WithValues("target-type", "Foo")
|
50 | |
//
|
51 | |
// // later on...
|
52 | |
// log.Info("setting field foo on object", "value", targetValue, "object", object)
|
|
43 |
// // elsewhere in the file, set up the logger to log with the prefix of
|
|
44 |
// // "reconcilers", and the named value target-type=Foo, for extra context.
|
|
45 |
// log := mainLogger.WithName("reconcilers").WithValues("target-type", "Foo")
|
|
46 |
//
|
|
47 |
// // later on...
|
|
48 |
// log.Info("setting foo on object", "value", targetValue, "object", object)
|
53 | 49 |
//
|
54 | 50 |
// Depending on our logging implementation, we could then make logging decisions
|
55 | 51 |
// based on field values (like only logging such events for objects in a certain
|
|
77 | 73 |
// Each log message from a Logger has four types of context:
|
78 | 74 |
// logger name, log verbosity, log message, and the named values.
|
79 | 75 |
//
|
80 | |
// The Logger name constists of a series of name "segments" added by successive
|
|
76 |
// The Logger name consists of a series of name "segments" added by successive
|
81 | 77 |
// calls to WithName. These name segments will be joined in some way by the
|
82 | |
// underlying implementation. It is strongly reccomended that name segements
|
|
78 |
// underlying implementation. It is strongly recommended that name segments
|
83 | 79 |
// contain simple identifiers (letters, digits, and hyphen), and do not contain
|
84 | 80 |
// characters that could muddle the log output or confuse the joining operation
|
85 | 81 |
// (e.g. whitespace, commas, periods, slashes, brackets, quotes, etc).
|
|
90 | 86 |
// and log messages for users to filter on. It's illegal to pass a log level
|
91 | 87 |
// below zero.
|
92 | 88 |
//
|
93 | |
// The log message consists of a constant message attached to the the log line.
|
94 | |
// This should generally be a simple description of what's occuring, and should
|
|
89 |
// The log message consists of a constant message attached to the log line.
|
|
90 |
// This should generally be a simple description of what's occurring, and should
|
95 | 91 |
// never be a format string.
|
96 | 92 |
//
|
97 | 93 |
// Variable information can then be attached using named values (key/value
|
|
114 | 110 |
// generally best to avoid using the following keys, as they're frequently used
|
115 | 111 |
// by implementations:
|
116 | 112 |
//
|
117 | |
// - `"caller"`: the calling information (file/line) of a particular log line.
|
118 | |
// - `"error"`: the underlying error value in the `Error` method.
|
119 | |
// - `"level"`: the log level.
|
120 | |
// - `"logger"`: the name of the associated logger.
|
121 | |
// - `"msg"`: the log message.
|
122 | |
// - `"stacktrace"`: the stack trace associated with a particular log line or
|
123 | |
// error (often from the `Error` message).
|
124 | |
// - `"ts"`: the timestamp for a log line.
|
|
113 |
// * `"caller"`: the calling information (file/line) of a particular log line.
|
|
114 |
// * `"error"`: the underlying error value in the `Error` method.
|
|
115 |
// * `"level"`: the log level.
|
|
116 |
// * `"logger"`: the name of the associated logger.
|
|
117 |
// * `"msg"`: the log message.
|
|
118 |
// * `"stacktrace"`: the stack trace associated with a particular log line or
|
|
119 |
// error (often from the `Error` message).
|
|
120 |
// * `"ts"`: the timestamp for a log line.
|
125 | 121 |
//
|
126 | 122 |
// Implementations are encouraged to make use of these keys to represent the
|
127 | |
// above concepts, when neccessary (for example, in a pure-JSON output form, it
|
|
123 |
// above concepts, when necessary (for example, in a pure-JSON output form, it
|
128 | 124 |
// would be necessary to represent at least message and timestamp as ordinary
|
129 | 125 |
// named values).
|
|
126 |
//
|
|
127 |
// Implementations may choose to give callers access to the underlying
|
|
128 |
// logging implementation. The recommended pattern for this is:
|
|
129 |
// // Underlier exposes access to the underlying logging implementation.
|
|
130 |
// // Since callers only have a logr.Logger, they have to know which
|
|
131 |
// // implementation is in use, so this interface is less of an abstraction
|
|
132 |
// // and more of way to test type conversion.
|
|
133 |
// type Underlier interface {
|
|
134 |
// GetUnderlying() <underlying-type>
|
|
135 |
// }
|
130 | 136 |
package logr
|
|
137 |
|
|
138 |
import (
|
|
139 |
"context"
|
|
140 |
)
|
131 | 141 |
|
132 | 142 |
// TODO: consider adding back in format strings if they're really needed
|
133 | 143 |
// TODO: consider other bits of zap/zapcore functionality like ObjectMarshaller (for arbitrary objects)
|
134 | |
// TODO: consider other bits of glog functionality like Flush, InfoDepth, OutputStats
|
|
144 |
// TODO: consider other bits of glog functionality like Flush, OutputStats
|
135 | 145 |
|
136 | 146 |
// Logger represents the ability to log messages, both errors and not.
|
137 | 147 |
type Logger interface {
|
|
170 | 180 |
|
171 | 181 |
// WithName adds a new element to the logger's name.
|
172 | 182 |
// Successive calls with WithName continue to append
|
173 | |
// suffixes to the logger's name. It's strongly reccomended
|
|
183 |
// suffixes to the logger's name. It's strongly recommended
|
174 | 184 |
// that name segments contain only letters, digits, and hyphens
|
175 | 185 |
// (see the package documentation for more information).
|
176 | 186 |
WithName(name string) Logger
|
177 | 187 |
}
|
|
188 |
|
|
189 |
// InfoLogger provides compatibility with code that relies on the v0.1.0
|
|
190 |
// interface.
|
|
191 |
//
|
|
192 |
// Deprecated: InfoLogger is an artifact of early versions of this API. New
|
|
193 |
// users should never use it and existing users should use Logger instead. This
|
|
194 |
// will be removed in a future release.
|
|
195 |
type InfoLogger = Logger
|
|
196 |
|
|
197 |
type contextKey struct{}
|
|
198 |
|
|
199 |
// FromContext returns a Logger constructed from ctx or nil if no
|
|
200 |
// logger details are found.
|
|
201 |
func FromContext(ctx context.Context) Logger {
|
|
202 |
if v, ok := ctx.Value(contextKey{}).(Logger); ok {
|
|
203 |
return v
|
|
204 |
}
|
|
205 |
|
|
206 |
return nil
|
|
207 |
}
|
|
208 |
|
|
209 |
// FromContextOrDiscard returns a Logger constructed from ctx or a Logger
|
|
210 |
// that discards all messages if no logger details are found.
|
|
211 |
func FromContextOrDiscard(ctx context.Context) Logger {
|
|
212 |
if v, ok := ctx.Value(contextKey{}).(Logger); ok {
|
|
213 |
return v
|
|
214 |
}
|
|
215 |
|
|
216 |
return Discard()
|
|
217 |
}
|
|
218 |
|
|
219 |
// NewContext returns a new context derived from ctx that embeds the Logger.
|
|
220 |
func NewContext(ctx context.Context, l Logger) context.Context {
|
|
221 |
return context.WithValue(ctx, contextKey{}, l)
|
|
222 |
}
|
|
223 |
|
|
224 |
// CallDepthLogger represents a Logger that knows how to climb the call stack
|
|
225 |
// to identify the original call site and can offset the depth by a specified
|
|
226 |
// number of frames. This is useful for users who have helper functions
|
|
227 |
// between the "real" call site and the actual calls to Logger methods.
|
|
228 |
// Implementations that log information about the call site (such as file,
|
|
229 |
// function, or line) would otherwise log information about the intermediate
|
|
230 |
// helper functions.
|
|
231 |
//
|
|
232 |
// This is an optional interface and implementations are not required to
|
|
233 |
// support it.
|
|
234 |
type CallDepthLogger interface {
|
|
235 |
Logger
|
|
236 |
|
|
237 |
// WithCallDepth returns a Logger that will offset the call stack by the
|
|
238 |
// specified number of frames when logging call site information. If depth
|
|
239 |
// is 0 the attribution should be to the direct caller of this method. If
|
|
240 |
// depth is 1 the attribution should skip 1 call frame, and so on.
|
|
241 |
// Successive calls to this are additive.
|
|
242 |
WithCallDepth(depth int) Logger
|
|
243 |
}
|
|
244 |
|
|
245 |
// WithCallDepth returns a Logger that will offset the call stack by the
|
|
246 |
// specified number of frames when logging call site information, if possible.
|
|
247 |
// This is useful for users who have helper functions between the "real" call
|
|
248 |
// site and the actual calls to Logger methods. If depth is 0 the attribution
|
|
249 |
// should be to the direct caller of this function. If depth is 1 the
|
|
250 |
// attribution should skip 1 call frame, and so on. Successive calls to this
|
|
251 |
// are additive.
|
|
252 |
//
|
|
253 |
// If the underlying log implementation supports the CallDepthLogger interface,
|
|
254 |
// the WithCallDepth method will be called and the result returned. If the
|
|
255 |
// implementation does not support CallDepthLogger, the original Logger will be
|
|
256 |
// returned.
|
|
257 |
//
|
|
258 |
// Callers which care about whether this was supported or not should test for
|
|
259 |
// CallDepthLogger support themselves.
|
|
260 |
func WithCallDepth(logger Logger, depth int) Logger {
|
|
261 |
if decorator, ok := logger.(CallDepthLogger); ok {
|
|
262 |
return decorator.WithCallDepth(depth)
|
|
263 |
}
|
|
264 |
return logger
|
|
265 |
}
|