gournal

package module
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Feb 23, 2018 License: Apache-2.0 Imports: 7 Imported by: 60

README

Gournal GoDoc Build Status Go Report Card codecov

Gournal (pronounced "Journal") is a Context-aware logging framework that introduces the Google Context type as a first-class parameter to all common log functions such as Info, Debug, etc.

Getting Started

Instead of being Yet Another Go Log library, Gournal actually takes its inspiration from the Simple Logging Facade for Java (SLF4J). Gournal is not attempting to replace anyone's favorite logger, rather existing logging frameworks such as Logrus, Zap, etc. can easily participate as a Gournal Appender.

The following example is a simple program that uses Logrus as a Gournal Appender to emit some log data:

package main

import (
	"context"

	"github.com/akutz/gournal"
	"github.com/akutz/gournal/logrus"
)

func main() {
	ctx := context.Background()
	ctx = context.WithValue(ctx, gournal.LevelKey(), gournal.InfoLevel)
	ctx = context.WithValue(ctx, gournal.AppenderKey(), logrus.New())

	gournal.Info(ctx, "Hello %s", "Bob")

	gournal.WithFields(map[string]interface{}{
		"size":     1,
		"location": "Austin",
	}).Warn(ctx, "Hello %s", "Mary")
}

To run the above example, clone this project and execute the following from the command line:

$ go run ./examples/01/main.go
INFO[0000] Hello Bob                                    
WARN[0000] Hello Mary                                    location=Austin size=1

Compatability

Gournal provides ready-to-use Appenders for the following logging frameworks:

With little overhead, Gournal leverages the Google Context type to provide an elegant solution to the absence of features that are commonly found in languages that employ thread-local storage. And not only that, but using Gournal helps avoid logger-lock-in. Replacing the underlying implementation of a Gournal Appender is as simple as placing a different Appender object into the Context.

Performance

Gournal has minimal impact on the performance of the underlying logger framework.

Benchmark | Logger | Time | Malloc Size | Malloc Count -----|--------|-----------|------|-------------|------------- Native without Fields | gournal.Logger | 1024 ns/op | 16 B/op | 1 allocs/op | Logrus | 4118 ns/op | 832 B/op | 19 allocs/op | Zap | 1347 ns/op | 0 B/op | 0 allocs/op Gournal without Fields | gournal.Logger | 1230 ns/op | 16 B/op | 1 allocs/op | Logrus | 3784 ns/op | 832 B/op | 19 allocs/op | Zap | 1448 ns/op | 0 B/op | 0 allocs/op Gournal with Fields | gournal.Logger | 4424 ns/op | 881 B/op | 18 allocs/op | Logrus | 6467 ns/op | 1746 B/op | 31 allocs/op | Zap | 3160 ns/op | 641 B/op | 8 allocs/op

The above benchmark information (results may vary) was generated using the following command:

$ go test ./benchmarks -bench . -benchmem 2> /dev/null
goos: darwin
goarch: amd64
pkg: github.com/akutz/gournal/benchmarks
BenchmarkNativeStdLibWithoutFields-8    	 1000000	      1416 ns/op	      16 B/op	       1 allocs/op
BenchmarkNativeLogrusWithoutFields-8    	  500000	      2205 ns/op	     336 B/op	      13 allocs/op
BenchmarkNativeZapWithoutFields-8       	 1000000	      1456 ns/op	       0 B/op	       0 allocs/op
BenchmarkGournalStdLibWithoutFields-8   	 1000000	      1571 ns/op	      16 B/op	       1 allocs/op
BenchmarkGournalLogrusWithoutFields-8   	 1000000	      2131 ns/op	     336 B/op	      13 allocs/op
BenchmarkGournalZapWithoutFields-8      	 1000000	      1481 ns/op	       0 B/op	       0 allocs/op
BenchmarkGournalStdLibWithFields-8      	  500000	      2998 ns/op	     945 B/op	      19 allocs/op
BenchmarkGournalLogrusWithFields-8      	  500000	      3519 ns/op	    1483 B/op	      30 allocs/op
BenchmarkGournalZapWithFields-8         	  500000	      2446 ns/op	     721 B/op	      10 allocs/op
PASS
ok  	github.com/akutz/gournal/benchmarks	13.887s

Configuration

Gournal is configured primarily via the Context instances supplied to the various logging functions. However, if a supplied argument is nil or is missing the Appender or Level, there are some default, global variables that can supplement the missing pieces.

Global Variable Default Value Description
DefaultLevel ErrorLevel Used when a Level is not present in a Context.
DefaultAppender nil Used when an Appender is not present in a Context.
DefaultContext context.Background() Used when a log method is invoked with a nil Context.

Please note that there is no default value for DefaultAppender. If this field is not assigned and log function is invoked with a nil Context or one absent an Appender object, a panic will occur.

Features

Gournal provides several features on top of the underlying logging framework that is doing the actual logging:

Concurrent Logging Frameworks

The following example illustrates how to utilize the Gournal DefaultAppender as well as multiple logging frameworks in the same program:

package main

import (
	"context"

	"github.com/akutz/gournal"
	"github.com/akutz/gournal/logrus"
	"github.com/akutz/gournal/zap"
)

func main() {
	// Make a Zap-based Appender the default appender for when one is not
	// present in a Context, or when a nill Context is provided to a logging
	// function.
	gournal.DefaultAppender = zap.New()

	// The following call fails to provide a valid Context argument. In this
	// case the DefaultAppender is used.
	gournal.WithFields(map[string]interface{}{
		"size":     2,
		"location": "Boston",
	}).Error(nil, "Hello %s", "Bob")

	ctx := context.Background()
	ctx = context.WithValue(ctx, gournal.LevelKey(), gournal.InfoLevel)

	// Even though this next call provides a valid Context, there is no
	// Appender present in the Context so the DefaultAppender will be used.
	gournal.Info(ctx, "Hello %s", "Mary")

	ctx = context.WithValue(ctx, gournal.AppenderKey(), logrus.New())

	// This last log function uses a Context that has been created with a
	// Logrus Appender. Even though the DefaultAppender is assigned and is a
	// Zap-based logger, this call will utilize the Context Appender instance,
	// a Logrus Appender.
	gournal.WithFields(map[string]interface{}{
		"size":     1,
		"location": "Austin",
	}).Warn(ctx, "Hello %s", "Alice")
}

To run the above example, clone this project and execute the following from the command line:

$ go run ./examples/02/main.go
{"level":"error","ts":1470251785.437946,"msg":"Hello Bob","size":2,"location":"Boston"}
{"level":"info","ts":1470251785.4379828,"msg":"Hello Mary"}
WARN[0000] Hello Alice                                   location=Austin size=1
Global Context Fields

Another nifty feature of Gournal is the ability to provide a Context with fields that will get emitted along-side every log message, whether they are explicitly provided with log message or not. This feature is illustrated in the example below:

package main

import (
	"context"

	"github.com/akutz/gournal"
	"github.com/akutz/gournal/logrus"
)

func main() {
	ctx := context.Background()
	ctx = context.WithValue(ctx, gournal.LevelKey(), gournal.InfoLevel)
	ctx = context.WithValue(ctx, gournal.AppenderKey(), logrus.New())

	ctx = context.WithValue(
		ctx,
		gournal.FieldsKey(),
		map[string]interface{}{
			"name":  "Venus",
			"color": 0x00ff00,
		})

	// The following log entry will print the message and the name and color
	// of the planet.
	gournal.Info(ctx, "Discovered planet")

	ctx = context.WithValue(
		ctx,
		gournal.FieldsKey(),
		func() map[string]interface{} {
			return map[string]interface{}{
				"galaxy":   "Milky Way",
				"distance": 42,
			}
		})

	// The following log entry will print the message and the galactic location
	// and distance of the planet.
	gournal.Info(ctx, "Discovered planet")

	// Create a Context with the FieldsKey that points to a function which
	// returns a Context's derived fields based upon what data was provided
	// to a the log function.
	ctx = context.WithValue(
		ctx,
		gournal.FieldsKey(),
		func(ctx context.Context,
			lvl gournal.Level,
			fields map[string]interface{},
			args ...interface{}) map[string]interface{} {

			if v, ok := fields["z-value"].(int); ok {
				delete(fields, "z-value")
				return map[string]interface{}{
					"point": struct {
						x int
						y int
						z int
					}{1, -1, v},
				}
			}

			return map[string]interface{}{
				"point": struct {
					x int
					y int
				}{1, -1},
			}
		})

	// The following log entry will print the message and two-dimensional
	// location information about the planet.
	gournal.Info(ctx, "Discovered planet")

	// This log entry, however, will print the message and the same location
	// information, however, because the function used to derive the Context's
	// fields inspects the field's "z-value" key, it will add that data to the
	// location information, making it three-dimensional.
	gournal.WithField("z-value", 3).Info(ctx, "Discovered planet")
}

To run the above example, clone this project and execute the following from the command line:

$ go run ./examples/03/main.go
INFO[0000] Discovered planet                             color=65280 name=Venus
INFO[0000] Discovered planet                             distance=42 galaxy=Milky Way
INFO[0000] Discovered planet                             point={x:1 y:-1}
INFO[0000] Discovered planet                             point={x:1 y:-1 z:3}
Multiple Log Levels

Instead of creating multiple logger instances that exist and consume resources for no other reason than to have multiple log levels, Gournal supports multiple log levels as well as ensuring that no resources are wasted if a log entry does not meet the level qualifications:

package main

import (
	"context"
	"fmt"

	"github.com/akutz/gournal"
	"github.com/akutz/gournal/logrus"
)

// myString is a custom type that has a custom fmt.Format function.
// This function should *not* be invoked unless the log level is such that the
// log message would actually get emitted. This saves resources as fields
// and formatters are not invoked at all unless the log level allows an
// entry to be logged.
type myString string

func (s myString) Format(f fmt.State, c rune) {
	fmt.Println("* INVOKED MYSTRING FORMATTER")
	fmt.Fprint(f, string(s))
}

func main() {
	ctx := context.Background()
	ctx = context.WithValue(ctx, gournal.AppenderKey(), logrus.New())

	counter := 0

	// Set up a context fields callback that will print a loud message to the
	// console so it is very apparent when the function is invoked. This
	// function should *not* be invoked unless the log level is such that the
	// log message would actually get emitted. This saves resources as fields
	// and formatters are not invoked at all unless the log level allows an
	// entry to be logged.
	getCtxFieldsFunc := func() map[string]interface{} {
		counter++
		fmt.Println("* INVOKED CONTEXT FIELDS")
		return map[string]interface{}{"counter": counter}
	}
	ctx = context.WithValue(ctx, gournal.FieldsKey(), getCtxFieldsFunc)

	var name myString = "Bob"

	// Log "Hello Bob" at the INFO level. This log entry will not get emitted
	// because the default Gournal log level (configurable by
	// gournal.DefaultLevel) is ERROR.
	//
	// Additionally, we should *not* see the messages produced by the
	// myString.Format and getCtxFieldsFunc functions.
	gournal.Info(ctx, "Hello %s", name)

	// Keep a reference to the context that has the original log level.
	oldCtx := ctx

	// Set the context's log level to be INFO.
	ctx = context.WithValue(ctx, gournal.LevelKey(), gournal.InfoLevel)

	// Note the log level has been changed to INFO. This is also a marker to
	// show that the previous log and messages generated by the functions should
	// not have occurred prior to this statement in the terminal.
	fmt.Println("* CTX LOG LEVEL INFO")

	name = "Mary"

	fields := map[string]interface{}{
		"length":   8,
		"location": "Austin",
	}

	// Log "Hello Mary" with some field information. We should not only see
	// the messages from the myString.Format and getCtxFieldsFunc functions,
	// but the field "size" from the getCtxFieldsFunc function should add the
	// field "counter" to the fields provided directly to this call.
	gournal.WithFields(fields).Info(ctx, "Hello %s", name)

	// Log "Hello Mary" again with the exact same info, except use the original
	// context that did not have an explicit log level. Since the default log
	// level is still ERROR, nothing will be emitted, not even the messages that
	// indicate the myString.Format or getCtxFieldsFunc functions are being
	// invoked.
	gournal.WithFields(fields).Info(oldCtx, "Hello %s", name)

	// Update the default log level to INFO
	gournal.DefaultLevel = gournal.InfoLevel
	fmt.Println("* DEFAULT LOG LEVEL INFO")

	// Log "Hello Mary" again with the exact same info, even use the original
	// context that did not have an explicit log level. However, since the
	// default log level is now INFO, the entry will be emitted, along with the
	// messages from the myString.Format or getCtxFieldsFunc functions are being
	// invoked.
	//
	// Note the counter value has only be incremented once since the function
	// was not invoked when the log level did not permit the entry to be logged.
	gournal.WithFields(fields).Info(oldCtx, "Hello %s", name)
}

To run the above example, clone this project and execute the following from the command line:

$ go run ./examples/04/main.go
* CTX LOG LEVEL INFO
* INVOKED CONTEXT FIELDS
* INVOKED MYSTRING FORMATTER
INFO[0000] Hello Mary                                    counter=1 length=8 location=Austin
* DEFAULT LOG LEVEL INFO
* INVOKED CONTEXT FIELDS
* INVOKED MYSTRING FORMATTER
INFO[0000] Hello Mary                                    counter=2 length=8 location=Austin

Documentation

Overview

Package gournal (pronounced "Journal") is a Context-aware logging framework.

Gournal introduces the Google Context type (https://blog.golang.org/context) as a first-class parameter to all common log functions such as Info, Debug, etc.

Instead of being Yet Another Go Log library, Gournal actually takes its inspiration from the Simple Logging Facade for Java (SLF4J). Gournal is not attempting to replace anyone's favorite logger, rather existing logging frameworks such as Logrus, Zap, etc. can easily participate as a Gournal Appender.

For more information on Gournal's features or how to use it, please refer to the project's README file or https://github.com/akutz/gournal.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrorKey defines the key when adding errors using WithError.
	ErrorKey = "error"

	// DefaultLevel is used when a Level is not present in a Context.
	DefaultLevel = ErrorLevel

	// DefaultAppender is used when an Appender is not present in a Context.
	DefaultAppender = NewAppender()

	// DefaultContext is used when a log method is invoked with a nil Context.
	DefaultContext = context.Background()
)

Functions

func AppenderKey

func AppenderKey() interface{}

AppenderKey returns the Context key for storing and retrieving an Appender object.

func Debug

func Debug(ctx context.Context, msg string, args ...interface{})

Debug emits a log entry at the DEBUG level.

func Error

func Error(ctx context.Context, msg string, args ...interface{})

Error emits a log entry at the ERROR level.

func Fatal

func Fatal(ctx context.Context, msg string, args ...interface{})

Fatal emits a log entry at the FATAL level.

func FieldsKey

func FieldsKey() interface{}

FieldsKey returns the Context key for storing and retrieving Context-specific data that is appended along with each log entry. Three different types of data are inspected for this context key:

  • map[string]interface{}

  • func() map[string]interface{}

  • func(ctx context.Context, lvl Level, fields map[string]interface{}, msg string) map[string]interface{}

func Info

func Info(ctx context.Context, msg string, args ...interface{})

Info emits a log entry at the INFO level.

func LevelKey

func LevelKey() interface{}

LevelKey returns the Context key used for storing and retrieving the log level.

func Panic

func Panic(ctx context.Context, msg string, args ...interface{})

Panic emits a log entry at the PANIC level.

func Print

func Print(ctx context.Context, msg string, args ...interface{})

Print emits a log entry at the INFO level.

func Warn

func Warn(ctx context.Context, msg string, args ...interface{})

Warn emits a log entry at the WARN level.

Types

type Appender

type Appender interface {

	// Append is implemented by logging frameworks to accept the log entry
	// at the provided level, its message, and its associated field data.
	Append(
		ctx context.Context,
		lvl Level,
		fields map[string]interface{},
		msg string)
}

Appender is the interface that must be implemented by the logging frameworks which are members of the Gournal facade.

func NewAppender added in v0.2.0

func NewAppender() Appender

NewAppender returns an Appender that writes to os.Stdout.

func NewAppenderWithOptions added in v0.2.0

func NewAppenderWithOptions(w io.Writer) Appender

NewAppenderWithOptions returns an Appender that writes to the provided io.Writer object.

type Entry

type Entry interface {

	// WithField adds a single field to the Entry. The provided key will
	// override an existing, equivalent key in the Entry.
	WithField(key string, value interface{}) Entry

	// WithFields adds a map to the Entry. Keys in the provided map will
	// override existing, equivalent keys in the Entry.
	WithFields(fields map[string]interface{}) Entry

	// WithError adds the provided error to the Entry using the ErrorKey value
	// as the key.
	WithError(err error) Entry

	// Debug emits a log entry at the DEBUG level.
	Debug(ctx context.Context, msg string, args ...interface{})

	// Info emits a log entry at the INFO level.
	Info(ctx context.Context, msg string, args ...interface{})

	// Print emits a log entry at the INFO level.
	Print(ctx context.Context, msg string, args ...interface{})

	// Warn emits a log entry at the WARN level.
	Warn(ctx context.Context, msg string, args ...interface{})

	// Error emits a log entry at the ERROR level.
	Error(ctx context.Context, msg string, args ...interface{})

	// Fatal emits a log entry at the FATAL level.
	Fatal(ctx context.Context, msg string, args ...interface{})

	// Panic emits a log entry at the PANIC level.
	Panic(ctx context.Context, msg string, args ...interface{})
}

Entry is the interface for types that contain information to be emmitted to a log appender.

func WithError

func WithError(err error) Entry

WithError adds the provided error to the Entry using the ErrorKey value as the key.

func WithField

func WithField(key string, value interface{}) Entry

WithField adds a single field to the Entry. The provided key will override an existing, equivalent key in the Entry.

func WithFields

func WithFields(fields map[string]interface{}) Entry

WithFields adds a map to the Entry. Keys in the provided map will override existing, equivalent keys in the Entry.

type Level

type Level uint8

Level is a log level.

const (
	// UnknownLevel is an unknown level.
	UnknownLevel Level = iota

	// PanicLevel level, highest level of severity. Logs and then calls panic
	// with the message passed to Debug, Info, ...
	PanicLevel

	// FatalLevel level. Logs and then calls os.Exit(1). It will exit even
	// if the logging level is set to Panic.
	FatalLevel

	// ErrorLevel level. Logs. Used for errors that should definitely be noted.
	// Commonly used for hooks to send errors to an error tracking service.
	ErrorLevel

	// WarnLevel level. Non-critical entries that deserve eyes.
	WarnLevel

	// InfoLevel level. General operational entries about what's going on
	// inside the application.
	InfoLevel

	// DebugLevel level. Usually only enabled when debugging. Very verbose
	// logging.
	DebugLevel
)

These are the different logging levels.

func ParseLevel

func ParseLevel(lvl string) Level

ParseLevel parses a string and returns its constant.

func (Level) String

func (level Level) String() string

String returns string representation of a Level.

type Logger

type Logger interface {

	// Debug emits a log entry at the DEBUG level.
	Debug(msg string, args ...interface{})

	// Info emits a log entry at the INFO level.
	Info(msg string, args ...interface{})

	// Print emits a log entry at the INFO level.
	Print(msg string, args ...interface{})

	// Warn emits a log entry at the WARN level.
	Warn(msg string, args ...interface{})

	// Error emits a log entry at the ERROR level.
	Error(msg string, args ...interface{})

	// Fatal emits a log entry at the FATAL level.
	Fatal(msg string, args ...interface{})

	// Panic emits a log entry at the PANIC level.
	Panic(msg string, args ...interface{})
}

Logger provides backwards-compatibility for code that does not yet use context-aware logging.

func New

func New(ctx context.Context) Logger

New returns a Logger for the provided context.

Directories

Path Synopsis
examples
01
02
03
04
Package logrus provides a Logrus logger that implements the Gournal Appender interface.
Package logrus provides a Logrus logger that implements the Gournal Appender interface.
Package stdlib provides a StdLib logger that implements the Gournal Appender interface.
Package stdlib provides a StdLib logger that implements the Gournal Appender interface.
Package zap provides a Zap logger that implements the Gournal Appender interface.
Package zap provides a Zap logger that implements the Gournal Appender interface.

Jump to

Keyboard shortcuts

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