log

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

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

Go to latest
Published: Dec 26, 2019 License: MIT Imports: 17 Imported by: 0

README

demo

logxi

log XI is a structured 12-factor app logger built for speed and happy development.

  • Simpler. Sane no-configuration defaults out of the box.
  • Faster. See benchmarks vs logrus and log15.
  • Structured. Key-value pairs are enforced. Logs JSON in production.
  • Configurable. Enable/disalbe Loggers and levels via env vars.
  • Friendlier. Happy, colorful and developer friendly logger in terminal.
  • Helpul. Traces, warnings and errors are emphasized with file, line number and callstack.
  • Efficient. Has level guards to avoid cost of building complex arguments.
Requirements
Go 1.3+
Installation
go get -u github.com/aa-ar/logxi
Getting Started
import "github.com/aa-ar/logxi"

// create package variable for Logger interface
var logger log.Logger

func main() {
    // use default logger
    who := "mario"
    log.Info("Hello", "who", who)

    // create a logger with a unique identifier which
    // can be enabled from environment variables
    logger = log.New("pkg")

    // specify a writer, use NewConcurrentWriter if it is not concurrent
    // safe
    modelLogger = log.NewLogger(log.NewConcurrentWriter(os.Stdout), "models")

    db, err := sql.Open("postgres", "dbname=testdb")
    if err != nil {
        modelLogger.Error("Could not open database", "err", err)
    }

    fruit := "apple"
    languages := []string{"go", "javascript"}
    if log.IsDebug() {
        // use key-value pairs after message
        logger.Debug("OK", "fruit", fruit, "languages", languages)
    }
}

logxi defaults to showing warnings and above. To view all logs

LOGXI=* go run main.go

Highlights

This logger package

  • Is fast in production environment

    A logger should be efficient and minimize performance tax. logxi encodes JSON 2X faster than logrus and log15 with primitive types. When diagnosing a problem in production, troubleshooting often means enabling small trace data in Debug and Info statements for some period of time.

    # primitive types
    BenchmarkLogxi          100000    20021 ns/op   2477 B/op    66 allocs/op
    BenchmarkLogrus          30000    46372 ns/op   8991 B/op   196 allocs/op
    BenchmarkLog15           20000    62974 ns/op   9244 B/op   236 allocs/op
    
    # nested object
    BenchmarkLogxiComplex    30000    44448 ns/op   6416 B/op   190 allocs/op
    BenchmarkLogrusComplex   20000    65006 ns/op  12231 B/op   278 allocs/op
    BenchmarkLog15Complex    20000    92880 ns/op  13172 B/op   311 allocs/op
    
  • Is developer friendly in the terminal. The HappyDevFormatter is colorful, prints file and line numbers for traces, warnings and errors. Arguments are printed in the order they are coded. Errors print the call stack.

    HappyDevFormatter is not too concerned with performance and delegates to JSONFormatter internally.

  • Logs machine parsable output in production environments. The default formatter for non terminals is JSONFormatter.

    TextFormatter may also be used which is MUCH faster than JSON but there is no guarantee it can be easily parsed.

  • Has level guards to avoid the cost of building arguments. Get in the habit of using guards.

    if log.IsDebug() {
        log.Debug("some ", "key1", expensive())
    }
    
  • Conforms to a logging interface so it can be replaced.

    type Logger interface {
        Trace(msg string, args ...interface{})
        Debug(msg string, args ...interface{})
        Info(msg string, args ...interface{})
        Warn(msg string, args ...interface{}) error
        Error(msg string, args ...interface{}) error
        Fatal(msg string, args ...interface{})
        Log(level int, msg string, args []interface{})
    
        SetLevel(int)
        IsTrace() bool
        IsDebug() bool
        IsInfo() bool
        IsWarn() bool
        // Error, Fatal not needed, those SHOULD always be logged
    }
    
  • Standardizes on key-value pair argument sequence

log.Debug("inside Fn()", "key1", value1, "key2", value2)

// instead of this log.WithFields(logrus.Fields{"m": "pkg", "key1": value1, "key2": value2}).Debug("inside fn()")

    logxi logs `FIX_IMBALANCED_PAIRS =>` if key-value pairs are imbalanced

    `log.Warn and log.Error` are special cases and return error:

    ```go
return log.Error(msg)               //=> fmt.Errorf(msg)
return log.Error(msg, "err", err)   //=> err
  • Supports Color Schemes (256 colors)

    log.New creates a logger that supports color schemes

    logger := log.New("mylog")
    

    To customize scheme

    # emphasize errors with white text on red background
    LOGXI_COLORS="ERR=white:red" yourapp
    
    # emphasize errors with pink = 200 on 256 colors table
    LOGXI_COLORS="ERR=200" yourapp
    
  • Is suppressable in unit tests

func TestErrNotFound() { log.Suppress(true) defer log.Suppress(false) ... }




## Configuration

### Enabling/Disabling Loggers

By default logxi logs entries whose level is `LevelWarn` or above when
using a terminal. For non-terminals, entries with level `LevelError` and
above are logged.

To quickly see all entries use short form

    # enable all, disable log named foo
    LOGXI=*,-foo yourapp

To better control logs in production, use long form which allows
for granular control of levels

    # the above statement is equivalent to this
    LOGXI=*=DBG,foo=OFF yourapp

`DBG` should obviously not be used in production unless for
troubleshooting. See `LevelAtoi` in `logger.go` for values.
For example, there is a problem in the data access layer
in production.

    # Set all to Error and set data related packages to Debug
    LOGXI=*=ERR,models=DBG,dat*=DBG,api=DBG yourapp

### Format

The format may be set via `LOGXI_FORMAT` environment
variable. Valid values are `"happy", "text", "JSON", "LTSV"`

    # Use JSON in production with custom time
    LOGXI_FORMAT=JSON,t=2006-01-02T15:04:05.000000-0700 yourapp

The "happy" formatter has more options

*   pretty - puts each key-value pair indented on its own line

    "happy" default to fitting key-value pair onto the same line. If
    result characters are longer than `maxcol` then the pair will be
    put on the next line and indented

*   maxcol - maximum number of columns before forcing a key to be on its
    own line. If you want everything on a single line, set this to high
    value like 1000. Default is 80.

*   context - the number of context lines to print on source. Set to -1
    to see only file:lineno. Default is 2.


### Color Schemes

The color scheme may be set with `LOGXI_COLORS` environment variable. For
example, the default dark scheme is emulated like this

    # on non-Windows, see Windows support below
    export LOGXI_COLORS=key=cyan+h,value,misc=blue+h,source=magenta,TRC,DBG,WRN=yellow,INF=green,ERR=red+h
    yourapp

    # color only errors
    LOGXI_COLORS=ERR=red yourapp

See [ansi](http://github.com/mgutz/ansi) package for styling. An empty
value, like "value" and "DBG" above means use default foreground and
background on terminal.

Keys

*   \*  - default color
*   TRC - trace color
*   DBG - debug color
*   WRN - warn color
*   INF - info color
*   ERR - error color
*   message - message color
*   key - key color
*   value - value color unless WRN or ERR
*   misc - time and log name color
*   source - source context color (excluding error line)

#### Windows

Use [ConEmu-Maximus5](https://github.com/Maximus5/ConEmu).
Read this page about [256 colors](https://code.google.com/p/conemu-maximus5/wiki/Xterm256Colors).

Colors in PowerShell and Command Prompt _work_ but not very pretty.

## Extending

What about hooks? There are least two ways to do this

*   Implement your own `io.Writer` to write to external services. Be sure to set
    the formatter to JSON to faciliate decoding with Go's built-in streaming
    decoder.
*   Create an external filter. See `v1/cmd/filter` as an example.

What about log rotation? 12 factor apps only concern themselves with
STDOUT. Use shell redirection operators to write to a file.

There are many utilities to rotate logs which accept STDIN as input. They can
do many things like send alerts, etc. The two obvious choices are Apache's `rotatelogs`
utility and `lograte`.

```sh
yourapp | rotatelogs yourapp 86400

Testing

# install godo task runner
go get -u gopkg.in/godo.v2/cmd/godo

# install dependencies
godo install -v

# run test
godo test

# run bench with allocs (requires manual cleanup of output)
godo bench-allocs

License

MIT License

Documentation

Index

Constants

View Source
const (
	// LevelEnv chooses level from LOGXI environment variable or defaults
	// to LevelInfo
	LevelEnv = -10000

	// LevelOff means logging is disabled for logger. This should always
	// be first
	LevelOff = -1000

	// LevelEmergency is usually 0 but that is also the "zero" value
	// for Go, which means whenever we do any lookup in string -> int
	// map 0 is returned (not good).
	LevelEmergency = -1

	// LevelAlert means action must be taken immediately.
	LevelAlert = 1

	// LevelFatal means it should be corrected immediately, eg cannot connect to database.
	LevelFatal = 2

	// LevelCritical is alias for LevelFatal
	LevelCritical = 2

	// LevelError is a non-urgen failure to notify devlopers or admins
	LevelError = 3

	// LevelWarn indiates an error will occur if action is not taken, eg file system 85% full
	LevelWarn = 4

	// LevelNotice is normal but significant condition.
	LevelNotice = 5

	// LevelInfo is info level
	LevelInfo = 6

	// LevelDebug is debug level
	LevelDebug = 7

	// LevelTrace is trace level and displays file and line in terminal
	LevelTrace = 10

	// LevelAll is all levels
	LevelAll = 1000
)
View Source
const FormatEnv = ""

FormatEnv selects formatter based on LOGXI_FORMAT environment variable

View Source
const FormatHappy = "happy"

FormatHappy uses HappyDevFormatter

View Source
const FormatJSON = "JSON"

FormatJSON uses JSONFormatter

View Source
const FormatText = "text"

FormatText uses TextFormatter

View Source
const Version = "1.0.0-pre"

Version is the version of this package

Variables

View Source
var AssignmentChar = ": "

The assignment character between key-value pairs

View Source
var KeyMap = &KeyMapping{
	Level:     "_l",
	Message:   "_m",
	Name:      "_n",
	PID:       "_p",
	Time:      "_t",
	CallStack: "_c",
}

KeyMap is the key map to use when printing log statements.

View Source
var LevelAtoi = map[string]int{
	"OFF": LevelOff,
	"FTL": LevelFatal,
	"ERR": LevelError,
	"WRN": LevelWarn,
	"INF": LevelInfo,
	"DBG": LevelDebug,
	"TRC": LevelTrace,
	"ALL": LevelAll,

	"off":   LevelOff,
	"fatal": LevelFatal,
	"error": LevelError,
	"warn":  LevelWarn,
	"info":  LevelInfo,
	"debug": LevelDebug,
	"trace": LevelTrace,
	"all":   LevelAll,
}

LevelMap maps int enums to string level.

View Source
var LevelMap = map[int]string{
	LevelFatal: "FTL",
	LevelError: "ERR",
	LevelWarn:  "WRN",
	LevelInfo:  "INF",
	LevelDebug: "DBG",
	LevelTrace: "TRC",
}

LevelMap maps int enums to string level.

View Source
var NullLog = &NullLogger{}

NullLog is a noop logger. Think of it as /dev/null.

View Source
var Separator = " "

Separator is the separator to use between key value pairs var Separator = "{~}"

Functions

func Debug

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

Debug logs a debug statement.

func Error

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

Error logs an error statement with callstack.

func Fatal

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

Fatal logs a fatal statement.

func Info

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

Info logs an info statement.

func IsDebug

func IsDebug() bool

IsDebug determines if this logger logs a debug statement.

func IsInfo

func IsInfo() bool

IsInfo determines if this logger logs an info statement.

func IsTrace

func IsTrace() bool

IsTrace determines if this logger logs a trace statement.

func IsWarn

func IsWarn() bool

IsWarn determines if this logger logs a warning statement.

func NewConcurrentWriter

func NewConcurrentWriter(writer io.Writer) io.Writer

NewConcurrentWriter crates a new concurrent writer wrapper around existing writer.

func ProcessEnv

func ProcessEnv(env *Configuration)

ProcessEnv (re)processes environment.

func ProcessLogxiColorsEnv

func ProcessLogxiColorsEnv(env string)

ProcessLogxiColorsEnv parases LOGXI_COLORS

func ProcessLogxiEnv

func ProcessLogxiEnv(env string)

ProcessLogxiEnv parses LOGXI variable

func ProcessLogxiFormatEnv

func ProcessLogxiFormatEnv(env string)

ProcessLogxiFormatEnv parses LOGXI_FORMAT

func RegisterFormatFactory

func RegisterFormatFactory(kind string, fn CreateFormatterFunc)

RegisterFormatFactory registers a format factory function.

func Suppress

func Suppress(quiet bool)

Suppress supresses logging and is useful to supress output in in unit tests.

Example log.Suppress(true) defer log.suppress(false)

func Trace

func Trace(msg string, args ...interface{})

Trace logs a trace statement. On terminals file and line number are logged.

func Warn

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

Warn logs a warning statement. On terminals it logs file and line number.

Types

type BufferPool

type BufferPool struct {
	sync.Pool
}

func NewBufferPool

func NewBufferPool() *BufferPool

func (*BufferPool) Get

func (bp *BufferPool) Get() *bytes.Buffer

func (*BufferPool) Put

func (bp *BufferPool) Put(b *bytes.Buffer)

type ConcurrentWriter

type ConcurrentWriter struct {
	sync.Mutex
	// contains filtered or unexported fields
}

ConcurrentWriter is a concurrent safe wrapper around io.Writer

func (*ConcurrentWriter) Write

func (cw *ConcurrentWriter) Write(p []byte) (n int, err error)

type Configuration

type Configuration struct {
	Format string `json:"format"`
	Colors string `json:"colors"`
	Levels string `json:"levels"`
}

Configuration comes from environment or external services like consul, etcd.

type CreateFormatterFunc

type CreateFormatterFunc func(name, kind string) (Formatter, error)

CreateFormatterFunc is a function which creates a new instance of a Formatter.

type DefaultLogger

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

DefaultLogger is the default logger for this package.

func (*DefaultLogger) Debug

func (l *DefaultLogger) Debug(msg string, args ...interface{})

Debug logs a debug entry.

func (*DefaultLogger) Error

func (l *DefaultLogger) Error(msg string, args ...interface{}) error

Error logs an error entry.

func (*DefaultLogger) Fatal

func (l *DefaultLogger) Fatal(msg string, args ...interface{})

Fatal logs a fatal entry then panics.

func (*DefaultLogger) Info

func (l *DefaultLogger) Info(msg string, args ...interface{})

Info logs an info entry.

func (*DefaultLogger) IsDebug

func (l *DefaultLogger) IsDebug() bool

IsDebug determines if this logger logs a debug statement.

func (*DefaultLogger) IsInfo

func (l *DefaultLogger) IsInfo() bool

IsInfo determines if this logger logs an info statement.

func (*DefaultLogger) IsTrace

func (l *DefaultLogger) IsTrace() bool

IsTrace determines if this logger logs a debug statement.

func (*DefaultLogger) IsWarn

func (l *DefaultLogger) IsWarn() bool

IsWarn determines if this logger logs a warning statement.

func (*DefaultLogger) Log

func (l *DefaultLogger) Log(level int, msg string, args []interface{})

Log logs a leveled entry.

func (*DefaultLogger) SetFormatter

func (l *DefaultLogger) SetFormatter(formatter Formatter)

SetFormatter set the formatter for this logger.

func (*DefaultLogger) SetLevel

func (l *DefaultLogger) SetLevel(level int)

SetLevel sets the level of this logger.

func (*DefaultLogger) Trace

func (l *DefaultLogger) Trace(msg string, args ...interface{})

Trace logs a debug entry.

func (*DefaultLogger) Warn

func (l *DefaultLogger) Warn(msg string, args ...interface{}) error

Warn logs a warn entry.

type Formatter

type Formatter interface {
	Format(writer io.Writer, level int, msg string, args []interface{})
}

Formatter records log entries.

type HappyDevFormatter

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

HappyDevFormatter is the formatter used for terminals. It is colorful, dev friendly and provides meaningful logs when warnings and errors occur.

HappyDevFormatter does not worry about performance. It's at least 3-4X slower than JSONFormatter since it delegates to JSONFormatter to marshal then unmarshal JSON. Then it does other stuff like read source files, sort keys all to give a developer more information.

SHOULD NOT be used in production for extended period of time. However, it works fine in SSH terminals and binary deployments.

func NewHappyDevFormatter

func NewHappyDevFormatter(name string) *HappyDevFormatter

NewHappyDevFormatter returns a new instance of HappyDevFormatter.

func (*HappyDevFormatter) Format

func (hd *HappyDevFormatter) Format(writer io.Writer, level int, msg string, args []interface{})

Format a log entry.

type JSONFormatter

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

JSONFormatter is a fast, efficient JSON formatter optimized for logging.

  • log entry keys are not escaped Who uses complex keys when coding? Checked by HappyDevFormatter in case user does. Nested object keys are escaped by json.Marshal().
  • Primitive types uses strconv
  • Logger reserved key values (time, log name, level) require no conversion
  • sync.Pool buffer for bytes.Buffer

func NewJSONFormatter

func NewJSONFormatter(name string) *JSONFormatter

NewJSONFormatter creates a new instance of JSONFormatter.

func (*JSONFormatter) Format

func (jf *JSONFormatter) Format(writer io.Writer, level int, msg string, args []interface{})

Format formats log entry as JSON.

func (*JSONFormatter) LogEntry

func (jf *JSONFormatter) LogEntry(level int, msg string, args []interface{}) map[string]interface{}

LogEntry returns the JSON log entry object built by Format(). Used by HappyDevFormatter to ensure any data logged while developing properly logs in production.

type KeyMapping

type KeyMapping struct {
	Level     string
	Message   string
	Name      string
	PID       string
	Time      string
	CallStack string
}

KeyMapping is the key map used to print built-in log entry fields.

type Logger

type Logger interface {
	Trace(msg string, args ...interface{})
	Debug(msg string, args ...interface{})
	Info(msg string, args ...interface{})
	Warn(msg string, args ...interface{}) error
	Error(msg string, args ...interface{}) error
	Fatal(msg string, args ...interface{})
	Log(level int, msg string, args []interface{})

	SetLevel(int)
	IsTrace() bool
	IsDebug() bool
	IsInfo() bool
	IsWarn() bool
}

Logger is the interface for logging.

var DefaultLog Logger

DefaultLogLog is the default log for this package.

var InternalLog Logger

internalLog is the logger used by logxi itself

func New

func New(name string) Logger

New creates a colorable default logger.

func NewLogger

func NewLogger(writer io.Writer, name string) Logger

NewLogger creates a new default logger. If writer is not concurrent safe, wrap it with NewConcurrentWriter.

func NewLogger3

func NewLogger3(writer io.Writer, name string, formatter Formatter) Logger

NewLogger3 creates a new logger with a writer, name and formatter. If writer is not concurrent safe, wrap it with NewConcurrentWriter.

type NullLogger

type NullLogger struct{}

NullLogger is the default logger for this package.

func (*NullLogger) Debug

func (l *NullLogger) Debug(msg string, args ...interface{})

Debug logs a debug entry.

func (*NullLogger) Error

func (l *NullLogger) Error(msg string, args ...interface{}) error

Error logs an error entry.

func (*NullLogger) Fatal

func (l *NullLogger) Fatal(msg string, args ...interface{})

Fatal logs a fatal entry then panics.

func (*NullLogger) Info

func (l *NullLogger) Info(msg string, args ...interface{})

Info logs an info entry.

func (*NullLogger) IsDebug

func (l *NullLogger) IsDebug() bool

IsDebug determines if this logger logs a debug statement.

func (*NullLogger) IsInfo

func (l *NullLogger) IsInfo() bool

IsInfo determines if this logger logs an info statement.

func (*NullLogger) IsTrace

func (l *NullLogger) IsTrace() bool

IsTrace determines if this logger logs a trace statement.

func (*NullLogger) IsWarn

func (l *NullLogger) IsWarn() bool

IsWarn determines if this logger logs a warning statement.

func (*NullLogger) Log

func (l *NullLogger) Log(level int, msg string, args []interface{})

Log logs a leveled entry.

func (*NullLogger) SetFormatter

func (l *NullLogger) SetFormatter(formatter Formatter)

SetFormatter set the formatter for this logger.

func (*NullLogger) SetLevel

func (l *NullLogger) SetLevel(level int)

SetLevel sets the level of this logger.

func (*NullLogger) Trace

func (l *NullLogger) Trace(msg string, args ...interface{})

Trace logs a debug entry.

func (*NullLogger) Warn

func (l *NullLogger) Warn(msg string, args ...interface{}) error

Warn logs a warn entry.

type TextFormatter

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

TextFormatter is the default recorder used if one is unspecified when creating a new Logger.

func NewTextFormatter

func NewTextFormatter(name string) *TextFormatter

NewTextFormatter returns a new instance of TextFormatter. SetName must be called befored using it.

func (*TextFormatter) Format

func (tf *TextFormatter) Format(writer io.Writer, level int, msg string, args []interface{})

Format records a log entry.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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