log

package module
v0.0.0-...-b02d349 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 2020 License: MIT Imports: 10 Imported by: 136

README

Build Status Coverage Status Go Report Card GoDoc

Introduction

This project bridges several gaps that are present in the standard logging support in Go:

  • Equips errors with stacktraces and provides a facility for printing them
  • Inherently supports the ability for each Go file to print its messages with a prefix representing that file/package
  • Adds some functions to specifically log messages of different levels (e.g. debug, error)
  • Adds a PanicIf() function that can be used to conditionally manage errors depending on whether an error variable is nil or actually has an error
  • Adds support for pluggable logging adapters (so the output can be sent somewhere other than the console)
  • Adds configuration (such as the logging level or adapter) that can be driven from the environment
  • Supports filtering to show/hide the logging of certain places of the application
  • The loggers can be definded at the package level, so you can determine which Go file any log message came from.

When used with the Panic-Defer-Recover pattern in Go, even panics rising from the Go runtime will be caught and wrapped with a stacktrace. This compartmentalizes which function they could have originated from, which is, otherwise, potentially non-trivial to figure out.

AppEngine

Go under AppEngine is very stripped down, such as there being no logging type (e.g. Logger in native Go) and there is no support for prefixing. As each logging call from this project takes a Context, this works cooperatively to bridge the additional gaps in AppEngine's logging support.

With standard console logging outside of this context, that parameter will take anil.

Getting Started

The simplest, possible example:

package thispackage

import (
    "context"
    "errors"

    "github.com/dsoprea/go-logging/v2"
)

var (
    thisfileLog = log.NewLogger("thispackage.thisfile")
)

func a_cry_for_help(ctx context.Context) {
    err := errors.New("a big error")
    thisfileLog.Errorf(ctx, err, "How big is my problem: %s", "pretty big")
}

func init() {
    cla := log.NewConsoleLogAdapter()
    log.AddAdapter("console", cla)
}

Notice two things:

  1. We register the "console" adapter at the bottom. The first adapter registered will be used by default.
  2. We pass-in a prefix (what we refer to as a "noun") to log.NewLogger(). This is a simple, descriptive name that represents the subject of the file. By convention, we construct this by dot-separating the current package and the name of the file. We recommend that you define a different log for every file at the package level, but it is your choice whether you want to do this or share the same logger over the entire package, define one in each struct, etc..

Example Output

Example output from a real application (not from the above):

2016/09/09 12:57:44 DEBUG: user: User revisiting: [test@example.com]
2016/09/09 12:57:44 DEBUG: context: Session already inited: [DCRBDGRY6RMWANCSJXVLD7GULDH4NZEB6SBAQ3KSFIGA2LP45IIQ]
2016/09/09 12:57:44 DEBUG: session_data: Session save not necessary: [DCRBDGRY6RMWANCSJXVLD7GULDH4NZEB6SBAQ3KSFIGA2LP45IIQ]
2016/09/09 12:57:44 DEBUG: context: Got session: [DCRBDGRY6RMWANCSJXVLD7GULDH4NZEB6SBAQ3KSFIGA2LP45IIQ]
2016/09/09 12:57:44 DEBUG: session_data: Found user in session.
2016/09/09 12:57:44 DEBUG: cache: Cache miss: [geo.geocode.reverse:dhxp15x]

Adapters

This project provides one built-in logging adapter, "console", which prints to the screen. To register it:

cla := log.NewConsoleLogAdapter()
log.AddAdapter("console", cla)

Custom Adapters

If you would like to implement your own logger, just create a struct type that satisfies the LogAdapter interface.

type LogAdapter interface {
    Debugf(lc *LogContext, message *string) error
    Infof(lc *LogContext, message *string) error
    Warningf(lc *LogContext, message *string) error
    Errorf(lc *LogContext, message *string) error
}

The LogContext struct passed in provides additional information that you may need in order to do what you need to do:

type LogContext struct {
    Logger *Logger
    Ctx context.Context
}

Logger represents your Logger instance.

Adapter example:

type DummyLogAdapter struct {

}

func (dla *DummyLogAdapter) Debugf(lc *LogContext, message *string) error {

}

func (dla *DummyLogAdapter) Infof(lc *LogContext, message *string) error {

}

func (dla *DummyLogAdapter) Warningf(lc *LogContext, message *string) error {

}

func (dla *DummyLogAdapter) Errorf(lc *LogContext, message *string) error {

}

Then, register it:

func init() {
    log.AddAdapter("dummy", new(DummyLogAdapter))
}

If this is a task-specific implementation, just register it from the init() of the file that defines it.

If this is the first adapter you've registered, it will be the default one used. Otherwise, you'll have to deliberately specify it when you are creating a logger: Instead of calling log.NewLogger(noun string), call log.NewLoggerWithAdapterName(noun string, adapterName string).

We discuss how to configure the adapter from configuration in the "Configuration" section below.

Adapter Notes

  • The Logger instance exports Noun() in the event you want to discriminate where your log entries go in your adapter. It also exports Adapter() for if you need to access the adapter instance from your application.
  • If no adapter is registered (specifically, the default adapter-name remains empty), logging calls will be a no-op. This allows libraries to implement go-logging where the larger application doesn't.

Filters

We support the ability to exclusively log for a specific set of nouns (we'll exclude any not specified):

log.AddIncludeFilter("nountoshow1")
log.AddIncludeFilter("nountoshow2")

Depending on your needs, you might just want to exclude a couple and include the rest:

log.AddExcludeFilter("nountohide1")
log.AddExcludeFilter("nountohide2")

We'll first hit the include-filters. If it's in there, we'll forward the log item to the adapter. If not, and there is at least one include filter in the list, we won't do anything. If the list of include filters is empty but the noun appears in the exclude list, we won't do anything.

It is a good convention to exclude the nouns of any library you are writing whose logging you do not want to generally be aware of unless you are debugging. You might call AddExcludeFilter() from the init() function at the bottom of those files unless there is some configuration variable, such as "(LibraryNameHere)DoShowLogging", that has been defined and set to TRUE.

Configuration

The following configuration items are available:

  • Format: The default format used to build the message that gets sent to the adapter. It is assumed that the adapter already prefixes the message with time and log-level (since the default AppEngine logger does). The default value is: {{.Noun}}: [{{.Level}}] {{if eq .ExcludeBypass true}} [BYPASS]{{end}} {{.Message}}. The available tokens are "Level", "Noun", "ExcludeBypass", and "Message".
  • DefaultAdapterName: The default name of the adapter to use when NewLogger() is called (if this isn't defined then the name of the first registered adapter will be used).
  • LevelName: The priority-level of messages permitted to be logged (all others will be discarded). By default, it is "info". Other levels are: "debug", "warning", "error", "critical"
  • IncludeNouns: Comma-separated list of nouns to log for. All others will be ignored.
  • ExcludeNouns: Comma-separated list on nouns to exclude from logging.
  • ExcludeBypassLevelName: The log-level at which we will show logging for nouns that have been excluded. Allows you to hide excessive, unimportant logging for nouns but to still see their warnings, errors, etc...

Configuration Providers

You provide the configuration by setting a configuration-provider. Configuration providers must satisfy the ConfigurationProvider interface. The following are provided with the project:

  • EnvironmentConfigurationProvider: Read values from the environment.
  • StaticConfigurationProvider: Set values directly on the struct.

The configuration provider must be applied before doing any logging (otherwise it will have no effect).

Environments such as AppEngine work best with EnvironmentConfigurationProvider as this is generally how configuration is exposed by AppEngine to the application. You can define this configuration directly in that configuration.

By default, no configuration-provider is applied, the level is defaulted to INFO and the format is defaulted to "{{.Noun}}:{{if eq .ExcludeBypass true}} [BYPASS]{{end}} {{.Message}}".

Again, if a configuration-provider does not provide a log-level or format, they will be defaulted (or left alone, if already set). If it does not provide an adapter-name, the adapter-name of the first registered adapter will be used.

Usage instructions of both follow.

Environment-Based Configuration

ecp := log.NewEnvironmentConfigurationProvider()
log.LoadConfiguration(ecp)

Each of the items listed at the top of the "Configuration" section can be specified in the environment using a prefix of "Log" (e.g. LogDefaultAdapterName).

Static Configuration

scp := log.NewStaticConfigurationProvider()
scp.SetLevelName(log.LevelNameWarning)

log.LoadConfiguration(scp)

Documentation

Index

Constants

View Source
const (
	LevelDebug   = iota
	LevelInfo    = iota
	LevelWarning = iota
	LevelError   = iota
)

Config severity integers.

View Source
const (
	LevelNameDebug   = "debug"
	LevelNameInfo    = "info"
	LevelNameWarning = "warning"
	LevelNameError   = "error"
)

Config severity names.

Variables

Seveirty name->integer map.

View Source
var (
	ErrAdapterAlreadyRegistered = e.New("adapter already registered")
	ErrFormatEmpty              = e.New("format is empty")
	ErrExcludeLevelNameInvalid  = e.New("exclude bypass-level is invalid")
	ErrNoAdapterConfigured      = e.New("no default adapter configured")
	ErrAdapterIsNil             = e.New("adapter is nil")
	ErrConfigurationNotLoaded   = e.New("can not configure because configuration is not loaded")
)

Errors

Functions

func AddAdapter

func AddAdapter(name string, la LogAdapter)

func AddExcludeFilter

func AddExcludeFilter(noun string)

Add global exclude filter.

func AddIncludeFilter

func AddIncludeFilter(noun string)

Add global include filter.

func ClearAdapters

func ClearAdapters()

func Errorf

func Errorf(message string, args ...interface{}) *errors.Error

func GetDefaultAdapterName

func GetDefaultAdapterName() string

Return the current default adapter name.

func Is

func Is(actual, against error) bool

Is checks if the left ("actual") error equals the right ("against") error. The right must be an unwrapped error (the kind that you'd initialize as a global variable). The left can be a wrapped or unwrapped error.

func IsConfigurationLoaded

func IsConfigurationLoaded() bool

func LoadConfiguration

func LoadConfiguration(cp ConfigurationProvider)

func Panic

func Panic(err interface{})

func PanicIf

func PanicIf(err interface{})

func Panicf

func Panicf(message string, args ...interface{})

func PrintError

func PrintError(err error)

Print is a utility function to prevent the caller from having to import the third-party library.

func PrintErrorf

func PrintErrorf(err error, format string, args ...interface{})

PrintErrorf is a utility function to prevent the caller from having to import the third-party library.

func RemoveExcludeFilter

func RemoveExcludeFilter(noun string)

Remove global exclude filter.

func RemoveIncludeFilter

func RemoveIncludeFilter(noun string)

Remove global include filter.

func SetDefaultAdapterName

func SetDefaultAdapterName(name string)

The adapter will automatically be the first one registered. This overrides that.

func Wrap

func Wrap(err interface{}) *errors.Error

Types

type ConfigurationProvider

type ConfigurationProvider interface {
	// Alternative format (defaults to .
	Format() string

	// Alternative adapter (defaults to "appengine").
	DefaultAdapterName() string

	// Alternative level at which to display log-items (defaults to
	// "info").
	LevelName() string

	// Configuration-driven comma-separated list of nouns to include. Defaults
	// to empty.
	IncludeNouns() string

	// Configuration-driven comma-separated list of nouns to exclude. Defaults
	// to empty.
	ExcludeNouns() string

	// Level at which to disregard exclusion (if the severity of a message
	// meets or exceed this, always display). Defaults to empty.
	ExcludeBypassLevelName() string
}

type ConsoleLogAdapter

type ConsoleLogAdapter struct {
}

func (*ConsoleLogAdapter) Debugf

func (cla *ConsoleLogAdapter) Debugf(lc *LogContext, message *string) error

func (*ConsoleLogAdapter) Errorf

func (cla *ConsoleLogAdapter) Errorf(lc *LogContext, message *string) error

func (*ConsoleLogAdapter) Infof

func (cla *ConsoleLogAdapter) Infof(lc *LogContext, message *string) error

func (*ConsoleLogAdapter) Warningf

func (cla *ConsoleLogAdapter) Warningf(lc *LogContext, message *string) error

type EnvironmentConfigurationProvider

type EnvironmentConfigurationProvider struct {
}

Environment configuration-provider.

func NewEnvironmentConfigurationProvider

func NewEnvironmentConfigurationProvider() *EnvironmentConfigurationProvider

func (*EnvironmentConfigurationProvider) DefaultAdapterName

func (ecp *EnvironmentConfigurationProvider) DefaultAdapterName() string

func (*EnvironmentConfigurationProvider) ExcludeBypassLevelName

func (ecp *EnvironmentConfigurationProvider) ExcludeBypassLevelName() string

func (*EnvironmentConfigurationProvider) ExcludeNouns

func (ecp *EnvironmentConfigurationProvider) ExcludeNouns() string

func (*EnvironmentConfigurationProvider) Format

func (*EnvironmentConfigurationProvider) IncludeNouns

func (ecp *EnvironmentConfigurationProvider) IncludeNouns() string

func (*EnvironmentConfigurationProvider) LevelName

func (ecp *EnvironmentConfigurationProvider) LevelName() string

type LogAdapter

type LogAdapter interface {
	Debugf(lc *LogContext, message *string) error
	Infof(lc *LogContext, message *string) error
	Warningf(lc *LogContext, message *string) error
	Errorf(lc *LogContext, message *string) error
}

func NewConsoleLogAdapter

func NewConsoleLogAdapter() LogAdapter

type LogContext

type LogContext struct {
	Logger *Logger
	Ctx    context.Context
}

type LogMethod

type LogMethod func(lc *LogContext, message *string) error

type Logger

type Logger struct {
	// contains filtered or unexported fields
}

func NewLogger

func NewLogger(noun string) (l *Logger)

func NewLoggerWithAdapterName

func NewLoggerWithAdapterName(noun string, adapterName string) (l *Logger)

func (*Logger) Adapter

func (l *Logger) Adapter() LogAdapter

func (*Logger) Debugf

func (l *Logger) Debugf(ctx context.Context, format string, args ...interface{})

func (*Logger) ErrorIff

func (l *Logger) ErrorIff(ctx context.Context, errRaw interface{}, format string, args ...interface{})

func (*Logger) Errorf

func (l *Logger) Errorf(ctx context.Context, errRaw interface{}, format string, args ...interface{})

func (*Logger) Infof

func (l *Logger) Infof(ctx context.Context, format string, args ...interface{})

func (*Logger) Noun

func (l *Logger) Noun() string

func (*Logger) PanicIff

func (l *Logger) PanicIff(ctx context.Context, errRaw interface{}, format string, args ...interface{})

func (*Logger) Panicf

func (l *Logger) Panicf(ctx context.Context, errRaw interface{}, format string, args ...interface{})

func (*Logger) Warningf

func (l *Logger) Warningf(ctx context.Context, format string, args ...interface{})

type MessageContext

type MessageContext struct {
	Level         *string
	Noun          *string
	Message       *string
	ExcludeBypass bool
}

TODO(dustin): !! Also populate whether we've bypassed an exception so that

we can add a template macro to prefix an exclamation of
some sort.

type StaticConfigurationProvider

type StaticConfigurationProvider struct {
	// contains filtered or unexported fields
}

Static configuration-provider.

func NewStaticConfigurationProvider

func NewStaticConfigurationProvider() *StaticConfigurationProvider

func (*StaticConfigurationProvider) DefaultAdapterName

func (scp *StaticConfigurationProvider) DefaultAdapterName() string

func (*StaticConfigurationProvider) ExcludeBypassLevelName

func (scp *StaticConfigurationProvider) ExcludeBypassLevelName() string

func (*StaticConfigurationProvider) ExcludeNouns

func (scp *StaticConfigurationProvider) ExcludeNouns() string

func (*StaticConfigurationProvider) Format

func (scp *StaticConfigurationProvider) Format() string

func (*StaticConfigurationProvider) IncludeNouns

func (scp *StaticConfigurationProvider) IncludeNouns() string

func (*StaticConfigurationProvider) LevelName

func (scp *StaticConfigurationProvider) LevelName() string

func (*StaticConfigurationProvider) SetDefaultAdapterName

func (scp *StaticConfigurationProvider) SetDefaultAdapterName(adapterName string)

func (*StaticConfigurationProvider) SetExcludeBypassLevelName

func (scp *StaticConfigurationProvider) SetExcludeBypassLevelName(excludeBypassLevelName string)

func (*StaticConfigurationProvider) SetExcludeNouns

func (scp *StaticConfigurationProvider) SetExcludeNouns(excludeNouns string)

func (*StaticConfigurationProvider) SetFormat

func (scp *StaticConfigurationProvider) SetFormat(format string)

func (*StaticConfigurationProvider) SetIncludeNouns

func (scp *StaticConfigurationProvider) SetIncludeNouns(includeNouns string)

func (*StaticConfigurationProvider) SetLevelName

func (scp *StaticConfigurationProvider) SetLevelName(levelName string)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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