logg

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Nov 6, 2023 License: MIT Imports: 9 Imported by: 53

README

Tests on Linux, MacOS and Windows Go Report Card GoDoc

This is a fork of the exellent Apex Log library.

Main changes:

  • Trim unneeded dependencies.
  • Make Fields into a slice to preserve log order.
  • Split the old Interface in two and remove all but one Log method (see below).
  • This allows for lazy creation of messages in Log(fmt.Stringer) and ignoring fields added in LevelLoggers with levels below the Loggers.
  • The pointer passed to HandleLog is not safe to use outside of the current log chain, and needs to be cloned with Clone first if that's needed.
  • See Benchmarks for more info.

This is probably the very fastest structured log library when logging is disabled:

image

One can never have enough log libraries!

// Logger is the main interface for the logger.
type Logger interface {
	// WithLevel returns a new entry with `level` set.
	WithLevel(Level) *Entry
}

// LevelLogger is the logger at a given level.
type LevelLogger interface {
	// Log logs a message at the given level using the string from calling s.String().
	// Note that s.String() will not be called if the level is not enabled.
	Log(s fmt.Stringer)

	// Logf logs a message at the given level using the format and args from calling fmt.Sprintf().
	// Note that fmt.Sprintf() will not be called if the level is not enabled.
	Logf(format string, a ...any)

	// WithLevel returns a new entry with `level` set.
	WithLevel(Level) *Entry

	// WithFields returns a new entry with the`fields` in fields set.
	// This is a noop if LevelLogger's level is less than Logger's.
	WithFields(fields Fielder) *Entry

	// WithLevel returns a new entry with the field f set with value v
	// This is a noop if LevelLogger's level is less than Logger's.
	WithField(f string, v any) *Entry

	// WithDuration returns a new entry with the "duration" field set
	// to the given duration in milliseconds.
	// This is a noop if LevelLogger's level is less than Logger's.
	WithDuration(time.Duration) *Entry

	// WithError returns a new entry with the "error" set to `err`.
	// This is a noop if err is nil or  LevelLogger's level is less than Logger's.
	WithError(error) *Entry
}

Benchmarks

Benchmarks below are borrowed and adapted from Zap.

Logging at a disabled level without any structured context

name                                      time/op
DisabledWithoutFields/apex/log-10         33.9ns ± 0%
DisabledWithoutFields/bep/logg-10         0.28ns ± 0%
DisabledWithoutFields/sirupsen/logrus-10  6.54ns ± 0%
DisabledWithoutFields/rs/zerolog-10       0.31ns ± 0%

name                                      alloc/op
DisabledWithoutFields/apex/log-10           112B ± 0%
DisabledWithoutFields/bep/logg-10          0.00B
DisabledWithoutFields/sirupsen/logrus-10   16.0B ± 0%
DisabledWithoutFields/rs/zerolog-10        0.00B

name                                      allocs/op
DisabledWithoutFields/apex/log-10           1.00 ± 0%
DisabledWithoutFields/bep/logg-10           0.00
DisabledWithoutFields/sirupsen/logrus-10    1.00 ± 0%
DisabledWithoutFields/rs/zerolog-10         0.00

Logging at a disabled level with some accumulated context

name                                           time/op
DisabledAccumulatedContext/apex/log-10         0.29ns ± 0%
DisabledAccumulatedContext/bep/logg-10         0.27ns ± 0%
DisabledAccumulatedContext/sirupsen/logrus-10  6.61ns ± 0%
DisabledAccumulatedContext/rs/zerolog-10       0.32ns ± 0%

name                                           alloc/op
DisabledAccumulatedContext/apex/log-10          0.00B
DisabledAccumulatedContext/bep/logg-10          0.00B
DisabledAccumulatedContext/sirupsen/logrus-10   16.0B ± 0%
DisabledAccumulatedContext/rs/zerolog-10        0.00B

name                                           allocs/op
DisabledAccumulatedContext/apex/log-10           0.00
DisabledAccumulatedContext/bep/logg-10           0.00
DisabledAccumulatedContext/sirupsen/logrus-10    1.00 ± 0%
DisabledAccumulatedContext/rs/zerolog-10         0.00

Logging at a disabled level, adding context at each log site

name                                     time/op
DisabledAddingFields/apex/log-10          328ns ± 0%
DisabledAddingFields/bep/logg-10         0.38ns ± 0%
DisabledAddingFields/sirupsen/logrus-10   610ns ± 0%
DisabledAddingFields/rs/zerolog-10       10.5ns ± 0%

name                                     alloc/op
DisabledAddingFields/apex/log-10           886B ± 0%
DisabledAddingFields/bep/logg-10          0.00B
DisabledAddingFields/sirupsen/logrus-10  1.52kB ± 0%
DisabledAddingFields/rs/zerolog-10        24.0B ± 0%

name                                     allocs/op
DisabledAddingFields/apex/log-10           10.0 ± 0%
DisabledAddingFields/bep/logg-10           0.00
DisabledAddingFields/sirupsen/logrus-10    12.0 ± 0%
DisabledAddingFields/rs/zerolog-10         1.00 ± 0%

Logging without any structured context

name                                    time/op
WithoutFields/apex/log-10                964ns ± 0%
WithoutFields/bep/logg-10                100ns ± 0%
WithoutFields/go-kit/kit/log-10          232ns ± 0%
WithoutFields/inconshreveable/log15-10  2.13µs ± 0%
WithoutFields/sirupsen/logrus-10         866ns ± 0%
WithoutFields/stdlib.Println-10         7.08ns ± 0%
WithoutFields/stdlib.Printf-10          56.4ns ± 0%
WithoutFields/rs/zerolog-10             30.9ns ± 0%
WithoutFields/rs/zerolog.Formatting-10  1.33µs ± 0%
WithoutFields/rs/zerolog.Check-10       32.1ns ± 0%

name                                    alloc/op
WithoutFields/apex/log-10                 352B ± 0%
WithoutFields/bep/logg-10                56.0B ± 0%
WithoutFields/go-kit/kit/log-10           520B ± 0%
WithoutFields/inconshreveable/log15-10  1.43kB ± 0%
WithoutFields/sirupsen/logrus-10        1.14kB ± 0%
WithoutFields/stdlib.Println-10          16.0B ± 0%
WithoutFields/stdlib.Printf-10            136B ± 0%
WithoutFields/rs/zerolog-10              0.00B
WithoutFields/rs/zerolog.Formatting-10  1.92kB ± 0%
WithoutFields/rs/zerolog.Check-10        0.00B

name                                    allocs/op
WithoutFields/apex/log-10                 6.00 ± 0%
WithoutFields/bep/logg-10                 2.00 ± 0%
WithoutFields/go-kit/kit/log-10           9.00 ± 0%
WithoutFields/inconshreveable/log15-10    20.0 ± 0%
WithoutFields/sirupsen/logrus-10          23.0 ± 0%
WithoutFields/stdlib.Println-10           1.00 ± 0%
WithoutFields/stdlib.Printf-10            6.00 ± 0%
WithoutFields/rs/zerolog-10               0.00
WithoutFields/rs/zerolog.Formatting-10    58.0 ± 0%
WithoutFields/rs/zerolog.Check-10         0.00

Logging with some accumulated context

name                                         time/op
AccumulatedContext/apex/log-10               12.7µs ± 0%
AccumulatedContext/bep/logg-10               1.52µs ± 0%
AccumulatedContext/go-kit/kit/log-10         2.52µs ± 0%
AccumulatedContext/inconshreveable/log15-10  9.36µs ± 0%
AccumulatedContext/sirupsen/logrus-10        3.41µs ± 0%
AccumulatedContext/rs/zerolog-10             37.9ns ± 0%
AccumulatedContext/rs/zerolog.Check-10       34.0ns ± 0%
AccumulatedContext/rs/zerolog.Formatting-10  1.36µs ± 0%

name                                         alloc/op
AccumulatedContext/apex/log-10               3.30kB ± 0%
AccumulatedContext/bep/logg-10               1.16kB ± 0%
AccumulatedContext/go-kit/kit/log-10         3.67kB ± 0%
AccumulatedContext/inconshreveable/log15-10  3.31kB ± 0%
AccumulatedContext/sirupsen/logrus-10        4.73kB ± 0%
AccumulatedContext/rs/zerolog-10              0.00B
AccumulatedContext/rs/zerolog.Check-10        0.00B
AccumulatedContext/rs/zerolog.Formatting-10  1.92kB ± 0%

name                                         allocs/op
AccumulatedContext/apex/log-10                 53.0 ± 0%
AccumulatedContext/bep/logg-10                 25.0 ± 0%
AccumulatedContext/go-kit/kit/log-10           56.0 ± 0%
AccumulatedContext/inconshreveable/log15-10    70.0 ± 0%
AccumulatedContext/sirupsen/logrus-10          68.0 ± 0%
AccumulatedContext/rs/zerolog-10               0.00
AccumulatedContext/rs/zerolog.Check-10         0.00
AccumulatedContext/rs/zerolog.Formatting-10    58.0 ± 0%

Logging with additional context at each log site

name                                   time/op
AddingFields/apex/log-10               13.2µs ± 0%
AddingFields/bep/logg-10               1.79µs ± 0%
AddingFields/go-kit/kit/log-10         2.23µs ± 0%
AddingFields/inconshreveable/log15-10  14.3µs ± 0%
AddingFields/sirupsen/logrus-10        4.46µs ± 0%
AddingFields/rs/zerolog-10              398ns ± 0%
AddingFields/rs/zerolog.Check-10        389ns ± 0%

name                                   alloc/op
AddingFields/apex/log-10               4.19kB ± 0%
AddingFields/bep/logg-10               2.02kB ± 0%
AddingFields/go-kit/kit/log-10         3.31kB ± 0%
AddingFields/inconshreveable/log15-10  6.68kB ± 0%
AddingFields/sirupsen/logrus-10        6.27kB ± 0%
AddingFields/rs/zerolog-10              24.0B ± 0%
AddingFields/rs/zerolog.Check-10        24.0B ± 0%

name                                   allocs/op
AddingFields/apex/log-10                 63.0 ± 0%
AddingFields/bep/logg-10                 34.0 ± 0%
AddingFields/go-kit/kit/log-10           57.0 ± 0%
AddingFields/inconshreveable/log15-10    74.0 ± 0%
AddingFields/sirupsen/logrus-10          79.0 ± 0%
AddingFields/rs/zerolog-10               1.00 ± 0%
AddingFields/rs/zerolog.Check-10         1.00 ± 0%

Documentation

Overview

package logg implements a simple structured logging API.

Example
package main

import (
	"bytes"
	"fmt"

	"github.com/bep/logg"
	"github.com/bep/logg/handlers/text"
)

func main() {
	var buff bytes.Buffer
	// Create a new logger.
	l := logg.New(
		logg.Options{
			Level:   logg.LevelInfo,
			Handler: text.New(&buff, text.Options{Separator: " "}),
		},
	)
	// Create a new log context.
	infoLogger := l.WithLevel(logg.LevelInfo)

	// Logg some user activity.
	userLogger := infoLogger.WithField("user", "foo").WithField("id", "123")
	userLogger.Log(logg.String("logged in"))
	userLogger.WithField("file", "jokes.txt").Log(logg.String("uploaded"))
	userLogger.WithField("file", "morejokes.txt").Log(logg.String("uploaded"))

	fmt.Print(buff.String())

}
Output:

INFO logged in user=foo id=123
INFO uploaded user=foo id=123 file=jokes.txt
INFO uploaded user=foo id=123 file=morejokes.txt
Example (Lazy_evaluation)
package main

import (
	"bytes"
	"fmt"
	"strings"
	"time"

	"github.com/bep/logg"
	"github.com/bep/logg/handlers/text"
)

func main() {
	var buff bytes.Buffer
	// Create a new logger.
	l := logg.New(
		logg.Options{
			Level:   logg.LevelError,
			Handler: text.New(&buff, text.Options{Separator: "|"}),
		},
	)

	errorLogger := l.WithLevel(logg.LevelError)

	// Info is below the logger's level, so
	// nothing will be printed.
	infoLogger := l.WithLevel(logg.LevelInfo)

	// Simulate a busy loop.
	for i := 0; i < 999; i++ {
		ctx := infoLogger.WithFields(
			logg.NewFieldsFunc(
				// This func will never be invoked with the current logger's level.
				func() logg.Fields {
					return logg.Fields{
						{"field", strings.Repeat("x", 9999)},
					}

				}),
		)
		ctx.Log(logg.StringFunc(
			// This func will never be invoked with the current logger's level.
			func() string {
				return "log message: " + strings.Repeat("x", 9999)
			},
		))

	}

	errorLogger.WithDuration(32 * time.Second).Log(logg.String("something took too long"))

	fmt.Print(buff.String())

}
Output:

ERROR|something took too long|duration=32000

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrInvalidLevel = errors.New("invalid level")

ErrInvalidLevel is returned if the severity level is invalid.

View Source
var ErrStopLogEntry = fmt.Errorf("stop log entry")

ErrStopLogEntry is a sentinel error that can be returned from a handler to stop the entry from being passed to the next handler.

Functions

This section is empty.

Types

type Clock

type Clock interface {
	Now() time.Time
}

Clock provides the current time.

type Entry

type Entry struct {
	Level     Level     `json:"level"`
	Timestamp time.Time `json:"timestamp"`
	Fields    Fields    `json:"fields,omitempty"`
	Message   string    `json:"message"`
	// contains filtered or unexported fields
}

Entry represents a single log entry at a given log level.

func NewEntry

func NewEntry(log *logger) *Entry

NewEntry returns a new entry for `log`.

func (*Entry) Clone

func (e *Entry) Clone() *Entry

Clone returns a new Entry with the same fields.

func (*Entry) Log

func (e *Entry) Log(s fmt.Stringer)

Log a message at the given level.

func (*Entry) Logf added in v0.2.0

func (e *Entry) Logf(format string, a ...any)

Log a message at the given level.

func (*Entry) WithDuration

func (e *Entry) WithDuration(d time.Duration) *Entry

func (*Entry) WithError

func (e *Entry) WithError(err error) *Entry

WithError returns a new entry with the "error" set to `err`.

The given error may implement .Fielder, if it does the method will add all its `.Fields()` into the returned entry.

func (*Entry) WithField

func (e *Entry) WithField(key string, value any) *Entry

func (*Entry) WithFields

func (e *Entry) WithFields(fielder Fielder) *Entry

func (Entry) WithLevel

func (e Entry) WithLevel(level Level) *Entry

type Field

type Field struct {
	Name  string `json:"name"`
	Value any    `json:"value"`
}

Field holds a named value.

type Fielder

type Fielder interface {
	Fields() Fields
}

Fielder is an interface for providing fields to custom types.

type Fields

type Fields []Field

Fields represents a slice of entry level data used for structured logging.

func (Fields) Fields

func (f Fields) Fields() Fields

Fields implements Fielder.

type FieldsFunc

type FieldsFunc func() Fields

func NewFieldsFunc

func NewFieldsFunc(fn func() Fields) FieldsFunc

func (FieldsFunc) Fields

func (f FieldsFunc) Fields() Fields

type Handler

type Handler interface {
	// HandleLog is invoked for each log event.
	// Note that if the Entry is going to be used after the call to HandleLog
	// in the handler chain returns, it must be cloned with Clone(). See
	// the memory.Handler implementation for an example.
	//
	// The Entry can be modified if needed, e.g. when passed down via
	// a multi.Handler (e.g. to sanitize the data).
	HandleLog(e *Entry) error
}

Handler is used to handle log events, outputting them to stdio or sending them to remote services. See the "handlers" directory for implementations.

It is left up to Handlers to implement thread-safety.

type HandlerFunc

type HandlerFunc func(*Entry) error

The HandlerFunc type is an adapter to allow the use of ordinary functions as log handlers. If f is a function with the appropriate signature, HandlerFunc(f) is a Handler object that calls f.

func (HandlerFunc) HandleLog

func (f HandlerFunc) HandleLog(e *Entry) error

HandleLog calls f(e).

type Level

type Level int

Level of severity.

const (
	LevelInvalid Level = iota
	LevelTrace
	LevelDebug
	LevelInfo
	LevelWarn
	LevelError
)

Log levels.

func MustParseLevel

func MustParseLevel(s string) Level

MustParseLevel parses level string or panics.

func ParseLevel

func ParseLevel(s string) (Level, error)

ParseLevel parses level string.

func (Level) MarshalJSON

func (l Level) MarshalJSON() ([]byte, error)

MarshalJSON implementation.

func (Level) String

func (l Level) String() string

String implementation.

func (*Level) UnmarshalJSON

func (l *Level) UnmarshalJSON(b []byte) error

UnmarshalJSON implementation.

type LevelLogger

type LevelLogger interface {
	// Log logs a message at the given level using the string from calling s.String().
	// Note that s.String() will not be called if the level is not enabled.
	Log(s fmt.Stringer)

	// Logf logs a message at the given level using the format and args from calling fmt.Sprintf().
	// Note that fmt.Sprintf() will not be called if the level is not enabled.
	Logf(format string, a ...any)

	// WithLevel returns a new entry with `level` set.
	WithLevel(Level) *Entry

	// WithFields returns a new entry with the`fields` in fields set.
	// This is a noop if LevelLogger's level is less than Logger's.
	WithFields(fields Fielder) *Entry

	// WithLevel returns a new entry with the field f set with value v
	// This is a noop if LevelLogger's level is less than Logger's.
	WithField(f string, v any) *Entry

	// WithDuration returns a new entry with the "duration" field set
	// to the given duration in milliseconds.
	// This is a noop if LevelLogger's level is less than Logger's.
	WithDuration(time.Duration) *Entry

	// WithError returns a new entry with the "error" set to `err`.
	// This is a noop if err is nil or  LevelLogger's level is less than Logger's.
	WithError(error) *Entry
}

LevelLogger is the logger at a given level.

type Logger

type Logger interface {
	// WithLevel returns a new entry with `level` set.
	WithLevel(Level) *Entry
}

Logger is the main interface for the logger.

func New

func New(cfg Options) Logger

New returns a new logger.

type Options

type Options struct {
	// Level is the minimum level to log at.
	// If not set, defaults to InfoLevel.
	Level Level

	// Handler is the log handler to use.
	Handler Handler

	// Clock is the clock to use for timestamps.
	// If not set, the system clock is used.
	Clock Clock
}

Options is the set of options used to configure a logger.

type String

type String string

String implements fmt.Stringer and can be used directly in the log methods.

func (String) String

func (s String) String() string

type StringFunc

type StringFunc func() string

StringFunc is a function that returns a string. It also implements the fmt.Stringer interface and can therefore be used as argument to the log methods.

func (StringFunc) String

func (f StringFunc) String() string

Directories

Path Synopsis
benchmarks module
cli
Package cli implements a colored text handler suitable for command-line interfaces.
Package cli implements a colored text handler suitable for command-line interfaces.
json
Package json implements a JSON handler.
Package json implements a JSON handler.
level
Package level implements a level filter handler.
Package level implements a level filter handler.
memory
Package memory implements an in-memory handler useful for testing, as the entries can be accessed after writes.
Package memory implements an in-memory handler useful for testing, as the entries can be accessed after writes.
multi
Package multi implements a handler which invokes a number of handlers.
Package multi implements a handler which invokes a number of handlers.
text
Package text implements a development-friendly textual handler.
Package text implements a development-friendly textual handler.

Jump to

Keyboard shortcuts

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