log

package
v0.0.10 Latest Latest
Warning

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

Go to latest
Published: Apr 13, 2019 License: MIT Imports: 14 Imported by: 54

README

Log

Convention

  • library/application MUST have a library/application registry.
  • every package MUST have a package level logger.
  • logger should only register themselves in registry if it's a long lived, i.e. server

Usage

See _examples

In your project have a logutil package to serve as registry, and import this package in all the other packages to create package logger so you don't need to manually register them

import (
	"github.com/dyweb/gommon/log"
)

const Project = "github.com/your/project"

var registry = log.NewLibraryRegistry(Project)

func Registry() *log.Registry {
	return &registry
}

func NewPackageLoggerAndRegistry() (*log.Logger, *log.Registry) {
	logger, child := log.NewPackageLoggerAndRegistryWithSkip(Project, 1)
	registry.AddRegistry(child)
	return logger, child
}

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 server

import (
	dlog "github.com/dyweb/gommon/log"
	"github.com/your/project/logutil"
)

var log, logReg = logutil.NewPackageLoggerAndRegistry()

func foo(file string) {
	// structual way
	log.DebugF("open", dlog.Str("file", file))
	// default handler
	// debug 20180204 open file=test.yml
	// logfmtish handler
	// lvl=debug t=20180204 msg=open file=test.yml
	// json handler
	// {"lvl": "debug", "t": "20180204", "msg": "open", "file": "test.yml"}
	
	// traditional way
	log.Debugf("open %s", file)
	// debug 20180204 open test.yml
	
	// for expensive operation, check before log
	if log.IsDebugEnabled() {
		log.Debug("counter". dlog.Int("counter", CountExpensive()))
	}
}

func bar() {
	// create a new logger based on package level logger
	logger := log.Copy().AddField(dlog.Str("bar", "barbarbar"))
	logger.Info("have extra fields")
}

Documentation

Overview

Package log provides structured logging with fine grained control over libraries using a tree of logger registry

TODO: add convention and usage

Index

Examples

Constants

View Source
const (
	MagicStructLoggerFunctionName  = "LoggerIdentity"
	MagicPackageLoggerFunctionName = "init"
	// a hack for new init func name after 1.12
	// See https://github.com/dyweb/gommon/issues/108
	MagicPackageLoggerFunctionNameGo112 = "init.ializers"
)
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(root *Registry)

func EnableSource added in v0.0.8

func EnableSource(root *Registry)

func NewApplicationLoggerAndRegistry added in v0.0.8

func NewApplicationLoggerAndRegistry(project string) (*Logger, *Registry)

TODO: validate skip

func NewPackageLoggerAndRegistryWithSkip added in v0.0.8

func NewPackageLoggerAndRegistryWithSkip(project string, skip int) (*Logger, *Registry)

func SetHandler added in v0.0.8

func SetHandler(root *Registry, handler Handler)

func SetLevel added in v0.0.8

func SetLevel(root *Registry, level Level)

func WalkLogger added in v0.0.8

func WalkLogger(root *Registry, cb func(l *Logger))

WalkLogger is PreOrderDfs

func WalkRegistry added in v0.0.8

func WalkRegistry(root *Registry, cb func(r *Registry))

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

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 the two methods above

const (
	UnknownType FieldType = iota
	IntType
	StringType
)

type Fields

type Fields []Field

Fields is a slice of Field

func CopyFields added in v0.0.8

func CopyFields(fields Fields) Fields

CopyFields make a copy of the slice so modifying one won't have effect on another,

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

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 is used for applying filter rules and print logger hierarchy.

func NewIdentityFromCaller

func NewIdentityFromCaller(skip int) Identity

TODO: document all the black magic here ... https://github.com/dyweb/gommon/issues/32

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 (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 NewFunctionLogger deprecated

func NewFunctionLogger(packageLogger *Logger) *Logger

Deprecated: use Copy method on package logger

func NewMethodLogger deprecated

func NewMethodLogger(structLogger *Logger) *Logger

Deprecated: use Copy method on struct logger

func NewPackageLogger

func NewPackageLogger() *Logger

func NewPackageLoggerWithSkip

func NewPackageLoggerWithSkip(skip int) *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 and handler, it is mainly used for benchmark test

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 Copy 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 Copy 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

func (*Logger) EnableSource added in v0.0.3

func (l *Logger) EnableSource() *Logger

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

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

func (*Logger) SetLevel

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

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{})

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 child registry and loggers

func NewLibraryRegistry added in v0.0.8

func NewLibraryRegistry(project string) Registry

func (*Registry) AddLogger added in v0.0.8

func (r *Registry) AddLogger(l *Logger)

AddLogger is used for registering a logger to package level log registry It skips add if the logger is already there

func (*Registry) AddRegistry added in v0.0.8

func (r *Registry) AddRegistry(child *Registry)

AddRegistry is for adding a package level log registry to a library/application level log registry It skips add if child registry already there

func (*Registry) Identity added in v0.0.8

func (r *Registry) Identity() RegistryIdentity

type RegistryIdentity added in v0.0.8

type RegistryIdentity struct {
	// Project is specified by user, i.e. for all the packages under gommon, they would have github.com/dyweb/gommon
	Project string
	// Package is detected base on runtime, i.e. github.com/dyweb/gommon/noodle
	Package string
	// Type is specified by user when creating registry
	Type RegistryType
	// File is where create registry is called
	File string
	// Line is where create registry is called
	Line int
}

type RegistryType added in v0.0.8

type RegistryType uint8
const (
	UnknownRegistry RegistryType = iota
	ApplicationRegistry
	LibraryRegistry
	PackageRegistry
)

func (RegistryType) String added in v0.0.8

func (r RegistryType) String() string

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
Package benchmarks provides benchmark using go test and simulated real environment using pprof
Package benchmarks provides benchmark using go test and simulated real environment using pprof
_examples
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