logr

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 29, 2018 License: Apache-2.0 Imports: 0 Imported by: 0

README

A more minimal logging API for Go

Before you consider this package, please read this blog post by the inimitable Dave Cheney. I really appreciate what he has to say, and it largely aligns with my own experiences. Too many choices of levels means inconsistent logs.

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.

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.

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.

  1. 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 may feel very similar, but the primary difference is the lack of semantics. Because verbosity is a numerical value, it's safe to assume that an app running with higher verbosity means more (and less important) logs will be generated.

This is a BETA grade API. I have implemented it for glog. Until there is a significant 2nd implementation, I don't really know how it will change.

Documentation

Overview

Package logr defines abstract interfaces for logging. Packages can depend on these interfaces and callers can implement logging in whatever way is appropriate.

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.

The logging specifically makes it non-trivial to use format strings, to encourage attaching structured information instead of unstructured format strings.

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 field 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 constists of a series of name "segments" added by successive calls to WithName. These name segments will be joined in some way by the underlying implementation. It is strongly reccomended that name segements contain simple identifiers (letters, digits, and hyphen), and do not contain characters that could muddle the log output or confuse the joining operation (e.g. whitespace, commas, periods, slashes, brackets, quotes, etc).

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 the log line. This should generally be a simple description of what's occuring, 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

While users are generally free to use key names of their choice, it's generally best to avoid using the following keys, as they're frequently used by implementations:

  • `"error"`: the underlying error value in the `Error` method.
  • `"stacktrace"`: the stack trace associated with a particular log line or error (often from the `Error` message).
  • `"caller"`: the calling information (file/line) of a particular log line.
  • `"msg"`: the log message.
  • `"level"`: the log level.
  • `"ts"`: the timestamp for a log line.

Implementations are encouraged to make use of these keys to represent the above concepts, when neccessary (for example, in a pure-JSON output form, it would be necessary to represent at least message and timestamp as ordinary named values).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type InfoLogger

type InfoLogger interface {
	// Info logs a non-error message with the given key/value pairs as context.
	//
	// The msg argument should be used to add some constant description to
	// the log line.  The key/value pairs can then be used to add additional
	// variable information.  The key/value pairs should alternate string
	// keys and arbitrary values.
	Info(msg string, keysAndValues ...interface{})

	// Enabled tests whether this InfoLogger is enabled.  For example,
	// commandline flags might be used to set the logging verbosity and disable
	// some info logs.
	Enabled() bool
}

InfoLogger represents the ability to log non-error messages, at a particular verbosity.

type Logger

type Logger interface {
	// All Loggers implement InfoLogger.  Calling InfoLogger methods directly on
	// a Logger value is equivalent to calling them on a V(0) InfoLogger.  For
	// example, logger.Info() produces the same result as logger.V(0).Info.
	InfoLogger

	// Error logs an error, with the given message and key/value pairs as context.
	// It functions similarly to calling Info with the "error" named value, but may
	// have unique behavior, and should be preferred for logging errors (see the
	// package documentations for more information).
	//
	// The msg field should be used to add context to any underlying error,
	// while the err field should be used to attach the actual error that
	// triggered this log line, if present.
	Error(err error, msg string, keysAndValues ...interface{})

	// V returns an InfoLogger value for a specific verbosity level.  A higher
	// verbosity level means a log message is less important.  It's illegal to
	// pass a log level less than zero.
	V(level int) InfoLogger

	// WithValues adds some key-value pairs of context to a logger.
	// See Info for documentation on how key/value pairs work.
	WithValues(keysAndValues ...interface{}) Logger

	// WithName adds a new element to the logger's name.
	// Successive calls with WithName continue to append
	// suffixes to the logger's name.  It's strongly reccomended
	// that name segments contain only letters, digits, and hyphens
	// (see the package documentation for more information).
	WithName(name string) Logger
}

Logger represents the ability to log messages, both errors and not.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL