log

package module
v1.1.2 Latest Latest
Warning

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

Go to latest
Published: Oct 1, 2020 License: MIT Imports: 13 Imported by: 2

README

Nacelle Logging GoDoc CircleCI Coverage Status

Opinionated structured logger for nacelle.


Nacelle loggers emit structured logs to standard error. It is absolutely essential to be able to correlate and aggregate log messages to form a view of a running system. In order to redirect logs to a secondary target (such as an ELK stack), the application's output should simply be redirected. This keeps the application simple and allows redirection of logs to any source without requiring an application update. For an example of redirection when run in a Docker container, see nacelle's fluentd wrapper.

There are five standard log levels: Debug, Info, Warning, Error, and Fatal. Logging at the fatal level will abort the application after flushing any outstanding log messages. The logger interface has a method for each log level with printf-like arguments (a format string and a variable number of arguments used to construct the message).

logger.Error("Failed to dial database (%s)", err.Error())

In addition, the logger interface has a WithFields variant, which takes a map of additional log data as a first argument. A nacelle.LogFields value is a map from strings to interface types and can be used interchangeably.

logger.DebugWithFields(nacelle.LogFields{
    "requestId": "00001111-2222-3333-4444-555566667777",
}, "Accepted request from %s", remoteAddr)

A logger can also be decorated with a set of fields so that multiple calls to the logger share the same set of base fields. This is useful for message correlation in servers where a logger instance can be given a unique request or client identifier. Creating a decorated logger does not modify the base logger, thus it is safe to create multiple concurrent decorated loggers from the same logger instance without worrying about interference.

requestLogger := logger.WithFields(nacelle.LogFields{
    "requestId": "00001111-2222-3333-4444-555566667777",
})

requestLogger.Info("Accepted request from %s", remoteAddr)
Caller Stack

Sometimes it is useful to define helper functions that logs messages for you in a common way (applying common context, performing additional behavior on errors, etc). Unfortunately, this can interfere with the way the caller file and line number are discovered. The logger has a WithIndirectCaller method that will increase the depth used when scanning the stack for callers. This should be used at each log location that aggregates log calls (e.g. any place where knowing this source location would not be helpful).

func logForMe(message string) {
    parentLogger.WithIndirectCaller(1).Log(message)
}

logForMe("foobar")

Using a logger instance directly does not require this additional hint.

Adapters

Nacelle ships with a handful of useful logging adapters. These are extensions of the logger interface that add additional behavior or additional structured data. A custom adapter can be created for behavior that is not provided here.

The replay adapter supports journaling log messages and conditionally re-writing them at a different log level. This is useful in circumstances where all the debug logs for a particular request need to be available without making all debug logs in the process available. Messages which are replayed at a higher level will keep the original message timestamp (if supplied), or use the time the log was first published (if not supplied). Each message will also be sent with an additional field called replayed-from-level with a value equal to the original level of the message.

requestLogger := NewReplayAdapter(
    logger,         // base logger
    log.LevelDebug, // track debug messages for replay
    log.LevelInfo,  // also track info messages
)

// handle request

if requestTookTooLong() {
    // Re-log journaled messages at warning level
    requestLogger.Replay(log.LevelWarning)
}

The rollup adapter supports collapsing similar log messages into a multiplicity. This is intended to be used with a chatty subsystem that only logs a handful of messages for which a higher frequency does not provide a benefit (for example, failure to connect to a Redis cache during a network partition). A rollup begins once two messages with the same format string are seen within the rollup window period. During a rollup, all log messages (except for the first in the window) are discarded but counted, and the first log message in that window will be sent at the end of the window period with an additional field called rollup-multiplicity with a value equal to the number of logs in that window.s

logger := NewRollupAdapter(
    logger,      // base logger
    time.Second, // rollup window
)

for i := 0; i < 10000; i++ {
    logger.Debug("Some problem here!")
}
Configuration

The following environment variables change the behavior of the loggers when using the nacelle bootstrapper.

Environment Variable Default Description
LOG_COLORIZE true Colorize log messages by level when true. Works with console encoding only.
LOG_JSON_FIELD_NAMES A JSON-encoded map to rename the fields for message, timestamp, and/or level.
LOG_DISPLAY_FIELDS true Omit log fields from output when false. Works with console encoding only.
LOG_DISPLAY_MULTILINE_FIELDS false Print fields on one line when true, one field per line when false. Works with console encoding only.
LOG_ENCODING console console for human-readable output and json for JSON-formatted output.
LOG_FIELD_BLACKLIST A JSON-encoded list of fields to omit from logs. Works with console encoding only.
LOG_FIELDS A JSON-encoded map of fields to include in every log.
LOG_LEVEL info The highest level that will be emitted.
LOG_SHORT_TIME false Omit date from timestamp when true. Works with console encoding only.

Example log output is shown below. The outputs are configure in order with: the default configuration, LOG_ENCODING=json, LOG_DISPLAY_FIELDS=false, LOG_SHORT_TIME=true, and LOG_DISPLAY_MULTILINE_FIELDS=true.

[I] [2019/07/24 09:15:30.806] Accepted request from 68.6.165.7 caller=derision/main.go:20 requestId=12341234-1234-1234-1234-123412341234 sequenceNumber=2

{"caller":"derision/main.go:20","level":"info","message":"Accepted request from 68.6.165.7","requestId":"12341234-1234-1234-1234-123412341234","sequenceNumber":2,"timestamp":"2019-07-24T09:16:55.673-0700"}

[I] [2019/07/24 09:15:49.517] Accepted request from 68.6.165.7

[I] [09:17:56] Accepted request from 68.6.165.7 caller=derision/main.go:20 requestId=12341234-1234-1234-1234-123412341234 sequenceNumber=2

[I] [2019/07/24 09:16:38.117] Accepted request from 68.6.165.7
    caller = derision/main.go:20
    requestId = 12341234-1234-1234-1234-123412341234
    sequenceNumber = 2

Documentation

Index

Constants

View Source
const FieldReplay = "replayed-from-level"

FieldReplay is a field assigned to a message that has been replayed at a different log level. Its value is equal to the original log level.

View Source
const FieldRollup = "rollup-multiplicity"

FieldRollup is a field assigned to the last message in a window. Its value is equal to the number of messages in the window before it was flushed.

View Source
const JSONTimeFormat = "2006-01-02T15:04:05.000-0700"

Variables

View Source
var (
	ErrIllegalLevel    = fmt.Errorf("illegal log level")
	ErrIllegalEncoding = fmt.Errorf("illegal log encoding")
)

Functions

func LogEmergencyError

func LogEmergencyError(message string, err error)

func LogEmergencyErrors

func LogEmergencyErrors(message string, errs []error)

Types

type Config

type Config struct {
	LogLevel                  string            `env:"log_level" file:"log_level" default:"info"`
	LogEncoding               string            `env:"log_encoding" file:"log_encoding" default:"console"`
	LogColorize               bool              `env:"log_colorize" file:"log_colorize" default:"true"`
	LogJSONFieldNames         map[string]string `env:"log_json_field_names" file:"log_json_field_names"`
	LogInitialFields          LogFields         `env:"log_fields" file:"log_fields"`
	LogShortTime              bool              `env:"log_short_time" file:"log_short_time" default:"false"`
	LogDisplayFields          bool              `env:"log_display_fields" file:"log_display_fields" default:"true"`
	LogDisplayMultilineFields bool              `env:"log_display_multiline_fields" file:"log_display_multiline_fields" default:"false"`
	LogFieldBlacklist         []string          `env:"log_field_blacklist" file:"log_field_blacklist"`
}

func (*Config) PostLoad

func (c *Config) PostLoad() error

type LogFields

type LogFields map[string]interface{}

type LogLevel

type LogLevel int
const (
	LevelFatal LogLevel = iota
	LevelError
	LevelWarning
	LevelInfo
	LevelDebug
	LevelNone
)

func (LogLevel) String

func (l LogLevel) String() string

type Logger

type Logger interface {
	WithIndirectCaller(frames int) Logger
	WithFields(LogFields) Logger
	LogWithFields(LogLevel, LogFields, string, ...interface{})
	Sync() error

	// Convenience Methods
	Debug(string, ...interface{})
	Info(string, ...interface{})
	Warning(string, ...interface{})
	Error(string, ...interface{})
	Fatal(string, ...interface{})
	DebugWithFields(LogFields, string, ...interface{})
	InfoWithFields(LogFields, string, ...interface{})
	WarningWithFields(LogFields, string, ...interface{})
	ErrorWithFields(LogFields, string, ...interface{})
	FatalWithFields(LogFields, string, ...interface{})
}

func EmergencyLogger

func EmergencyLogger() Logger

func InitLogger added in v1.1.0

func InitLogger(c *Config) (Logger, error)

func NewNilLogger

func NewNilLogger() Logger

func NewRollupAdapter

func NewRollupAdapter(logger Logger, windowDuration time.Duration) Logger

NewRollupAdapter returns a logger with functionality to throttle similar messages. Messages begin a roll-up when a second messages with an identical format string is seen in the same window period. All remaining messages logged within that period are captured and emitted as a single message at the end of the window period. The fields and args are equal to the first rolled-up message.

type NilShim

type NilShim struct{}

func (*NilShim) LogWithFields

func (n *NilShim) LogWithFields(LogLevel, LogFields, string, ...interface{})

func (*NilShim) Sync

func (n *NilShim) Sync() error

func (*NilShim) WithFields

func (n *NilShim) WithFields(LogFields) logShim

type ReplayLogger

type ReplayLogger interface {
	Logger

	// Replay will cause all of the messages previously logged at one of the
	// journaled levels to be re-set at the given level. All future messages
	// logged at one of the journaled levels will be replayed immediately.
	Replay(LogLevel)
}

ReplayLogger is a Logger that provides a way to replay a sequence of message in the order they were logged, at a higher log level.

func NewReplayAdapter

func NewReplayAdapter(logger Logger, levels ...LogLevel) ReplayLogger

NewReplayAdapter creates a ReplayLogger wrapping the given logger.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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