slog

package module
v1.1.2 Latest Latest
Warning

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

Go to latest
Published: Feb 21, 2022 License: MIT Imports: 13 Imported by: 42

README

Slf4go Build Status

Simple logger facade for Golang, inspired by slf4j, which focused on performance and scalability.

Introduction

Before introducing this library, let's walk through the composition of logging library.

  1. Provide API: like Trace Debug Info Warn Error etc.
  2. Collect Info: like timestamp, stacktrace, and other context fields etc.
  3. Format & Store: print log into stdout or store it directly etc.

For most logging library, 1 and 2 is quite similar, but different libraries may use different logging libraries, if your project dependents multi libraries, the final log could be very messy.

In the java language, most libraries use slf4j as its logging facade, you can decide to use logback or log4j etc as real logging implementation, and switch it easily.

I believe there should have similar "facade" in golang, and i hope this library could be golang's slf4j.

slf4go focus on 1 and 2, and will collect all information to build an integrated Log instance, it expect other library provide 3 implementation, for more details, check Driver section.

The structure of standard Log is:

type Log struct {
	Time   int64  `json:"date"`   // log's time(us)
	Logger string `json:"logger"` // log's name, default is package

	Pid        int     `json:"pid"`         // the process id which generated this log
	Gid        int     `json:"gid"`         // the goroutine id which generated this log
	DebugStack *string `json:"debug_stack"` // the debug stack of this log. Only for Panic and Fatal
	Stack      *Stack  `json:"stack"`       // the stack info of this log. Contains {Package, Filename, Function, Line}

	Level  Level         `json:"level"`  // log's level
	Format *string       `json:"format"` // log's format
	Args   []interface{} `json:"args"`   // log's format args
	Fields Fields        `json:"fields"` // additional custom fields
}

What need special explanation is, slf4go has very high performance, for more details, check Performance section.

Components

slf4go have several components:

  • Log: Log record's structure, contains Time, Logger, Pid, Gid, Stack, Fields, etc.
  • Logger: Provide api for Trace, Debug, Info, Warn, Error, Panic, Fatal etc.
  • Driver: It's an interface, used for decoupling Api and Implementation.
  • Hook: Provide a hook feature, can be used for log's synchronous callback.

For better understanding, check this chart:

Usage

This section provides complete instructions on how to install and use slf4go.

Install

Could use this command to install slf4go:

go get github.com/go-eden/slf4go

Could import slf4go like this:

import (
	slog "github.com/go-eden/slf4go"
)

Use Global Logger

By default, Slf4go provided a global Logger, in most case, you can use it directly by static function, don't need any other operation.

slog.Debugf("debug time: %v", time.Now())
slog.Warn("warn log")
slog.Error("error log")
slog.Panicf("panic time: %v", time.Now())

The final log is like this:

2019-06-16 19:35:05.167 [0] [TRACE] [main] default_example.go:12 debug time: 2019-06-16 19:35:05.167783 +0800 CST m=+0.000355435
2019-06-16 19:35:05.168 [0] [ WARN] [main] default_example.go:15 warn log
2019-06-16 19:35:05.168 [0] [ERROR] [main] default_example.go:17 error log
2019-06-16 19:35:05.168 [0] [PANIC] [main] default_example.go:20 panic time: 2019-06-16 19:35:05.168064 +0800 CST m=+0.000636402
goroutine 1 [running]:
runtime/debug.Stack(0x10aab40, 0xc0000b4100, 0x1)
	/usr/local/Cellar/go/1.12.6/libexec/src/runtime/debug/stack.go:24 +0x9d
github.com/go-eden/slf4go.Panicf(0x10cfd89, 0xe, 0xc0000b40f0, 0x1, 0x1)
	/Users/sulin/workspace/go-eden/slf4go/slf_core.go:191 +0x80
main.main()
	/Users/sulin/workspace/go-eden/slf4go/example/default_example.go:20 +0x213

What needs additional explanation is that panic and fatal will print goroutine stack automatically.

Use Your Own Logger

You can create your own Logger for other purposes:

log1 := slog.GetLogger() // Logger's name will be package name, like "main" or "github.com/go-eden/slf4go" etc
log1.Info("hello")
log2 := slog.NewLogger("test") // Logger's name will be the specified "test"
log2.Info("world")

The name of log1 will be caller's package name, like main or github.com/go-eden/slf4go etc, it depends on where you call it. The name of log2 will be the specified test.

Those name are important:

  • It would be fill into the final log directly.
  • It would be used to check if logging is enabled.
  • It would be used to decide whether and where to record the log.

Use Fields

You could use BindFields to add fields into the specified Logger, and use WithFields to create new Logger with the specified fields.

log1 := slog.GetLogger()
log1.BindFields(slog.Fields{"age": 18})
log1.Debug("hell1")

log1.WithFields(slog.Fields{"fav": "basketball"}).Warn("hello2")

log2 := log1.WithFields(slog.Fields{"fav": "basketball"})
log2.Info("hello2")

The fields will be attached to Log, and finally passed to Driver, the Driver decided how to print or where to store them.

Use Level

You can setup global level by SetLevel, which means the lower level log will be ignored.

slog.SetLevel(slog.WarnLevel)
slog.Info("no log") // will be ignored
slog.Error("error")

Above code setup global level to be WARN, so INFO log will be ignored, there should have other way to config different loggers' level, it based on which Driver you use.

You can check the specified level was enabled or not like this:

l := slog.GetLogger()
if l.IsDebugEnabled() {
    l.Debug("debug....")
}

In this example, slf4go will ask Driver whether accept DEBUG log of current logger, this process should cost few nanoseconds.

In fact, Logger will call IsDebugEnabled() in the Debug() function to filter unnecessary log, but this can't avoid the performance loss of preparing Debug()'s arguments, like string's concat.

As a comparison, preparing data and building Log would cost hundreds nanoseconds.

Use Hook

In slf4go, it's very easy to register log hook:

slog.RegisterHook(func(l *Log) {
    println(l) // you better not log again, it could be infinite loop 
})
slog.Debugf("are you prety? %t", true)

The RegisterHook accept func(*Log) argument, and slf4go will broadcast all Log to it asynchronously.

Driver

Driver is the bridge between the upper slf4go and the lower logging implementation.

// Driver define the standard log print specification
type Driver interface {
	// Retrieve the name of current driver, like 'default', 'logrus'...
	Name() string

	// Print responsible of printing the standard Log
	Print(l *Log)

	// Retrieve log level of the specified logger,
	// it should return the lowest Level that could be print,
	// which can help invoker to decide whether prepare print or not.
	GetLevel(logger string) Level
}

You can provider your own implementation, and replace the default driver.

Default StdDriver

By default, slf4go provides a StdDriver as fallback, it will format Log and print it into stdout directly, if you don't need other features, could use it directly.

This Driver will print log like this:

2019-06-16 19:35:05.168 [0] [ERROR] [github.com/go-eden/slf4go] default_example.go:17 error log

It contains these information:

  • 2019-06-16 19:35:05.168: Log's datetime.
  • [0]: Log's gid, the id of goroutine, like thread-id, it could be used for tracing.
  • [ERROR]: Log's level.
  • [github.com/go-eden/slf4go]: the caller's logger, it's package name by default.
  • default_example.go:17: the caller's filename and line.
  • error log: message, if level is PANIC or FATAL, DebugStack will be print too.

Other information was ignored, like fields pid etc.

Provide your own driver

You could implement a Driver easily, this is an example:

type MyDriver struct {}

func (*MyDriver) Name() string {
	return "my_driver"
}

func (*MyDriver) Print(l *slog.Log) {
    // print log, save db, send remote, etc.
}

func (*MyDriver) GetLevel(logger string) Level {
	return InfoLevel
}

func func init() {
	slog.SetDriver(new(MyDriver))
}

Above code provided an empty Driver, which means all log will be ignored totally.

Use slf4go-classic

TODO

User slf4go-zap

Thanks @phenix3443.

https://github.com/go-eden/slf4go-zap

Use slf4go-logrus

This is a simple Driver implementation for bridging slf4go and logrus.

I haven't used logrus too much, so this library don't have lots of features currently, hope you can help me improve this library, any Pull Request will be very welcomed.

https://github.com/go-eden/slf4go-logrus

Performance

This section's benchmark was based on MacBook Pro (15-inch, 2018).

When using empty Driver, the performace of slf4go is like this:

BenchmarkLogger-12    	 3000000	       420 ns/op	     112 B/op	       2 allocs/op

Which means if your Driver handles Log asychronously, log function like Debug Info Warn Error will complete in 500ns.

Here are some optimization points, hope it can help someone.

stack

Normally, you can obtain caller's trace Frame by runtime.Caller and runtime.FuncForPC, slf4go caches Frame for pc, which is a big performance boost.

In my benchmark, it improved performace from 430ns to 180ns, For more details, you can check source code.

etime

slf4go use etime to get system time, compared to time.Now(), etime.CurrentMicrosecond() has better performance.

In my benchmark, it improved performance from 68ns to 40ns.

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Debug

func Debug(v ...interface{})

Debug reference Logger.Debug

func Debugf

func Debugf(format string, v ...interface{})

Debugf reference Logger.Debugf

func DelContextField added in v1.1.0

func DelContextField(key string)

func EnableAsyncDriver added in v1.0.10

func EnableAsyncDriver()

EnableAsyncDriver enable the AsyncDriver, it has better performance

func Error

func Error(v ...interface{})

Error reference Logger.Error

func Errorf

func Errorf(format string, v ...interface{})

Errorf reference Logger.Errorf

func Fatal

func Fatal(v ...interface{})

Fatal reference Logger.Fatal

func Fatalf

func Fatalf(format string, v ...interface{})

Fatalf reference Logger.Fatalf

func GetContext

func GetContext() string

GetContext obtain the global context name

func GetContextField added in v1.1.0

func GetContextField(key string) (value interface{})

func Info

func Info(v ...interface{})

Info reference Logger.Info

func Infof

func Infof(format string, v ...interface{})

Infof reference Logger.Infof

func Panic

func Panic(v ...interface{})

Panic reference Logger.Panic

func Panicf

func Panicf(format string, v ...interface{})

Panicf reference Logger.Panicf

func RegisterHook

func RegisterHook(f func(*Log))

RegisterHook register a hook, all log will inform it

func SetContext

func SetContext(name string)

SetContext update the global context name

func SetContextField added in v1.1.0

func SetContextField(key string, value interface{})

func SetContextFields added in v1.1.0

func SetContextFields(fields Fields)

func SetDriver

func SetDriver(d Driver)

SetDriver update the global log driver

func SetLevel

func SetLevel(lv Level)

SetLevel update the global level, all lower level will not be send to driver to print

func SetLoggerLevel added in v1.0.8

func SetLoggerLevel(loggerName string, level Level)

SetLoggerLevel setup log-level of the specified logger by name

func SetLoggerLevelMap added in v1.0.8

func SetLoggerLevelMap(levelMap map[string]Level)

SetLoggerLevelMap batch set logger's level

func Trace

func Trace(v ...interface{})

Trace reference Logger.Trace

func Tracef

func Tracef(format string, v ...interface{})

Tracef reference Logger.Tracef

func Warn

func Warn(v ...interface{})

Warn reference Logger.Warn

func Warnf

func Warnf(format string, v ...interface{})

Warnf reference Logger.Warnf

Types

type AsyncDriver added in v1.0.10

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

AsyncDriver The async driver, it should have better performance

func (*AsyncDriver) GetLevel added in v1.0.10

func (t *AsyncDriver) GetLevel(_ string) Level

func (*AsyncDriver) Name added in v1.0.10

func (t *AsyncDriver) Name() string

func (*AsyncDriver) Print added in v1.0.10

func (t *AsyncDriver) Print(l *Log)

type Driver

type Driver interface {

	// Name return the name of current driver, like 'default', 'logrus'...
	Name() string

	// Print responsible of printing the standard Log
	Print(l *Log)

	// GetLevel return log level of the specified logger,
	// it should return the lowest Level that could be print,
	// which can help invoker to decide whether prepare print or not.
	GetLevel(logger string) Level
}

Driver define the standard log print specification

type Fields

type Fields map[string]interface{}

Fields represents attached fileds of log

func NewFields

func NewFields(fields ...Fields) Fields

NewFields merge multi fileds into new Fields instance

type Level

type Level int32
const (
	TraceLevel Level = iota
	DebugLevel
	InfoLevel
	WarnLevel
	ErrorLevel
	PanicLevel
	FatalLevel
)

func (Level) String

func (l Level) String() string

String Retrieve Level's name

type LevelSetting added in v1.0.8

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

type Log

type Log struct {
	Time   int64  `json:"date"`   // log's time(us)
	Logger string `json:"logger"` // log's name, default is package

	Pid        int     `json:"pid"`         // the process id which generated this log
	Gid        int     `json:"gid"`         // the goroutine id which generated this log
	Stack      *Stack  `json:"stack"`       // the stack info of this log
	DebugStack *string `json:"debug_stack"` // the debug stack of this log

	Level     Level         `json:"level"`      // log's level
	Format    *string       `json:"format"`     // log's format
	Args      []interface{} `json:"args"`       // log's format args
	Fields    Fields        `json:"fields"`     // additional custom fields
	CxtFields Fields        `json:"cxt_fields"` // caller's goroutine context fields
}

Log represent an log, contains all properties.

func NewLog

func NewLog(level Level, pc uintptr, debugStack *string, format *string, args []interface{}, fields, cxtFields Fields) *Log

NewLog create an new Log instance for better performance, caller should be provided by upper

func (*Log) Uptime

func (l *Log) Uptime() int64

Uptime obtain log's createTime relative to application's startTime

type Logger

type Logger interface {

	// Name obtain logger's name
	Name() string

	// Level obtain logger's level, lower will not be print
	Level() Level

	// BindFields add the specified fields into the current Logger.
	BindFields(fields Fields)

	// WithFields derive an new Logger by the specified fields from the current Logger.
	WithFields(fields Fields) Logger

	// IsTraceEnabled Whether trace of current logger enabled or not
	IsTraceEnabled() bool

	// IsDebugEnabled Whether debug of current logger enabled or not
	IsDebugEnabled() bool

	// IsInfoEnabled Whether info of current logger enabled or not
	IsInfoEnabled() bool

	// IsWarnEnabled Whether warn of current logger enabled or not
	IsWarnEnabled() bool

	// IsErrorEnabled Whether error of current logger enabled or not
	IsErrorEnabled() bool

	// IsPanicEnabled Whether panic of current logger enabled or not
	IsPanicEnabled() bool

	// IsFatalEnabled Whether fatal of current logger enabled or not
	IsFatalEnabled() bool

	// Trace record trace level's log
	Trace(v ...interface{})

	// Tracef record trace level's log with custom format.
	Tracef(format string, v ...interface{})

	// Debug record debug level's log
	Debug(v ...interface{})

	// Debugf record debug level's log with custom format.
	Debugf(format string, v ...interface{})

	// Info record info level's log
	Info(v ...interface{})

	// Infof record info level's log with custom format.
	Infof(format string, v ...interface{})

	// Warn record warn level's log
	Warn(v ...interface{})

	// Warnf record warn level's log with custom format.
	Warnf(format string, v ...interface{})

	// Error record error level's log
	Error(v ...interface{})

	// Errorf record error level's log with custom format.
	Errorf(format string, v ...interface{})

	// Panic record panic level's log
	Panic(v ...interface{})

	// Panicf record panic level's log with custom format
	Panicf(format string, v ...interface{})

	// Fatal record fatal level's log
	Fatal(v ...interface{})

	// Fatalf record fatal level's log with custom format.
	Fatalf(format string, v ...interface{})
}

func GetLogger

func GetLogger() (l Logger)

GetLogger create new Logger by caller's package name

func NewLogger

func NewLogger(name string) Logger

NewLogger create new Logger by the specified name

type Stack

type Stack struct {
	Package  string `json:"package"`
	Filename string `json:"filename"`
	Function string `json:"function"`
	Line     int    `json:"line"`
	// contains filtered or unexported fields
}

Stack represent pc's stack details.

func ParseStack

func ParseStack(pc uintptr) (s *Stack)

ParseStack retrieve pc's stack details, should cache result for performance optimization.

func ParseStack2 added in v1.0.8

func ParseStack2(pc uintptr) (s *Stack)

ParseStack2 this is slower, cannot use it.

type StdDriver

type StdDriver struct {
	sync.Mutex
}

StdDriver The default driver, just print stdout directly

func (*StdDriver) GetLevel

func (p *StdDriver) GetLevel(_ string) Level

func (*StdDriver) Name

func (p *StdDriver) Name() string

func (*StdDriver) Print

func (p *StdDriver) Print(l *Log)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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