c40779224f
Fixes #52
117 lines
5.2 KiB
Go
117 lines
5.2 KiB
Go
// Package log provides a structured logger.
|
|
//
|
|
// Structured logging produces logs easily consumed later by humans or
|
|
// machines. Humans might be interested in debugging errors, or tracing
|
|
// specific requests. Machines might be interested in counting interesting
|
|
// events, or aggregating information for off-line processing. In both cases,
|
|
// it is important that the log messages are structured and actionable.
|
|
// Package log is designed to encourage both of these best practices.
|
|
//
|
|
// Basic Usage
|
|
//
|
|
// The fundamental interface is Logger. Loggers create log events from
|
|
// key/value data. The Logger interface has a single method, Log, which
|
|
// accepts a sequence of alternating key/value pairs, which this package names
|
|
// keyvals.
|
|
//
|
|
// type Logger interface {
|
|
// Log(keyvals ...interface{}) error
|
|
// }
|
|
//
|
|
// Here is an example of a function using a Logger to create log events.
|
|
//
|
|
// func RunTask(task Task, logger log.Logger) string {
|
|
// logger.Log("taskID", task.ID, "event", "starting task")
|
|
// ...
|
|
// logger.Log("taskID", task.ID, "event", "task complete")
|
|
// }
|
|
//
|
|
// The keys in the above example are "taskID" and "event". The values are
|
|
// task.ID, "starting task", and "task complete". Every key is followed
|
|
// immediately by its value.
|
|
//
|
|
// Keys are usually plain strings. Values may be any type that has a sensible
|
|
// encoding in the chosen log format. With structured logging it is a good
|
|
// idea to log simple values without formatting them. This practice allows
|
|
// the chosen logger to encode values in the most appropriate way.
|
|
//
|
|
// Contextual Loggers
|
|
//
|
|
// A contextual logger stores keyvals that it includes in all log events.
|
|
// Building appropriate contextual loggers reduces repetition and aids
|
|
// consistency in the resulting log output. With and WithPrefix add context to
|
|
// a logger. We can use With to improve the RunTask example.
|
|
//
|
|
// func RunTask(task Task, logger log.Logger) string {
|
|
// logger = log.With(logger, "taskID", task.ID)
|
|
// logger.Log("event", "starting task")
|
|
// ...
|
|
// taskHelper(task.Cmd, logger)
|
|
// ...
|
|
// logger.Log("event", "task complete")
|
|
// }
|
|
//
|
|
// The improved version emits the same log events as the original for the
|
|
// first and last calls to Log. Passing the contextual logger to taskHelper
|
|
// enables each log event created by taskHelper to include the task.ID even
|
|
// though taskHelper does not have access to that value. Using contextual
|
|
// loggers this way simplifies producing log output that enables tracing the
|
|
// life cycle of individual tasks. (See the Contextual example for the full
|
|
// code of the above snippet.)
|
|
//
|
|
// Dynamic Contextual Values
|
|
//
|
|
// A Valuer function stored in a contextual logger generates a new value each
|
|
// time an event is logged. The Valuer example demonstrates how this feature
|
|
// works.
|
|
//
|
|
// Valuers provide the basis for consistently logging timestamps and source
|
|
// code location. The log package defines several valuers for that purpose.
|
|
// See Timestamp, DefaultTimestamp, DefaultTimestampUTC, Caller, and
|
|
// DefaultCaller. A common logger initialization sequence that ensures all log
|
|
// entries contain a timestamp and source location looks like this:
|
|
//
|
|
// logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
|
|
// logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
|
|
//
|
|
// Concurrent Safety
|
|
//
|
|
// Applications with multiple goroutines want each log event written to the
|
|
// same logger to remain separate from other log events. Package log provides
|
|
// two simple solutions for concurrent safe logging.
|
|
//
|
|
// NewSyncWriter wraps an io.Writer and serializes each call to its Write
|
|
// method. Using a SyncWriter has the benefit that the smallest practical
|
|
// portion of the logging logic is performed within a mutex, but it requires
|
|
// the formatting Logger to make only one call to Write per log event.
|
|
//
|
|
// NewSyncLogger wraps any Logger and serializes each call to its Log method.
|
|
// Using a SyncLogger has the benefit that it guarantees each log event is
|
|
// handled atomically within the wrapped logger, but it typically serializes
|
|
// both the formatting and output logic. Use a SyncLogger if the formatting
|
|
// logger may perform multiple writes per log event.
|
|
//
|
|
// Error Handling
|
|
//
|
|
// This package relies on the practice of wrapping or decorating loggers with
|
|
// other loggers to provide composable pieces of functionality. It also means
|
|
// that Logger.Log must return an error because some
|
|
// implementations—especially those that output log data to an io.Writer—may
|
|
// encounter errors that cannot be handled locally. This in turn means that
|
|
// Loggers that wrap other loggers should return errors from the wrapped
|
|
// logger up the stack.
|
|
//
|
|
// Fortunately, the decorator pattern also provides a way to avoid the
|
|
// necessity to check for errors every time an application calls Logger.Log.
|
|
// An application required to panic whenever its Logger encounters
|
|
// an error could initialize its logger as follows.
|
|
//
|
|
// fmtlogger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
|
|
// logger := log.LoggerFunc(func(keyvals ...interface{}) error {
|
|
// if err := fmtlogger.Log(keyvals...); err != nil {
|
|
// panic(err)
|
|
// }
|
|
// return nil
|
|
// })
|
|
package log
|