Simple logger facade for Golang, inspired by slf4j, which focused on performance and scalability.
Before introducing this library, let's walk through the composition of logging library.
- Provide API: like
TraceDebugInfoWarnErroretc. - Collect Info: like timestamp, stacktrace, and other context fields etc.
- Format & Store: print log into
stdoutor 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.
slf4go have several components:
Log: Log record's structure, containsTime,Logger,Pid,Gid,Stack,Fields, etc.Logger: Provide api forTrace,Debug,Info,Warn,Error,Panic,Fataletc.Driver: It's an interface, used for decouplingApiandImplementation.Hook: Provide a hook feature, can be used for log's synchronous callback.
For better understanding, check this chart:
This section provides complete instructions on how to install and use slf4go.
Could use this command to install slf4go:
go get github.com/go-eden/slf4goCould import slf4go like this:
import (
slog "github.com/go-eden/slf4go"
)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.
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.
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.
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.
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 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.
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 ofgoroutine, likethread-id, it could be used for tracing.[ERROR]: Log's level.[github.com/go-eden/slf4go]: the caller'slogger, it's package name by default.default_example.go:17: the caller'sfilenameandline.error log: message, if level isPANICorFATAL,DebugStackwill be print too.
Other information was ignored, like fields pid etc.
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.
TODO
Thanks @phenix3443.
https://github.com/go-eden/slf4go-zap
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
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.
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.
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.
MIT
