log

package
v0.0.0-...-488a668 Latest Latest
Warning

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

Go to latest
Published: May 25, 2024 License: BSD-3-Clause Imports: 13 Imported by: 7

README

Package log

Producing good logs is important; so important in fact that there are SEVERAL ways to do it in Go. Either with the standard library itself or using some very good third party libraries. This package aims to:

  • Provide a simple, idiomatic and friendly way to produce good/useful logs.
  • Be extensible; i.e., play nice with other existing solutions.
  • Be unobtrusive and simple to replace.
  • Be super simple to setup and use.

Loggers and Providers

To handle all log-related operations you first need to create a logger instance. Loggers are interface-based utilities backed by a provider, the provider can be implemented using any tool (or 3rd party package of your choosing). This makes the package very easy to extend and re-use.

There are two types of loggers supported:

  • SimpleLogger: Focusing on a simple interface to provide leveled logging. This loggers are compatible with the standard package logger interfaces and can be used as a drop-in replacement with minimal to no code changes.

  • Logger: Logger instances extend the base functionality of the "SimpleLogger" and provide utilities to produce more structured/contextual log messages.

Levels

Log messages relate to different kinds of events and some can be considered more relevant/urgent than others, and as such, can be recorded and managed in different ways. The simplest mechanism to specify the nature of a given log message is by setting a specific Level to it. The Logger API provides convenient methods to do this automatically. The standard level values supported by this package are (in order of relevance):

  • Debug: Should be use for information broadly interesting to developers and system administrators. Might include minor (recoverable) failures and issues indicating potential performance problems.

  • Info: Should be used for informational messages that might make sense to end users and system administrators, and highlight the progress of the application.

  • Warning: Should be used for potentially harmful situations of interest to end users or system managers that indicate potential problems.

  • Error: Error events of considerable importance that will prevent normal program execution, but might still allow the application to continue running.

  • Panic: Panic level should be used for very severe error events that might cause the application to terminate. Usually by calling panic() after logging.

  • Fatal: Fatal level should be used for very severe error events that WILL cause the application to terminate. Usually by calling os.Exit(1) after logging.

Loggers can also be adjusted to ignore events "below" a certain level; hence adjusting the verbosity level of the produced output.

var log Logger

// By setting the level to `Warning`; all `Debug` and `Info` events
// will be automatically discarded by the logger.
log.SetLevel(Warning)

Contextual Information

Sometimes is useful to add additional contextual data to log messages in the form of key/value pairs. These Fields can be used, for example, to provide further details about your environment, the task at hand, the user performing the operation, etc.

log.WithFields(Fields{
  "app.env": "dev",
  "app.live": false,
  "app.purpose": "demo",
  "app.version": "0.1.0",
}).Info("application is starting")
Sub-Loggers

There might be some fields that you need to include in all logged messages. Having to continuously add those will get repetitive and wasteful. An alternative is to provide these common fields when initializing the logger instance with Sub. You can use it regularly and add additional fields at a per-message level as required.

The following example initialize a logger using the popular ZeroLog library as provider.

// these fields will be "inherited" by all messages produced by
// the logger instance
commonFields := Fields{
  "app.env": "dev",
  "app.live": false,
  "app.purpose": "demo",
  "app.version": "0.1.0",
}

// setup a logger instance using "zero" as provider
log := WithZero(ZeroOptions{
  PrettyPrint: true,
  ErrorField:  "error",
}).Sub(commonFields)

// use the logger
log.WithField("stamp", time.Now().Unix()).Debug("starting...")
log.Info("application is ready")

Composites

Logs are usually required at different places and in different formats. Having readable console output can useful to quickly "eyeball" simple details; while at the same time having a fully indexed collection of JSON events can be helpful as observability, record-keeping and advanced analysis tool.

You can setup both (or as many as required) loggers and use Composite to manage your application's logging requirements through the same simple Logger interface.

// common application details
 appDetails := Fields{
  "app.env":     "dev",
  "app.purpose": "demo",
  "app.version": "0.1.0",
  "app.live":    false,
 }

 // sample file to collect logs in JSON format
 logFile, _ := os.OpenFile("my-logs.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
 defer logFile.Close()

 // these logger instance will "pretty" print output to the console
 consoleOutput := WithZero(ZeroOptions{PrettyPrint: true}).Sub(appDetails)

 // these logger instance will append logs in JSON format to a file.
 // Note that `Sink` can be a network connection, database, or anything
 // else conforming to the `io.Write` interface.
 fileOutput := WithZero(ZeroOptions{Sink: logFile}).Sub(appDetails)

 // using `Composite` we can "combine" both (or more) individual loggers
 // and keep the same simple to use interface.
 log := Composite(consoleOutput, fileOutput)

 // we can then just use the logger as usual
 log.Debug("initial message")

Log messages will be pretty printed to console output, while simultaneously appended to the "my-logs.txt" file as one JSON object per-line.

// formatted for readability
{
 "level": "debug",
 "app.env": "dev",
 "app.live": false,
 "app.purpose": "demo",
 "app.version": "0.1.0",
 "time": "2023-01-28T11:23:46-06:00",
 "message": "initial message"
}

Documentation

Overview

Package log provide reusable components to support structured and leveled logging.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CharmOptions

type CharmOptions struct {
	// TimeFormat defines the format used to display timestamps in log.
	TimeFormat string

	// ReportCaller enables the display of the file and line number
	// where a log entry was generated.
	ReportCaller bool

	// Prefix defines a string to be added at the beginning of each
	// log entry.
	Prefix string

	// AsJSON enables the use of JSON as the log entry format.
	AsJSON bool
}

CharmOptions defines the available settings to adjust the behavior of a logger instance backed by the `charmbracelet` library.

type Fields

type Fields = map[string]interface{}

Fields provides additional contextual information on logs; particularly useful for structured messages.

type Level

type Level uint

Level values assign a severity value to logged messages.

const (
	// Debug level should be use for information broadly interesting to developers
	// and system administrators. Might include minor (recoverable) failures and
	// issues indicating potential performance problems.
	Debug Level = 0

	// Info level should be used for informational messages that might make sense
	// to end users and system administrators, and highlight the progress of the
	// application.
	Info Level = 1

	// Warning level should be used for potentially harmful situations of interest
	// to end users or system managers that indicate potential problems.
	Warning Level = 2

	// Error events of considerable importance that will prevent normal program
	// execution, but might still allow the application to continue running.
	Error Level = 3

	// Panic level should be used for very severe error events that might cause the
	// application to terminate. Usually by calling panic() after logging.
	Panic Level = 4

	// Fatal level should be used for very severe error events that WILL cause the
	// application to terminate. Usually by calling os.Exit(1) after logging.
	Fatal Level = 5
)

func (Level) String

func (l Level) String() string

String returns a textual representation of a level value.

type Logger

type Logger interface {
	SimpleLogger // include leveled logging support

	// WithFields adds additional tags to a message to support structured logging.
	// This method should be chained with any print-style message.
	// For example: log.WithFields(fields).Debug("message")
	WithFields(fields map[string]interface{}) Logger

	// WithField adds a key/value pair to the next chained message.
	// log.WithField("foo", "bar").Debug("message")
	WithField(key string, value interface{}) Logger

	// SetLevel adjust the "verbosity" of the logger instance. Once a level is set,
	// all messages from "lower" levels will be discarded. Log messages are managed
	// at 6 distinct levels: Debug, Info, Warning, Error, Panic and Fatal.
	SetLevel(lvl Level)

	// Sub returns a new logger instance using the provided tags. Every message
	// generated by the sub-logger will include the fields set on `tags`.
	Sub(tags map[string]interface{}) Logger

	// Print logs a message at the specified `level`.
	Print(level Level, args ...interface{})

	// Printf logs a formatted message at the specified `level`.
	Printf(level Level, format string, args ...interface{})
}

Logger instances provide additional functionality to the base simple logger.

func Composite

func Composite(ll ...Logger) Logger

Composite allows to combine and control multiple logger instances through a single common interface. This is useful, for example, when you want to save structured logs to a file while at same time displaying textual messages to standard output and/or sending messages to some aggregation system.

Example
// Pretty print to standard output
l1 := WithZero(ZeroOptions{
	PrettyPrint: true,
	ErrorField:  "error",
	Sink:        os.Stderr,
})

// Send structured (JSON) logs to a file
lf, _ := os.CreateTemp("", "_logs")
l2 := WithZero(ZeroOptions{
	PrettyPrint: false,
	ErrorField:  "error",
	Sink:        lf,
})

// Create a composite logger instance
log := Composite(l1, l2)

// Use composite logger instance as usual
log.WithFields(Fields{
	"foo": 1,
	"bar": true,
	"baz": "application",
}).Debug("initializing application")
Output:

func Discard

func Discard() Logger

Discard returns a no-op handler that will discard all generated output.

Example

nolint: revive

log := Discard()
log.Debug("none of this messages")
log.Info("will produce any output")
log.Warning("but the component respects")
log.Error("the same API so is easy to")
log.Panic("switch at runtime")
log.Fatal("not even panics and crashes are executed for 'discard'")
Output:

func WithCharm

func WithCharm(opt CharmOptions) Logger

WithCharm provides a log h using the charmbracelet log library.

More information: https://github.com/charmbracelet/log
Example
// Create logger instance
log := WithCharm(CharmOptions{
	TimeFormat:   time.Kitchen,
	ReportCaller: true,
	Prefix:       "my-component",
})

// Use log handler
log.Debug("use log handler now")
log.WithFields(Fields{
	"foo": 1,
	"bar": true,
	"baz": "application",
}).Info("loggers support structured information")
Output:

func WithLogrus

func WithLogrus(log logrus.FieldLogger) Logger

WithLogrus provides a log handler using the flexibility-oriented "logrus" library.

Example
// Setup base logger component
lg := logrus.New()
lg.SetLevel(logrus.DebugLevel)
lg.SetOutput(os.Stdout)
lg.SetFormatter(&logrus.TextFormatter{
	DisableColors:    true,
	DisableTimestamp: true,
})

// Create logger instance
log := WithLogrus(lg)

// Use log handler
log.Debug("use log handler now")
log.WithFields(Fields{
	"foo": 1,
	"bar": true,
	"baz": "application",
}).Info("loggers support structured information")
Output:

func WithStandard

func WithStandard(log *stdL.Logger) Logger

WithStandard provides a log handler using only standard library packages.

Example
// Setup a logger using the standard library package
// Renamed to "stdL" in this example.
ll := stdL.New(os.Stdout, "", 0)

// Create logger instance
log := WithStandard(ll)

// Use log handler
log.Debug("use log handler now")
log.WithFields(Fields{
	"foo": 1,
	"bar": true,
	"baz": "application",
}).Info("loggers support structured information")
Output:

func WithZap

func WithZap(log *zap.Logger) Logger

WithZap provides a log handler using the performance-oriented "zap" library.

More information: https://github.com/uber-go/zap
Example
// Setup zap instance
zz, _ := zap.NewProduction()
defer func() {
	_ = zz.Sync()
}()

// Create logger instance
log := WithZap(zz)

// Use log handler
log.Debug("use log handler now")
log.WithFields(Fields{
	"foo": 1,
	"bar": true,
	"baz": "application",
}).Info("loggers support structured information")
Output:

func WithZero

func WithZero(options ZeroOptions) Logger

WithZero provides a log h using the zerolog library.

More information: https://github.com/rs/zerolog
Example
// Create logger instance
log := WithZero(ZeroOptions{
	PrettyPrint: true,
	ErrorField:  "error",
})

// Use log handler
log.Debug("use log handler now")
log.WithFields(Fields{
	"foo": 1,
	"bar": true,
	"baz": "application",
}).Info("loggers support structured information")
Output:

type SimpleLogger

type SimpleLogger interface {
	// Debug logs a basic 'debug' level message.
	// Information broadly interesting to developers and system administrators.
	// Might include minor (recoverable) failures and issues indicating potential
	// performance problems.
	Debug(args ...interface{})

	// Debugf logs a formatted 'debug' level message.
	// Information broadly interesting to developers and system administrators.
	// Might include minor (recoverable) failures and issues indicating potential
	// performance problems.
	Debugf(format string, args ...interface{})

	// Info logs a basic 'info' level message.
	// Informational messages that might make sense to end users and system
	// administrators, and highlight the progress of the application.
	Info(args ...interface{})

	// Infof logs a formatted 'info' level message.
	// Informational messages that might make sense to end users and system
	// administrators, and highlight the progress of the application.
	Infof(format string, args ...interface{})

	// Warning logs a 'warning' level message.
	// Potentially harmful situations of interest to end users or system managers
	// that indicate potential problems.
	Warning(args ...interface{})

	// Warningf logs a formatted 'warning' level message.
	// Potentially harmful situations of interest to end users or system managers
	// that indicate potential problems.
	Warningf(format string, args ...interface{})

	// Error logs an 'error' level message.
	// Events of considerable importance that will prevent normal program execution,
	// but might still allow the application to continue running.
	Error(args ...interface{})

	// Errorf logs a formatted 'error' level message.
	// Events of considerable importance that will prevent normal program execution,
	// but might still allow the application to continue running.
	Errorf(format string, args ...interface{})

	// Panic logs a 'panic' level message.
	// Very severe error events that might cause the application to terminate.
	// Usually by calling panic() after logging.
	Panic(args ...interface{})

	// Panicf logs a formatted 'panic' level message.
	// Very severe error events that might cause the application to terminate.
	// Usually by calling panic() after logging.
	Panicf(format string, args ...interface{})

	// Fatal logs a 'fatal' level message.
	// Very severe error events that WILL cause the application to terminate.
	// Usually by calling os.Exit(1) after logging.
	Fatal(args ...interface{})

	// Fatalf logs a formatted 'fatal' level message.
	// Very severe error events that WILL cause the application to terminate.
	// Usually by calling os.Exit(1) after logging.
	Fatalf(format string, args ...interface{})
}

SimpleLogger defines the requirements of the log handler as a minimal interface to allow for easy customization and prevent hard dependencies on a specific implementation. Logs are managed at 6 distinct levels: Debug, Info, Warning, Error, Panic and Fatal.

type ZeroOptions

type ZeroOptions struct {
	// Whether to print messages in a textual representation. If not enabled
	// messages are logged in a structured (JSON) format by default. This
	// value is only applied when writing to console, if a custom `Sink` is
	// provided the messages are always submitted in JSON format.
	PrettyPrint bool

	// ErrorField is the field name used to display error messages. When
	// using pretty print on a color-enabled console, the field will be
	// highlighted by default for readability. If not provided, `error`
	// will be used by default.
	ErrorField string

	// A destination for all produced messages. This can be a file, network
	// connection, or any other element supporting the `io.Writer` interface.
	// If no sink is specified `os.Stdout` will be used by default.
	Sink io.Writer
}

ZeroOptions defines the available settings to adjust the behavior of a logger instance backed by the `zerolog` library.

Jump to

Keyboard shortcuts

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