gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

logr.go (21081B)


      1 /*
      2 Copyright 2019 The logr Authors.
      3 
      4 Licensed under the Apache License, Version 2.0 (the "License");
      5 you may not use this file except in compliance with the License.
      6 You may obtain a copy of the License at
      7 
      8     http://www.apache.org/licenses/LICENSE-2.0
      9 
     10 Unless required by applicable law or agreed to in writing, software
     11 distributed under the License is distributed on an "AS IS" BASIS,
     12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 See the License for the specific language governing permissions and
     14 limitations under the License.
     15 */
     16 
     17 // This design derives from Dave Cheney's blog:
     18 //     http://dave.cheney.net/2015/11/05/lets-talk-about-logging
     19 
     20 // Package logr defines a general-purpose logging API and abstract interfaces
     21 // to back that API.  Packages in the Go ecosystem can depend on this package,
     22 // while callers can implement logging with whatever backend is appropriate.
     23 //
     24 // # Usage
     25 //
     26 // Logging is done using a Logger instance.  Logger is a concrete type with
     27 // methods, which defers the actual logging to a LogSink interface.  The main
     28 // methods of Logger are Info() and Error().  Arguments to Info() and Error()
     29 // are key/value pairs rather than printf-style formatted strings, emphasizing
     30 // "structured logging".
     31 //
     32 // With Go's standard log package, we might write:
     33 //
     34 //	log.Printf("setting target value %s", targetValue)
     35 //
     36 // With logr's structured logging, we'd write:
     37 //
     38 //	logger.Info("setting target", "value", targetValue)
     39 //
     40 // Errors are much the same.  Instead of:
     41 //
     42 //	log.Printf("failed to open the pod bay door for user %s: %v", user, err)
     43 //
     44 // We'd write:
     45 //
     46 //	logger.Error(err, "failed to open the pod bay door", "user", user)
     47 //
     48 // Info() and Error() are very similar, but they are separate methods so that
     49 // LogSink implementations can choose to do things like attach additional
     50 // information (such as stack traces) on calls to Error(). Error() messages are
     51 // always logged, regardless of the current verbosity.  If there is no error
     52 // instance available, passing nil is valid.
     53 //
     54 // # Verbosity
     55 //
     56 // Often we want to log information only when the application in "verbose
     57 // mode".  To write log lines that are more verbose, Logger has a V() method.
     58 // The higher the V-level of a log line, the less critical it is considered.
     59 // Log-lines with V-levels that are not enabled (as per the LogSink) will not
     60 // be written.  Level V(0) is the default, and logger.V(0).Info() has the same
     61 // meaning as logger.Info().  Negative V-levels have the same meaning as V(0).
     62 // Error messages do not have a verbosity level and are always logged.
     63 //
     64 // Where we might have written:
     65 //
     66 //	if flVerbose >= 2 {
     67 //	    log.Printf("an unusual thing happened")
     68 //	}
     69 //
     70 // We can write:
     71 //
     72 //	logger.V(2).Info("an unusual thing happened")
     73 //
     74 // # Logger Names
     75 //
     76 // Logger instances can have name strings so that all messages logged through
     77 // that instance have additional context.  For example, you might want to add
     78 // a subsystem name:
     79 //
     80 //	logger.WithName("compactor").Info("started", "time", time.Now())
     81 //
     82 // The WithName() method returns a new Logger, which can be passed to
     83 // constructors or other functions for further use.  Repeated use of WithName()
     84 // will accumulate name "segments".  These name segments will be joined in some
     85 // way by the LogSink implementation.  It is strongly recommended that name
     86 // segments contain simple identifiers (letters, digits, and hyphen), and do
     87 // not contain characters that could muddle the log output or confuse the
     88 // joining operation (e.g. whitespace, commas, periods, slashes, brackets,
     89 // quotes, etc).
     90 //
     91 // # Saved Values
     92 //
     93 // Logger instances can store any number of key/value pairs, which will be
     94 // logged alongside all messages logged through that instance.  For example,
     95 // you might want to create a Logger instance per managed object:
     96 //
     97 // With the standard log package, we might write:
     98 //
     99 //	log.Printf("decided to set field foo to value %q for object %s/%s",
    100 //	    targetValue, object.Namespace, object.Name)
    101 //
    102 // With logr we'd write:
    103 //
    104 //	// Elsewhere: set up the logger to log the object name.
    105 //	obj.logger = mainLogger.WithValues(
    106 //	    "name", obj.name, "namespace", obj.namespace)
    107 //
    108 //	// later on...
    109 //	obj.logger.Info("setting foo", "value", targetValue)
    110 //
    111 // # Best Practices
    112 //
    113 // Logger has very few hard rules, with the goal that LogSink implementations
    114 // might have a lot of freedom to differentiate.  There are, however, some
    115 // things to consider.
    116 //
    117 // The log message consists of a constant message attached to the log line.
    118 // This should generally be a simple description of what's occurring, and should
    119 // never be a format string.  Variable information can then be attached using
    120 // named values.
    121 //
    122 // Keys are arbitrary strings, but should generally be constant values.  Values
    123 // may be any Go value, but how the value is formatted is determined by the
    124 // LogSink implementation.
    125 //
    126 // Logger instances are meant to be passed around by value. Code that receives
    127 // such a value can call its methods without having to check whether the
    128 // instance is ready for use.
    129 //
    130 // Calling methods with the null logger (Logger{}) as instance will crash
    131 // because it has no LogSink. Therefore this null logger should never be passed
    132 // around. For cases where passing a logger is optional, a pointer to Logger
    133 // should be used.
    134 //
    135 // # Key Naming Conventions
    136 //
    137 // Keys are not strictly required to conform to any specification or regex, but
    138 // it is recommended that they:
    139 //   - be human-readable and meaningful (not auto-generated or simple ordinals)
    140 //   - be constant (not dependent on input data)
    141 //   - contain only printable characters
    142 //   - not contain whitespace or punctuation
    143 //   - use lower case for simple keys and lowerCamelCase for more complex ones
    144 //
    145 // These guidelines help ensure that log data is processed properly regardless
    146 // of the log implementation.  For example, log implementations will try to
    147 // output JSON data or will store data for later database (e.g. SQL) queries.
    148 //
    149 // While users are generally free to use key names of their choice, it's
    150 // generally best to avoid using the following keys, as they're frequently used
    151 // by implementations:
    152 //   - "caller": the calling information (file/line) of a particular log line
    153 //   - "error": the underlying error value in the `Error` method
    154 //   - "level": the log level
    155 //   - "logger": the name of the associated logger
    156 //   - "msg": the log message
    157 //   - "stacktrace": the stack trace associated with a particular log line or
    158 //     error (often from the `Error` message)
    159 //   - "ts": the timestamp for a log line
    160 //
    161 // Implementations are encouraged to make use of these keys to represent the
    162 // above concepts, when necessary (for example, in a pure-JSON output form, it
    163 // would be necessary to represent at least message and timestamp as ordinary
    164 // named values).
    165 //
    166 // # Break Glass
    167 //
    168 // Implementations may choose to give callers access to the underlying
    169 // logging implementation.  The recommended pattern for this is:
    170 //
    171 //	// Underlier exposes access to the underlying logging implementation.
    172 //	// Since callers only have a logr.Logger, they have to know which
    173 //	// implementation is in use, so this interface is less of an abstraction
    174 //	// and more of way to test type conversion.
    175 //	type Underlier interface {
    176 //	    GetUnderlying() <underlying-type>
    177 //	}
    178 //
    179 // Logger grants access to the sink to enable type assertions like this:
    180 //
    181 //	func DoSomethingWithImpl(log logr.Logger) {
    182 //	    if underlier, ok := log.GetSink().(impl.Underlier); ok {
    183 //	       implLogger := underlier.GetUnderlying()
    184 //	       ...
    185 //	    }
    186 //	}
    187 //
    188 // Custom `With*` functions can be implemented by copying the complete
    189 // Logger struct and replacing the sink in the copy:
    190 //
    191 //	// WithFooBar changes the foobar parameter in the log sink and returns a
    192 //	// new logger with that modified sink.  It does nothing for loggers where
    193 //	// the sink doesn't support that parameter.
    194 //	func WithFoobar(log logr.Logger, foobar int) logr.Logger {
    195 //	   if foobarLogSink, ok := log.GetSink().(FoobarSink); ok {
    196 //	      log = log.WithSink(foobarLogSink.WithFooBar(foobar))
    197 //	   }
    198 //	   return log
    199 //	}
    200 //
    201 // Don't use New to construct a new Logger with a LogSink retrieved from an
    202 // existing Logger. Source code attribution might not work correctly and
    203 // unexported fields in Logger get lost.
    204 //
    205 // Beware that the same LogSink instance may be shared by different logger
    206 // instances. Calling functions that modify the LogSink will affect all of
    207 // those.
    208 package logr
    209 
    210 import (
    211 	"context"
    212 )
    213 
    214 // New returns a new Logger instance.  This is primarily used by libraries
    215 // implementing LogSink, rather than end users.  Passing a nil sink will create
    216 // a Logger which discards all log lines.
    217 func New(sink LogSink) Logger {
    218 	logger := Logger{}
    219 	logger.setSink(sink)
    220 	if sink != nil {
    221 		sink.Init(runtimeInfo)
    222 	}
    223 	return logger
    224 }
    225 
    226 // setSink stores the sink and updates any related fields. It mutates the
    227 // logger and thus is only safe to use for loggers that are not currently being
    228 // used concurrently.
    229 func (l *Logger) setSink(sink LogSink) {
    230 	l.sink = sink
    231 }
    232 
    233 // GetSink returns the stored sink.
    234 func (l Logger) GetSink() LogSink {
    235 	return l.sink
    236 }
    237 
    238 // WithSink returns a copy of the logger with the new sink.
    239 func (l Logger) WithSink(sink LogSink) Logger {
    240 	l.setSink(sink)
    241 	return l
    242 }
    243 
    244 // Logger is an interface to an abstract logging implementation.  This is a
    245 // concrete type for performance reasons, but all the real work is passed on to
    246 // a LogSink.  Implementations of LogSink should provide their own constructors
    247 // that return Logger, not LogSink.
    248 //
    249 // The underlying sink can be accessed through GetSink and be modified through
    250 // WithSink. This enables the implementation of custom extensions (see "Break
    251 // Glass" in the package documentation). Normally the sink should be used only
    252 // indirectly.
    253 type Logger struct {
    254 	sink  LogSink
    255 	level int
    256 }
    257 
    258 // Enabled tests whether this Logger is enabled.  For example, commandline
    259 // flags might be used to set the logging verbosity and disable some info logs.
    260 func (l Logger) Enabled() bool {
    261 	return l.sink != nil && l.sink.Enabled(l.level)
    262 }
    263 
    264 // Info logs a non-error message with the given key/value pairs as context.
    265 //
    266 // The msg argument should be used to add some constant description to the log
    267 // line.  The key/value pairs can then be used to add additional variable
    268 // information.  The key/value pairs must alternate string keys and arbitrary
    269 // values.
    270 func (l Logger) Info(msg string, keysAndValues ...interface{}) {
    271 	if l.sink == nil {
    272 		return
    273 	}
    274 	if l.Enabled() {
    275 		if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
    276 			withHelper.GetCallStackHelper()()
    277 		}
    278 		l.sink.Info(l.level, msg, keysAndValues...)
    279 	}
    280 }
    281 
    282 // Error logs an error, with the given message and key/value pairs as context.
    283 // It functions similarly to Info, but may have unique behavior, and should be
    284 // preferred for logging errors (see the package documentations for more
    285 // information). The log message will always be emitted, regardless of
    286 // verbosity level.
    287 //
    288 // The msg argument should be used to add context to any underlying error,
    289 // while the err argument should be used to attach the actual error that
    290 // triggered this log line, if present. The err parameter is optional
    291 // and nil may be passed instead of an error instance.
    292 func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) {
    293 	if l.sink == nil {
    294 		return
    295 	}
    296 	if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
    297 		withHelper.GetCallStackHelper()()
    298 	}
    299 	l.sink.Error(err, msg, keysAndValues...)
    300 }
    301 
    302 // V returns a new Logger instance for a specific verbosity level, relative to
    303 // this Logger.  In other words, V-levels are additive.  A higher verbosity
    304 // level means a log message is less important.  Negative V-levels are treated
    305 // as 0.
    306 func (l Logger) V(level int) Logger {
    307 	if l.sink == nil {
    308 		return l
    309 	}
    310 	if level < 0 {
    311 		level = 0
    312 	}
    313 	l.level += level
    314 	return l
    315 }
    316 
    317 // WithValues returns a new Logger instance with additional key/value pairs.
    318 // See Info for documentation on how key/value pairs work.
    319 func (l Logger) WithValues(keysAndValues ...interface{}) Logger {
    320 	if l.sink == nil {
    321 		return l
    322 	}
    323 	l.setSink(l.sink.WithValues(keysAndValues...))
    324 	return l
    325 }
    326 
    327 // WithName returns a new Logger instance with the specified name element added
    328 // to the Logger's name.  Successive calls with WithName append additional
    329 // suffixes to the Logger's name.  It's strongly recommended that name segments
    330 // contain only letters, digits, and hyphens (see the package documentation for
    331 // more information).
    332 func (l Logger) WithName(name string) Logger {
    333 	if l.sink == nil {
    334 		return l
    335 	}
    336 	l.setSink(l.sink.WithName(name))
    337 	return l
    338 }
    339 
    340 // WithCallDepth returns a Logger instance that offsets the call stack by the
    341 // specified number of frames when logging call site information, if possible.
    342 // This is useful for users who have helper functions between the "real" call
    343 // site and the actual calls to Logger methods.  If depth is 0 the attribution
    344 // should be to the direct caller of this function.  If depth is 1 the
    345 // attribution should skip 1 call frame, and so on.  Successive calls to this
    346 // are additive.
    347 //
    348 // If the underlying log implementation supports a WithCallDepth(int) method,
    349 // it will be called and the result returned.  If the implementation does not
    350 // support CallDepthLogSink, the original Logger will be returned.
    351 //
    352 // To skip one level, WithCallStackHelper() should be used instead of
    353 // WithCallDepth(1) because it works with implementions that support the
    354 // CallDepthLogSink and/or CallStackHelperLogSink interfaces.
    355 func (l Logger) WithCallDepth(depth int) Logger {
    356 	if l.sink == nil {
    357 		return l
    358 	}
    359 	if withCallDepth, ok := l.sink.(CallDepthLogSink); ok {
    360 		l.setSink(withCallDepth.WithCallDepth(depth))
    361 	}
    362 	return l
    363 }
    364 
    365 // WithCallStackHelper returns a new Logger instance that skips the direct
    366 // caller when logging call site information, if possible.  This is useful for
    367 // users who have helper functions between the "real" call site and the actual
    368 // calls to Logger methods and want to support loggers which depend on marking
    369 // each individual helper function, like loggers based on testing.T.
    370 //
    371 // In addition to using that new logger instance, callers also must call the
    372 // returned function.
    373 //
    374 // If the underlying log implementation supports a WithCallDepth(int) method,
    375 // WithCallDepth(1) will be called to produce a new logger. If it supports a
    376 // WithCallStackHelper() method, that will be also called. If the
    377 // implementation does not support either of these, the original Logger will be
    378 // returned.
    379 func (l Logger) WithCallStackHelper() (func(), Logger) {
    380 	if l.sink == nil {
    381 		return func() {}, l
    382 	}
    383 	var helper func()
    384 	if withCallDepth, ok := l.sink.(CallDepthLogSink); ok {
    385 		l.setSink(withCallDepth.WithCallDepth(1))
    386 	}
    387 	if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
    388 		helper = withHelper.GetCallStackHelper()
    389 	} else {
    390 		helper = func() {}
    391 	}
    392 	return helper, l
    393 }
    394 
    395 // IsZero returns true if this logger is an uninitialized zero value
    396 func (l Logger) IsZero() bool {
    397 	return l.sink == nil
    398 }
    399 
    400 // contextKey is how we find Loggers in a context.Context.
    401 type contextKey struct{}
    402 
    403 // FromContext returns a Logger from ctx or an error if no Logger is found.
    404 func FromContext(ctx context.Context) (Logger, error) {
    405 	if v, ok := ctx.Value(contextKey{}).(Logger); ok {
    406 		return v, nil
    407 	}
    408 
    409 	return Logger{}, notFoundError{}
    410 }
    411 
    412 // notFoundError exists to carry an IsNotFound method.
    413 type notFoundError struct{}
    414 
    415 func (notFoundError) Error() string {
    416 	return "no logr.Logger was present"
    417 }
    418 
    419 func (notFoundError) IsNotFound() bool {
    420 	return true
    421 }
    422 
    423 // FromContextOrDiscard returns a Logger from ctx.  If no Logger is found, this
    424 // returns a Logger that discards all log messages.
    425 func FromContextOrDiscard(ctx context.Context) Logger {
    426 	if v, ok := ctx.Value(contextKey{}).(Logger); ok {
    427 		return v
    428 	}
    429 
    430 	return Discard()
    431 }
    432 
    433 // NewContext returns a new Context, derived from ctx, which carries the
    434 // provided Logger.
    435 func NewContext(ctx context.Context, logger Logger) context.Context {
    436 	return context.WithValue(ctx, contextKey{}, logger)
    437 }
    438 
    439 // RuntimeInfo holds information that the logr "core" library knows which
    440 // LogSinks might want to know.
    441 type RuntimeInfo struct {
    442 	// CallDepth is the number of call frames the logr library adds between the
    443 	// end-user and the LogSink.  LogSink implementations which choose to print
    444 	// the original logging site (e.g. file & line) should climb this many
    445 	// additional frames to find it.
    446 	CallDepth int
    447 }
    448 
    449 // runtimeInfo is a static global.  It must not be changed at run time.
    450 var runtimeInfo = RuntimeInfo{
    451 	CallDepth: 1,
    452 }
    453 
    454 // LogSink represents a logging implementation.  End-users will generally not
    455 // interact with this type.
    456 type LogSink interface {
    457 	// Init receives optional information about the logr library for LogSink
    458 	// implementations that need it.
    459 	Init(info RuntimeInfo)
    460 
    461 	// Enabled tests whether this LogSink is enabled at the specified V-level.
    462 	// For example, commandline flags might be used to set the logging
    463 	// verbosity and disable some info logs.
    464 	Enabled(level int) bool
    465 
    466 	// Info logs a non-error message with the given key/value pairs as context.
    467 	// The level argument is provided for optional logging.  This method will
    468 	// only be called when Enabled(level) is true. See Logger.Info for more
    469 	// details.
    470 	Info(level int, msg string, keysAndValues ...interface{})
    471 
    472 	// Error logs an error, with the given message and key/value pairs as
    473 	// context.  See Logger.Error for more details.
    474 	Error(err error, msg string, keysAndValues ...interface{})
    475 
    476 	// WithValues returns a new LogSink with additional key/value pairs.  See
    477 	// Logger.WithValues for more details.
    478 	WithValues(keysAndValues ...interface{}) LogSink
    479 
    480 	// WithName returns a new LogSink with the specified name appended.  See
    481 	// Logger.WithName for more details.
    482 	WithName(name string) LogSink
    483 }
    484 
    485 // CallDepthLogSink represents a LogSink that knows how to climb the call stack
    486 // to identify the original call site and can offset the depth by a specified
    487 // number of frames.  This is useful for users who have helper functions
    488 // between the "real" call site and the actual calls to Logger methods.
    489 // Implementations that log information about the call site (such as file,
    490 // function, or line) would otherwise log information about the intermediate
    491 // helper functions.
    492 //
    493 // This is an optional interface and implementations are not required to
    494 // support it.
    495 type CallDepthLogSink interface {
    496 	// WithCallDepth returns a LogSink that will offset the call
    497 	// stack by the specified number of frames when logging call
    498 	// site information.
    499 	//
    500 	// If depth is 0, the LogSink should skip exactly the number
    501 	// of call frames defined in RuntimeInfo.CallDepth when Info
    502 	// or Error are called, i.e. the attribution should be to the
    503 	// direct caller of Logger.Info or Logger.Error.
    504 	//
    505 	// If depth is 1 the attribution should skip 1 call frame, and so on.
    506 	// Successive calls to this are additive.
    507 	WithCallDepth(depth int) LogSink
    508 }
    509 
    510 // CallStackHelperLogSink represents a LogSink that knows how to climb
    511 // the call stack to identify the original call site and can skip
    512 // intermediate helper functions if they mark themselves as
    513 // helper. Go's testing package uses that approach.
    514 //
    515 // This is useful for users who have helper functions between the
    516 // "real" call site and the actual calls to Logger methods.
    517 // Implementations that log information about the call site (such as
    518 // file, function, or line) would otherwise log information about the
    519 // intermediate helper functions.
    520 //
    521 // This is an optional interface and implementations are not required
    522 // to support it. Implementations that choose to support this must not
    523 // simply implement it as WithCallDepth(1), because
    524 // Logger.WithCallStackHelper will call both methods if they are
    525 // present. This should only be implemented for LogSinks that actually
    526 // need it, as with testing.T.
    527 type CallStackHelperLogSink interface {
    528 	// GetCallStackHelper returns a function that must be called
    529 	// to mark the direct caller as helper function when logging
    530 	// call site information.
    531 	GetCallStackHelper() func()
    532 }
    533 
    534 // Marshaler is an optional interface that logged values may choose to
    535 // implement. Loggers with structured output, such as JSON, should
    536 // log the object return by the MarshalLog method instead of the
    537 // original value.
    538 type Marshaler interface {
    539 	// MarshalLog can be used to:
    540 	//   - ensure that structs are not logged as strings when the original
    541 	//     value has a String method: return a different type without a
    542 	//     String method
    543 	//   - select which fields of a complex type should get logged:
    544 	//     return a simpler struct with fewer fields
    545 	//   - log unexported fields: return a different struct
    546 	//     with exported fields
    547 	//
    548 	// It may return any value of any type.
    549 	MarshalLog() interface{}
    550 }