log

package
v0.0.13 Latest Latest
Warning

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

Go to latest
Published: Jan 18, 2020 License: MIT Imports: 15 Imported by: 48

README

Log

Convention

  • every package has a log registry and its default logger.
  • logger should only register themselves in registry if it's a long lived one, i.e. server

Usage

See examples in handlers

In package, create package level var called log this saves import and also avoid people importing standard log. Use dlog as import alias because log is already used.

package main

import (
	"os"
	"time"

	dlog "github.com/dyweb/gommon/log"
	"github.com/dyweb/gommon/log/handlers/cli"
)

var logReg = dlog.NewRegistry()
var log = logReg.Logger()

func main() {
	dlog.SetHandler(cli.New(os.Stderr, true))

	if len(os.Args) > 1 {
		if os.Args[1] == "nocolor" || os.Args[1] == "no" {
			dlog.SetHandler(cli.NewNoColor(os.Stderr))
		}
	}

	log.Info("hi")
	log.Infof("open file %s", "foo.yml")
	log.InfoF("open",
		dlog.Str("file", "foo.yml"),
		dlog.Int("mode", 0666),
	)
	log.Warn("I am yellow")
	func() {
		defer func() {
			if r := recover(); r != nil {
				log.Info("recovered", r)
			}
		}()
		log.Panic("I just want to panic")
	}()
	dlog.SetLevel(dlog.DebugLevel)
	log.Debug("I will sleep for a while")
	time.Sleep(500 * time.Millisecond)
	log.Fatal("I am red")
}

Documentation

Overview

Package log provides structured logging with fine grained control over packages using logger registry

TODO: add convention and usage

Index

Examples

Constants

View Source
const LevelEnvKey = "GOMMON_LOG_LEVEL"

LevelEnvKey is the environment variable name for setting default log level

View Source
const (
	MagicStructLoggerFunctionName = "LoggerIdentity"
)
View Source
const PrintLevel = InfoLevel

PrintLevel is for library/application that requires a Printf based logger interface

View Source
const UnknownFile = "<?>"

Variables

View Source
var UnknownIdentity = Identity{Package: "unk", Type: UnknownLogger}

Functions

func DisableSource added in v0.0.8

func DisableSource()

func EnableSource added in v0.0.8

func EnableSource()

func SetHandler added in v0.0.8

func SetHandler(handler Handler)

func SetLevel added in v0.0.8

func SetLevel(level Level)

func WalkLogger added in v0.0.8

func WalkLogger(cb func(l *Logger))

WalkLogger calls WalkRegistry and within each registry, walk in insert order of loggers

func WalkRegistry added in v0.0.8

func WalkRegistry(cb func(r *Registry))

WalkRegistry walks registry in globalRegistryGroup in sorted order of id (package path)

Types

type Caller added in v0.0.8

type Caller struct {
	// File is the full file path without any trimming, it would be UnknownFile if caller is not found
	File string
	Line int
}

Caller is the result from runtime.Caller

func EmptyCaller added in v0.0.8

func EmptyCaller() Caller

EmptyCaller is mainly used for testing handler, it contains a empty file and line 0

type Field

type Field struct {
	Key  string
	Type FieldType

	// values
	Int       int64
	Str       string
	Interface interface{}
}

Field is based on uber-go/zap https://github.com/uber-go/zap/blob/master/zapcore/field.go It can be treated as a Union, the value is stored in either Int, Str or Interface TODO: interface{} is actually not used ...

func Int added in v0.0.3

func Int(k string, v int) Field

Int creates a field with int value, it uses int64 internally

func Str added in v0.0.3

func Str(k string, v string) Field

Str creates a field with string value

func Stringer added in v0.0.3

func Stringer(k string, v fmt.Stringer) Field

Stringer calls the String() method and stores return value

type FieldType added in v0.0.3

type FieldType uint8

FieldType avoids doing type assertion or calling reflection TODO: difference between type assertion and reflection

const (
	UnknownType FieldType = iota
	IntType
	StringType
)

type Fields

type Fields []Field

Fields is a slice of Field

type Handler

type Handler interface {
	// HandleLog requires level, now, msg, all the others are optional
	// source is Caller which contains full file line TODO: pass frame instead of string so handler can use trace for error handling?
	// context are fields attached to the logger instance
	// fields are ad-hoc fields from logger method like DebugF(msg, fields)
	HandleLog(level Level, now time.Time, msg string, source Caller, context Fields, fields Fields)
	// Flush writes the buffer to underlying storage
	Flush()
}

Handler formats log message and writes to underlying storage, stdout, file, remote server etc. It MUST be thread safe because logger calls handler concurrently without any locking. There is NO log entry struct in gommon/log, which is used in many logging packages, the reason is if extra field is added to the interface, compiler would throw error on stale handler implementations.

func DefaultHandler

func DefaultHandler() Handler

DefaultHandler returns the singleton defaultHandler instance, which logs to stderr in text format TODO: should allow customize default handler like default level, i.e. use json as default handler

func MultiHandler added in v0.0.8

func MultiHandler(handlers ...Handler) Handler

MultiHandler creates a handler that duplicates the log to all the provided handlers, it runs in serial and don't handle panic

func NewTextHandler added in v0.0.8

func NewTextHandler(w io.Writer) Handler

NewTextHandler formats log in human readable format without any escape, thus it is NOT machine readable Default handler is a textHandler using os.Stderr

type HandlerFunc

type HandlerFunc func(level Level, now time.Time, msg string, source string, context Fields, fields Fields)

HandlerFunc is an adapter to allow use of ordinary functions as log entry handlers

func (HandlerFunc) HandleLog

func (f HandlerFunc) HandleLog(level Level, now time.Time, msg string, source string, context Fields, fields Fields)

TODO: why the receiver is value instead of pointer https://github.com/dyweb/gommon/issues/30 and what's the overhead

type Identity

type Identity struct {
	Package  string
	Function string
	Struct   string
	File     string
	Line     int
	Type     LoggerType
}

Identity is set based on logger's initialization location, it is close to, but NOT exactly same as location of actual log. It can be used to inspect logger when traverse and print package hierarchy. NOTE: logger hierarchy is flat instead of tree after https://github.com/dyweb/gommon/issues/110

func (*Identity) Diff

func (id *Identity) Diff(parent *Identity) string

TODO: this is used for print tree like structure ... it's hard to maintain exact parent and child logger due to cycle import

func (*Identity) Hash

func (id *Identity) Hash() uint64

func (*Identity) SourceLocation

func (id *Identity) SourceLocation() string

func (*Identity) String

func (id *Identity) String() string

type Level

type Level uint8

Level is log level TODO: allow change default logging level at compile time

Example
fmt.Println(FatalLevel.String())
fmt.Println(FatalLevel.AlignedUpperString())
Output:

fatal
FATA
const (
	// FatalLevel log error and call `os.Exit(1)`
	// TODO: allow user to add hooks before calling os.Exit?
	FatalLevel Level = iota
	// PanicLevel log error and call `panic`
	PanicLevel
	// ErrorLevel log error and do nothing
	// TODO: add integration with errors package
	ErrorLevel
	// WarnLevel log warning that is often ignored
	WarnLevel
	// InfoLevel log info
	InfoLevel
	// DebugLevel log debug message, user should enable DebugLevel logging when report bug
	DebugLevel
	// TraceLevel is very verbose, user should enable it only on packages they are currently investing instead of globally
	// TODO: add compile flag to use empty trace logger implementation to eliminate the call at runtime
	TraceLevel
)

func ParseLevel added in v0.0.11

func ParseLevel(s string) (Level, error)

ParseLevel converts a level string to log level, it is case insensitive. For invalid input, it returns InfoLevel and a non nil error

func (Level) AlignedUpperString added in v0.0.5

func (level Level) AlignedUpperString() string

AlignedUpperString returns log level with fixed length of 4 in uppercase, i.e. FATA, WARN

func (Level) ColoredAlignedUpperString added in v0.0.4

func (level Level) ColoredAlignedUpperString() string

ColoredAlignedUpperString returns fixed length level string in uppercase, wrapped by terminal color characters, only works on *nix

func (Level) ColoredString added in v0.0.3

func (level Level) ColoredString() string

TODO: use switch and generate the function ... or just generate it manually ColoredString returns level string wrapped by terminal color characters, only works on *nix

func (Level) String

func (level Level) String() string

String returns log level in lower case and not aligned in length, i.e. fatal, warn

type LoggableStruct

type LoggableStruct interface {
	GetLogger() *Logger
	SetLogger(logger *Logger)
	LoggerIdentity(justCallMe func() Identity) Identity
}

LoggableStruct is used to inject a logger into the struct, the methods for the interface can and should be generated using gommon.

In struct initializer call dlog.NewStructLogger(pkgLogger, structInstancePointer)

type Logger

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

Logger is a concrete type instead of interface because most logic is in handler. There is NO lock when calling logging methods, handlers may have locks. Lock is used when updating logger attributes like Level.

For Printf style logging (Levelf), Logger formats string using fmt.Sprintf before passing it to handlers.

logger.Debugf("id is %d", id)

For structural logging (LevelF), Logger passes fields to handlers without any processing.

logger.DebugF("hi", log.Fields{log.Str("foo", "bar")})

If you want to mix two styles, call fmt.Sprintf before calling DebugF,

logger.DebugF(fmt.Sprintf("id is %d", id), log.Fields{log.Str("foo", "bar")})

func NewPackageLogger

func NewPackageLogger() *Logger

func NewStructLogger

func NewStructLogger(packageLogger *Logger, loggable LoggableStruct) *Logger

func NewTestLogger added in v0.0.8

func NewTestLogger(level Level) *Logger

NewTestLogger does NOT have identity nor handler, it is mainly used for benchmark

func (*Logger) AddField added in v0.0.8

func (l *Logger) AddField(f Field) *Logger

AddField add field to current logger in place, it does NOT create a copy of logger Use WithField if you want a copy It does NOT check duplication

func (*Logger) AddFields added in v0.0.8

func (l *Logger) AddFields(fields ...Field) *Logger

AddFields add fields to current logger in place, it does NOT create a copy of logger Use WithFields if you want a copy It does NOT check duplication

func (*Logger) Copy added in v0.0.8

func (l *Logger) Copy() *Logger

Copy create a new logger with different identity, the identity is based on where Copy is called Normally you should call Copy inside func or method on a package/strcut logger

func (*Logger) Debug

func (l *Logger) Debug(args ...interface{})

func (*Logger) DebugF added in v0.0.3

func (l *Logger) DebugF(msg string, fields ...Field)

func (*Logger) Debugf

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

func (*Logger) DisableSource added in v0.0.3

func (l *Logger) DisableSource() *Logger

DisableSource turns off logging source file and line number

func (*Logger) EnableSource added in v0.0.3

func (l *Logger) EnableSource() *Logger

EnableSource turns on logging source file and line number

func (*Logger) Error

func (l *Logger) Error(args ...interface{})

func (*Logger) ErrorF added in v0.0.3

func (l *Logger) ErrorF(msg string, fields ...Field)

func (*Logger) Errorf

func (l *Logger) Errorf(format string, args ...interface{})

func (*Logger) Fatal

func (l *Logger) Fatal(args ...interface{})

Fatal calls os.Exit(1) after it writes and flushes the log

func (*Logger) FatalF added in v0.0.3

func (l *Logger) FatalF(msg string, fields ...Field)

FatalF duplicates instead of calling Fatal to keep source line correct

func (*Logger) Fatalf

func (l *Logger) Fatalf(format string, args ...interface{})

Fatalf duplicates instead of calling Fatal to keep source line correct

func (*Logger) Flush added in v0.0.8

func (l *Logger) Flush()

Flush calls Flush of its handler

func (*Logger) Identity

func (l *Logger) Identity() Identity

Identity returns the identity set when the logger is created. NOTE: caller can modify the identity because all fields are public, but they should NOT do this

func (*Logger) Info

func (l *Logger) Info(args ...interface{})

func (*Logger) InfoF added in v0.0.3

func (l *Logger) InfoF(msg string, fields ...Field)

func (*Logger) Infof

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

func (*Logger) IsDebugEnabled

func (l *Logger) IsDebugEnabled() bool

func (*Logger) IsErrorEnabled

func (l *Logger) IsErrorEnabled() bool

func (*Logger) IsInfoEnabled

func (l *Logger) IsInfoEnabled() bool

func (*Logger) IsPrintEnabled added in v0.0.8

func (l *Logger) IsPrintEnabled() bool

func (*Logger) IsTraceEnabled

func (l *Logger) IsTraceEnabled() bool

func (*Logger) IsWarnEnabled

func (l *Logger) IsWarnEnabled() bool

func (*Logger) Level

func (l *Logger) Level() Level

Level returns current level of logger

func (*Logger) NoopF added in v0.0.8

func (l *Logger) NoopF(msg string, fields ...Field)

Noop is only for test escape analysis

func (*Logger) Panic

func (l *Logger) Panic(args ...interface{})

Panic calls panic after it writes and flushes the log

func (*Logger) PanicF added in v0.0.3

func (l *Logger) PanicF(msg string, fields ...Field)

PanicF duplicates instead of calling Panic to keep source line correct

func (*Logger) Panicf

func (l *Logger) Panicf(format string, args ...interface{})

Panicf duplicates instead of calling Panic to keep source line correct

func (*Logger) Print added in v0.0.8

func (l *Logger) Print(args ...interface{})

func (*Logger) PrintF added in v0.0.8

func (l *Logger) PrintF(msg string, fields ...Field)

func (*Logger) Printf added in v0.0.8

func (l *Logger) Printf(format string, args ...interface{})

func (*Logger) ResetCallerSkip added in v0.0.8

func (l *Logger) ResetCallerSkip() *Logger

ResetCallerSkip set skip to 0, the default value

func (*Logger) SetCallerSkip added in v0.0.8

func (l *Logger) SetCallerSkip(skip int) *Logger

SetCallerSkip is used for util function to log using its caller's location instead of its own Without extra skip, some common util function will keep logging same line and make the real source hard to track.

func echo(w http.ResponseWriter, r *http.Request) {
    if r.Query().Get("word") == "" {
         writeError(w, errors.New("word is required")
         return
    }
    w.Write([]byte(r.Query().Get("word")))
}
func writeError(w http.ResponseWriter, err error) {
    l := pkgLogger.Copy().SetCallerSkip(1)
    l.Error(err)
    w.Write([]byte(err.String()))
}

func (*Logger) SetHandler

func (l *Logger) SetHandler(h Handler) *Logger

SetHandler sets handler of logger, it is thread safe

func (*Logger) SetLevel

func (l *Logger) SetLevel(level Level) *Logger

SetLevel sets level of logger, it is thread safe

func (*Logger) Trace

func (l *Logger) Trace(args ...interface{})

func (*Logger) TraceF added in v0.0.3

func (l *Logger) TraceF(msg string, fields ...Field)

func (*Logger) Tracef

func (l *Logger) Tracef(format string, args ...interface{})

func (*Logger) Warn

func (l *Logger) Warn(args ...interface{})

func (*Logger) WarnF added in v0.0.3

func (l *Logger) WarnF(msg string, fields ...Field)

func (*Logger) Warnf

func (l *Logger) Warnf(format string, args ...interface{})

func (*Logger) WithField added in v0.0.11

func (l *Logger) WithField(f Field) *Logger

WithField is Copy + AddField Use WithField when a common context is shared inside entire func/method Use *F methods if you just want adhoc fields, this won't create copy of entire logger i.e. logger.InfoF(dlog.Str("user", "cat007")) instead of logger.WithField(dlog.Str("user", "cat007")) TODO: there are some optimization here, i.e. the length is known to be + 1 and there is no need to lock

func (*Logger) WithFields added in v0.0.11

func (l *Logger) WithFields(fields ...Field) *Logger

WithFields is Copy + AddFields, see WithField doc on when and when not to use it. It is faster than calling WithField multiple times because of reduced alloc & copy on existing fields

type LoggerType

type LoggerType uint8

LoggerType can be used for filtering loggers, it is set when creating logger

const (
	UnknownLogger LoggerType = iota
	// PackageLogger is normally singleton in entire package
	// We used to have application and library logger but they are replaced by registry
	PackageLogger
	FunctionLogger
	StructLogger
	MethodLogger
)

func (LoggerType) String

func (tpe LoggerType) String() string

type Registry added in v0.0.8

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

Registry contains default and tracked loggers, it is per package

func NewRegistry added in v0.0.11

func NewRegistry() *Registry

NewRegistry create a log registry with a default logger for a package. It registers itself in globalRegistryGroup so it can be updated later using WalkRegistries

func (*Registry) Identity added in v0.0.8

func (r *Registry) Identity() string

func (*Registry) Logger added in v0.0.11

func (r *Registry) Logger() *Logger

Logger returns the default logger in registry

func (*Registry) NewLogger added in v0.0.11

func (r *Registry) NewLogger() *Logger

NewLogger creates a logger based on default logger and register it in registry. It should be used sparingly, if you need to add more fields as context for a func, you should make copy from default logger using methods like TODO: WithFields? to avoid register them in registry

type StructLoggerConfig added in v0.0.8

type StructLoggerConfig struct {
	Struct   string `yaml:"struct"`
	Receiver string `yaml:"receiver"`
	Field    string `yaml:"field"`
}

StructLoggerConfig is used to generate methods on struct for get identity using runtime, it also generates getter and setter

func (*StructLoggerConfig) Render added in v0.0.8

func (c *StructLoggerConfig) Render() ([]byte, error)

type Syncer added in v0.0.3

type Syncer interface {
	Sync() error
}

Syncer is implemented by os.File, handler implementation should check this interface and call Sync if they support using file as sink TODO: about sync - in go, both os.Stderr and os.Stdout are not (line) buffered - what would happen if os.Stderr.Close() - os.Stderr.Sync() will there be any different if stderr/stdout is redirected to a file

type TestHandler added in v0.0.5

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

TestHandler stores log as entry, its slice is protected by a RWMutex and safe for concurrent use

func NewTestHandler

func NewTestHandler() *TestHandler

NewTestHandler returns a test handler, it should only be used in test, a concrete type instead of Handler interface is returned to reduce unnecessary type cast in test

func (*TestHandler) Flush added in v0.0.5

func (h *TestHandler) Flush()

Flush implements Handler interface

func (*TestHandler) HandleLog added in v0.0.5

func (h *TestHandler) HandleLog(level Level, time time.Time, msg string, source Caller, context Fields, fields Fields)

func (*TestHandler) HasLog added in v0.0.5

func (h *TestHandler) HasLog(level Level, msg string) bool

HasLog checks if a log with specified level and message exists in slice TODO: support field, source etc.

Directories

Path Synopsis
handlers
cli
Package cli generates human readable text with color and display time in delta.
Package cli generates human readable text with color and display time in delta.
json
Package json writes log in JSON format, it escapes string in json based encoding/json, It does not use encoding/json directly because all the fields have known type
Package json writes log in JSON format, it escapes string in json based encoding/json, It does not use encoding/json directly because all the fields have known type
Package logx is extension for log package
Package logx is extension for log package

Jump to

Keyboard shortcuts

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